diff --git a/Cargo.lock b/Cargo.lock index 0834a27d43..2f033cce32 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1459,7 +1459,6 @@ dependencies = [ "anyhow", "ax-crate-interface", "ax-driver", - "ax-errno 0.6.1", "ax-hal", "ax-std", "axbuild", @@ -1489,7 +1488,6 @@ dependencies = [ "ax-cpumask", "ax-crate-interface", "ax-driver", - "ax-errno 0.6.1", "ax-hal", "ax-kernel-guard", "ax-kspin", @@ -1524,6 +1522,7 @@ dependencies = [ "riscv_vcpu", "riscv_vplic", "spin", + "thiserror 2.0.18", "x86", "x86_vcpu", "x86_vlapic", diff --git a/os/axvisor/Cargo.toml b/os/axvisor/Cargo.toml index 1b056d7ae0..9e3c6f5b53 100644 --- a/os/axvisor/Cargo.toml +++ b/os/axvisor/Cargo.toml @@ -63,6 +63,7 @@ spin = { workspace = true } ax-crate-interface = { workspace = true } [target.'cfg(any(not(any(windows, unix)), target_env = "musl"))'.dependencies] +anyhow.workspace = true log = "0.4" # System dependent modules provided by ArceOS. @@ -73,7 +74,6 @@ ax-hal = { workspace = true, features = ["paging", "irq", "smp", "hv"] } axvm = { workspace = true, default-features = false } axvmconfig = { workspace = true, default-features = false } # System independent crates provided by ArceOS -ax-errno = { workspace = true } ax-driver.workspace = true axplat-dyn.workspace = true diff --git a/os/axvisor/src/config.rs b/os/axvisor/src/config.rs index 619da69ef5..16458124f1 100644 --- a/os/axvisor/src/config.rs +++ b/os/axvisor/src/config.rs @@ -12,14 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -use alloc::format; #[cfg(all( feature = "fs", any(target_arch = "x86_64", target_arch = "loongarch64") ))] use core::sync::atomic::{AtomicBool, Ordering}; -use ax_errno::{AxResult, ax_err_type}; +use anyhow::{Context, Result, anyhow, bail}; #[cfg(all(feature = "fs", target_arch = "x86_64"))] use axvm::InterruptTriggerMode; use axvm::{ @@ -33,6 +32,8 @@ use axvm::{ VMImageConfig, }, }; +#[cfg(feature = "fs")] +use axvm::{AxVmError, AxVmResult}; use axvmconfig::{AxVMCrateConfig, VMType}; #[cfg(all( @@ -99,15 +100,16 @@ pub fn init_guest_vms() { for raw_cfg_str in gvm_raw_configs { debug!("Initializing guest VM with config: {:#?}", raw_cfg_str); if let Err(e) = init_guest_vm(&raw_cfg_str) { - error!("Failed to initialize guest VM: {e:?}"); + error!("Failed to initialize guest VM: {e:#}"); } } } -pub fn init_guest_vm(raw_cfg: &str) -> AxResult { +pub fn init_guest_vm(raw_cfg: &str) -> Result { let image_provider = AxvisorBootImageProvider; let vm_create_config = AxVMCrateConfig::from_toml(raw_cfg) - .map_err(|e| ax_err_type!(InvalidData, format!("Failed to resolve VM config: {e:?}")))?; + .map_err(|error| anyhow!("parse VM TOML configuration: {error}"))?; + let configured_vm_id = vm_create_config.base.id; #[cfg(all( feature = "fs", @@ -123,7 +125,8 @@ pub fn init_guest_vm(raw_cfg: &str) -> AxResult { } let mut vm_config = build_axvm_config(&vm_create_config); - let prepared_boot = prepare_guest_boot(&mut vm_config, vm_create_config, &image_provider)?; + let prepared_boot = prepare_guest_boot(&mut vm_config, vm_create_config, &image_provider) + .with_context(|| format!("prepare boot resources for VM[{configured_vm_id}]"))?; let prepared_config = prepared_boot.config(); sync_axvm_config_from_crate_config(&mut vm_config, prepared_config); @@ -134,26 +137,26 @@ pub fn init_guest_vm(raw_cfg: &str) -> AxResult { info!("Creating VM[{}] {:?}", vm_config.id(), vm_config.name()); // Create VM. - let vm = AxVM::new(vm_config) - .map_err(|e| ax_err_type!(InvalidData, format!("Failed to create VM: {e:?}")))?; + let vm = AxVM::new(vm_config).with_context(|| format!("create VM[{configured_vm_id}]"))?; let vm_id = vm.id(); - let memory_layout = vm.prepare_memory_layout()?; + let memory_layout = vm + .prepare_memory_layout() + .with_context(|| format!("prepare memory layout for VM[{vm_id}]"))?; let main_mem = memory_layout.main_memory().clone(); // Load corresponding images for VM. info!("VM[{}] created success, loading images...", vm.id()); - prepared_boot.load_images(main_mem, vm.clone(), &image_provider)?; + prepared_boot + .load_images(main_mem, vm.clone(), &image_provider) + .with_context(|| format!("load boot images for VM[{vm_id}]"))?; vm.prepare() - .map_err(|e| ax_err_type!(InvalidData, format!("VM[{}] setup failed: {e:?}", vm.id())))?; + .with_context(|| format!("prepare devices and vCPUs for VM[{vm_id}]"))?; if !axvm::register_vm(vm) { - return Err(ax_err_type!( - AlreadyExists, - format!("VM[{vm_id}] already exists") - )); + bail!("register VM[{vm_id}]: a VM with this ID already exists"); } #[cfg(target_arch = "loongarch64")] crate::manager::register_loongarch_passthrough_irq_routes(vm_id); @@ -363,18 +366,33 @@ impl BootImageProvider for AxvisorBootImageProvider { } #[cfg(feature = "fs")] - fn read_file(&self, file_name: &str) -> AxResult> { + fn read_file(&self, file_name: &str) -> AxVmResult> { crate::manager::AxvmManager::read_file(file_name) + .map_err(|error| boot_file_error("read guest image file", file_name, error)) } #[cfg(feature = "fs")] - fn read_file_exact(&self, file_name: &str, read_size: usize) -> AxResult> { + fn read_file_exact( + &self, + file_name: &str, + read_size: usize, + ) -> AxVmResult> { crate::manager::AxvmManager::read_file_exact(file_name, read_size) + .map_err(|error| boot_file_error("read guest image file", file_name, error)) } #[cfg(feature = "fs")] - fn file_size(&self, file_name: &str) -> AxResult { + fn file_size(&self, file_name: &str) -> AxVmResult { crate::manager::AxvmManager::file_size(file_name) + .map_err(|error| boot_file_error("inspect guest image file", file_name, error)) + } +} + +#[cfg(feature = "fs")] +fn boot_file_error(operation: &'static str, file_name: &str, error: anyhow::Error) -> AxVmError { + AxVmError::Boot { + operation, + detail: format!("`{file_name}`: {error:#}"), } } diff --git a/os/axvisor/src/main.rs b/os/axvisor/src/main.rs index a3b1214516..002372df09 100644 --- a/os/axvisor/src/main.rs +++ b/os/axvisor/src/main.rs @@ -48,7 +48,8 @@ fn main() { banner::print_logo(); info!("Starting virtualization..."); - let manager = manager::AxvmManager::new().expect("failed to initialize AxVM manager"); + let manager = manager::AxvmManager::new() + .unwrap_or_else(|error| panic!("failed to initialize AxVM manager: {error:#}")); manager.init_default_vms(); manager.start_default_vms(); diff --git a/os/axvisor/src/manager.rs b/os/axvisor/src/manager.rs index d2ba028472..5c36472844 100644 --- a/os/axvisor/src/manager.rs +++ b/os/axvisor/src/manager.rs @@ -7,7 +7,9 @@ use alloc::vec::Vec; #[cfg(feature = "fs")] use alloc::{string::String, vec, vec::Vec}; -use ax_errno::AxResult; +#[cfg(feature = "fs")] +use anyhow::anyhow; +use anyhow::{Context, Result}; use axvm::{AxVMRef, AxvmRuntime, VMId}; /// AxVM top-level manager. @@ -21,9 +23,9 @@ pub struct AxvmManager { impl AxvmManager { /// Initialize the AxVM runtime services. - pub fn new() -> AxResult { + pub fn new() -> Result { Ok(Self { - runtime: AxvmRuntime::new()?, + runtime: AxvmRuntime::new().context("initialize AxVM runtime")?, }) } @@ -40,28 +42,28 @@ impl AxvmManager { } /// Create one VM from a TOML config string. - pub fn create_vm_from_toml(raw_cfg: &str) -> AxResult { - crate::config::init_guest_vm(raw_cfg) + pub fn create_vm_from_toml(raw_cfg: &str) -> Result { + crate::config::init_guest_vm(raw_cfg).context("create VM from TOML configuration") } /// Start a VM by ID. - pub fn start_vm(vm_id: VMId) -> AxResult { - AxvmRuntime::start_vm(vm_id) + pub fn start_vm(vm_id: VMId) -> Result<()> { + AxvmRuntime::start_vm(vm_id).with_context(|| format!("start VM[{vm_id}]")) } /// Stop a VM by ID. - pub fn stop_vm(vm_id: VMId) -> AxResult { - AxvmRuntime::stop_vm(vm_id) + pub fn stop_vm(vm_id: VMId) -> Result<()> { + AxvmRuntime::stop_vm(vm_id).with_context(|| format!("stop VM[{vm_id}]")) } /// Resume a VM by ID. - pub fn resume_vm(vm_id: VMId) -> AxResult { - AxvmRuntime::resume_vm(vm_id) + pub fn resume_vm(vm_id: VMId) -> Result<()> { + AxvmRuntime::resume_vm(vm_id).with_context(|| format!("resume VM[{vm_id}]")) } /// Reset a VM by ID. - pub fn reset_vm(vm_id: VMId) -> AxResult { - AxvmRuntime::reset_vm(vm_id) + pub fn reset_vm(vm_id: VMId) -> Result<()> { + AxvmRuntime::reset_vm(vm_id).with_context(|| format!("reset VM[{vm_id}]")) } /// Remove a VM by ID. @@ -145,7 +147,7 @@ impl AxvmManager { let file_size = match Self::file_size(path_str) { Ok(file_size) => file_size, Err(e) => { - error!("Failed to get config file {} metadata: {:?}", path_str, e); + error!("Failed to get config file {path_str} metadata: {e:#}"); continue; } }; @@ -159,7 +161,7 @@ impl AxvmManager { let buffer = match Self::read_file_exact(path_str, file_size) { Ok(buffer) => buffer, Err(e) => { - error!("Failed to read file {}: {:?}", path_str, e); + error!("Failed to read file {path_str}: {e:#}"); continue; } }; @@ -174,53 +176,33 @@ impl AxvmManager { } #[cfg(feature = "fs")] - fn open_file(file_name: &str) -> AxResult { - ax_std::fs::File::open(file_name).map_err(|err| { - ax_errno::ax_err_type!( - NotFound, - alloc::format!( - "Failed to open {}, err {:?}, please check your disk.img", - file_name, - err - ) - ) - }) + fn open_file(file_name: &str) -> Result { + ax_std::fs::File::open(file_name) + .map_err(|error| anyhow!("open guest image file `{file_name}`: {error}")) } #[cfg(feature = "fs")] - pub fn file_size(file_name: &str) -> AxResult { + pub fn file_size(file_name: &str) -> Result { Self::open_file(file_name)? .metadata() - .map_err(|err| { - ax_errno::ax_err_type!( - Io, - alloc::format!( - "Failed to get metadate of file {}, err {:?}", - file_name, - err - ) - ) - }) + .map_err(|error| anyhow!("read metadata for guest image file `{file_name}`: {error}")) .map(|metadata| metadata.size() as usize) } #[cfg(feature = "fs")] - pub fn read_file_exact(file_name: &str, read_size: usize) -> AxResult> { + pub fn read_file_exact(file_name: &str, read_size: usize) -> Result> { use ax_std::io::Read; let mut file = Self::open_file(file_name)?; let mut buffer = vec![0u8; read_size]; - file.read_exact(&mut buffer).map_err(|err| { - ax_errno::ax_err_type!( - Io, - alloc::format!("Failed in reading from file {}, err {:?}", file_name, err) - ) + file.read_exact(&mut buffer).map_err(|error| { + anyhow!("read {read_size} bytes from guest image file `{file_name}`: {error}") })?; Ok(buffer) } #[cfg(feature = "fs")] - pub fn read_file(file_name: &str) -> AxResult> { + pub fn read_file(file_name: &str) -> Result> { let size = Self::file_size(file_name)?; Self::read_file_exact(file_name, size) } diff --git a/os/axvisor/src/shell/command/vm.rs b/os/axvisor/src/shell/command/vm.rs index 75faf258c6..0fea644d7f 100644 --- a/os/axvisor/src/shell/command/vm.rs +++ b/os/axvisor/src/shell/command/vm.rs @@ -20,6 +20,7 @@ use std::{ vec::Vec, }; +use anyhow::Context; use axvm::{StopReason, VmStatus, VmVcpuState}; #[cfg(feature = "fs")] use std::fs::read_to_string; @@ -153,11 +154,8 @@ fn vm_create(cmd: &ParsedCommand) { vm_id, config_path ); } - Err(_) => { - println!( - "✗ Failed to create VM from {}: Configuration error or panic occurred", - config_path - ); + Err(error) => { + println!("✗ Failed to create VM from {config_path}: {error:#}"); } }, Err(e) => { @@ -202,7 +200,7 @@ fn vm_start(cmd: &ParsedCommand) { } if let Err(e) = start_single_vm(vm.clone()) { - println!("✗ VM[{}] failed to start: {:?}", vm.id(), e); + println!("✗ VM[{}] failed to start: {e:#}", vm.id()); } else { println!("✓ VM[{}] started successfully", vm.id()); started_count += 1; @@ -230,21 +228,13 @@ fn vm_start(cmd: &ParsedCommand) { /// Start a single VM by setting up vCPUs and calling boot. /// Returns Ok(()) if successful, Err otherwise. -fn start_single_vm(vm: axvm::AxVMRef) -> Result<(), &'static str> { +fn start_single_vm(vm: axvm::AxVMRef) -> anyhow::Result<()> { let vm_id = vm.id(); let status = vm.status(); // Validate state transition using helper function - can_start_vm(status)?; - - match crate::manager::AxvmManager::start_vm(vm_id) { - Ok(_) => Ok(()), - Err(err) => { - // Revert status on failure - error!("Failed to boot VM[{}]: {:?}", vm_id, err); - Err("Failed to boot VM") - } - } + can_start_vm(status).map_err(anyhow::Error::msg)?; + crate::manager::AxvmManager::start_vm(vm_id).with_context(|| format!("boot VM[{vm_id}]")) } fn start_vm_by_id(vm_id: usize) { @@ -253,7 +243,7 @@ fn start_vm_by_id(vm_id: usize) { println!("✓ VM[{}] started successfully", vm_id); } Some(Err(err)) => { - println!("✗ VM[{}] failed to start: {}", vm_id, err); + println!("✗ VM[{vm_id}] failed to start: {err:#}"); } None => { println!("✗ VM[{}] not found", vm_id); @@ -285,10 +275,7 @@ fn stop_vm_by_id(vm_id: usize, force: bool) { let status = vm.status(); // Validate state transition using helper function - if let Err(err) = can_stop_vm(status, force) { - println!("⚠ VM[{}] {}", vm_id, err); - return Err(err); - } + can_stop_vm(status, force).map_err(anyhow::Error::msg)?; // Print appropriate message based on status match status { @@ -312,13 +299,8 @@ fn stop_vm_by_id(vm_id: usize, force: bool) { } // Call shutdown - match crate::manager::AxvmManager::stop_vm(vm_id) { - Ok(_) => Ok(()), - Err(_err) => { - // Revert status on failure - Err("Failed to shutdown VM") - } - } + crate::manager::AxvmManager::stop_vm(vm_id) + .with_context(|| format!("send shutdown request to VM[{vm_id}]")) }) { Some(Ok(_)) => { println!("✓ VM[{}] stop signal sent successfully", vm_id); @@ -327,7 +309,7 @@ fn stop_vm_by_id(vm_id: usize, force: bool) { ); } Some(Err(err)) => { - println!("✗ Failed to stop VM[{}]: {:?}", vm_id, err); + println!("✗ Failed to stop VM[{vm_id}]: {err:#}"); } None => { println!("✗ VM[{}] not found", vm_id); @@ -358,7 +340,7 @@ fn reset_vm_by_id(vm_id: usize) { println!("Resetting VM[{}]...", vm_id); match crate::manager::AxvmManager::reset_vm(vm_id) { Ok(()) => println!("✓ VM[{}] reset and started successfully", vm_id), - Err(err) => println!("✗ VM[{}] reset failed: {:?}", vm_id, err), + Err(err) => println!("✗ VM[{vm_id}] reset failed: {err:#}"), } } @@ -392,13 +374,13 @@ fn vm_suspend(cmd: &ParsedCommand) { fn suspend_vm_by_id(vm_id: usize) { println!("Suspending VM[{}]...", vm_id); - let result: Option> = crate::manager::AxvmManager::with_vm(vm_id, |vm| { + let result: Option> = crate::manager::AxvmManager::with_vm(vm_id, |vm| { let status = vm.status(); // Check if VM can be suspended - can_suspend_vm(status)?; + can_suspend_vm(status).map_err(anyhow::Error::msg)?; - vm.pause().map_err(|_| "Failed to suspend VM")?; + vm.pause().with_context(|| format!("suspend VM[{vm_id}]"))?; info!("VM[{}] status set to Paused", vm_id); Ok(()) @@ -459,7 +441,7 @@ fn suspend_vm_by_id(vm_id: usize) { println!(" Use 'vm resume {}' to resume the VM", vm_id); } Some(Err(err)) => { - println!("✗ Failed to suspend VM[{}]: {}", vm_id, err); + println!("✗ Failed to suspend VM[{vm_id}]: {err:#}"); } None => { println!("✗ VM[{}] not found", vm_id); @@ -489,13 +471,14 @@ fn vm_resume(cmd: &ParsedCommand) { fn resume_vm_by_id(vm_id: usize) { println!("Resuming VM[{}]...", vm_id); - let result: Option> = crate::manager::AxvmManager::with_vm(vm_id, |vm| { + let result: Option> = crate::manager::AxvmManager::with_vm(vm_id, |vm| { let status = vm.status(); // Check if VM can be resumed - can_resume_vm(status)?; + can_resume_vm(status).map_err(anyhow::Error::msg)?; - crate::manager::AxvmManager::resume_vm(vm_id).map_err(|_| "Failed to resume VM")?; + crate::manager::AxvmManager::resume_vm(vm_id) + .with_context(|| format!("resume suspended VM[{vm_id}]"))?; info!("VM[{}] resumed", vm_id); Ok(()) @@ -506,7 +489,7 @@ fn resume_vm_by_id(vm_id: usize) { println!("✓ VM[{}] resumed successfully", vm_id); } Some(Err(err)) => { - println!("✗ Failed to resume VM[{}]: {}", vm_id, err); + println!("✗ Failed to resume VM[{vm_id}]: {err:#}"); } None => { println!("✗ VM[{}] not found", vm_id); @@ -607,7 +590,7 @@ fn delete_vm_by_id(vm_id: usize, keep_data: bool) { match crate::manager::AxvmManager::remove_vm(vm_id) { Some(vm) => { if let Err(err) = vm.destroy() { - println!("⚠ VM[{}] destroy failed: {:?}", vm_id, err); + println!("⚠ VM[{vm_id}] destroy failed: {err}"); } println!("✓ VM[{}] removed from VM list", vm_id); diff --git a/virtualization/axvm/Cargo.toml b/virtualization/axvm/Cargo.toml index 7e1575e2b5..5dfbb6c52c 100644 --- a/virtualization/axvm/Cargo.toml +++ b/virtualization/axvm/Cargo.toml @@ -26,9 +26,9 @@ bitflags = "2.9" byte-unit = { version = "5", default-features = false, features = ["byte"] } numeric-enum-macro = "0.2" spin = { workspace = true } +thiserror = { workspace = true } # System independent crates provided by ArceOS. -ax-errno = { workspace = true } ax-cpumask = { workspace = true } ax-crate-interface = { workspace = true } ax-kernel-guard = { workspace = true } diff --git a/virtualization/axvm/src/arch/aarch64/capabilities.rs b/virtualization/axvm/src/arch/aarch64/capabilities.rs index b232555ad7..bf10515d51 100644 --- a/virtualization/axvm/src/arch/aarch64/capabilities.rs +++ b/virtualization/axvm/src/arch/aarch64/capabilities.rs @@ -2,10 +2,12 @@ use alloc::format; -use ax_errno::{AxResult, ax_err_type}; - use super::Aarch64Arch; -use crate::architecture::{BootImagePlatform, GuestBootPlatform, HostTimePlatform}; +use crate::{ + AxVmResult, + architecture::{BootImagePlatform, GuestBootPlatform, HostTimePlatform}, + ax_err_type, +}; impl HostTimePlatform for Aarch64Arch {} @@ -13,7 +15,7 @@ impl BootImagePlatform for Aarch64Arch { fn load_guest_dtb( loader: &crate::boot::images::ImageLoaderCore<'_>, dtb: &crate::boot::fdt::GuestDtbImage, - ) -> AxResult { + ) -> AxVmResult { let bytes = dtb.as_bytes(); let source = core::ptr::NonNull::new(bytes.as_ptr() as *mut u8) .ok_or_else(|| ax_err_type!(InvalidData, "Guest DTB pointer is null"))?; @@ -26,7 +28,7 @@ impl GuestBootPlatform for Aarch64Arch { vm_config: &mut crate::config::AxVMConfig, vm_create_config: &mut axvmconfig::AxVMCrateConfig, provider: &dyn crate::boot::BootImageProvider, - ) -> AxResult> { + ) -> AxVmResult> { super::fdt::core::prepare_dtb_guest(vm_config, vm_create_config, provider) } } @@ -49,7 +51,7 @@ pub(super) fn patch_runtime_fdt( fdt_bytes: &[u8], vm: &crate::AxVMRef, crate_config: &axvmconfig::AxVMCrateConfig, -) -> AxResult> { +) -> AxVmResult> { let initrd = vm.with_config(|config| { super::fdt::initrd_start_size_from_image_config(config.image_config.ramdisk.as_ref()) }); @@ -66,7 +68,7 @@ pub(super) fn patch_provided_fdt( provided_dtb: &[u8], host_dtb: Option<&[u8]>, crate_config: &axvmconfig::AxVMCrateConfig, -) -> AxResult> { +) -> AxVmResult> { let provided_fdt = fdt_edit::Fdt::from_bytes(provided_dtb).map_err(|err| { ax_err_type!( InvalidData, diff --git a/virtualization/axvm/src/arch/aarch64/fdt.rs b/virtualization/axvm/src/arch/aarch64/fdt.rs index a3c0db1fb5..ba77ccad6e 100644 --- a/virtualization/axvm/src/arch/aarch64/fdt.rs +++ b/virtualization/axvm/src/arch/aarch64/fdt.rs @@ -2,10 +2,10 @@ use alloc::vec::Vec; -use ax_errno::{AxResult, ax_err_type}; use fdt_edit::Fdt; use crate::{ + AxVmResult, ax_err_type, boot::{BootImageProvider, fdt::GuestDtbImage}, config::AxVMConfig, }; @@ -46,7 +46,7 @@ pub(super) fn update_cpu_node( fdt: &Fdt, host_fdt: Option<&Fdt>, crate_config: &axvmconfig::AxVMCrateConfig, -) -> AxResult> { +) -> AxVmResult> { let Some(host_fdt) = host_fdt else { return Ok(fdt.encode().as_ref().to_vec()); }; @@ -92,6 +92,6 @@ pub fn handle_fdt_operations( vm_config: &mut AxVMConfig, vm_create_config: &mut axvmconfig::AxVMCrateConfig, provider: &dyn BootImageProvider, -) -> AxResult> { +) -> AxVmResult> { core::prepare_dtb_guest(vm_config, vm_create_config, provider) } diff --git a/virtualization/axvm/src/arch/aarch64/images.rs b/virtualization/axvm/src/arch/aarch64/images.rs index f2f256817f..9684e1c3c9 100644 --- a/virtualization/axvm/src/arch/aarch64/images.rs +++ b/virtualization/axvm/src/arch/aarch64/images.rs @@ -1,10 +1,9 @@ //! Public AArch64 image-loader facade preserving the DTB constructor contract. -use ax_errno::AxResult; use axvmconfig::AxVMCrateConfig; use crate::{ - AxVMRef, VMMemoryRegion, + AxVMRef, AxVmResult, VMMemoryRegion, boot::{BootImageProvider, fdt::GuestDtbImage, images::ImageLoaderCore}, }; @@ -27,7 +26,7 @@ impl<'a> ImageLoader<'a> { )) } - pub fn load(&mut self) -> AxResult { + pub fn load(&mut self) -> AxVmResult { self.0.load() } } diff --git a/virtualization/axvm/src/arch/aarch64/ipi.rs b/virtualization/axvm/src/arch/aarch64/ipi.rs index 09f99fcc04..2e0700347a 100644 --- a/virtualization/axvm/src/arch/aarch64/ipi.rs +++ b/virtualization/axvm/src/arch/aarch64/ipi.rs @@ -1,9 +1,7 @@ //! AArch64 guest IPI target resolution and injection. -use ax_errno::AxResult; - use super::Aarch64DeferredRunWork; -use crate::architecture::BoundVcpuExit; +use crate::{AxVmResult, architecture::BoundVcpuExit}; #[derive(Clone, Copy, Debug)] pub(crate) struct SendIpiExit { @@ -18,7 +16,7 @@ pub(crate) fn handle( vm: &crate::AxVMRef, vcpu_id: usize, exit: SendIpiExit, -) -> AxResult> { +) -> AxVmResult> { let vm_id = vm.id(); debug!( "VM[{vm_id}] run VCpu[{vcpu_id}] SendIPI, target_cpu={:#x}, target_cpu_aux={:#x}, \ diff --git a/virtualization/axvm/src/arch/aarch64/mod.rs b/virtualization/axvm/src/arch/aarch64/mod.rs index 702663fb88..c2b7ead900 100644 --- a/virtualization/axvm/src/arch/aarch64/mod.rs +++ b/virtualization/axvm/src/arch/aarch64/mod.rs @@ -13,15 +13,17 @@ use arm_vcpu::{ }; use arm_vgic::host::ArmVgicHostIf; use ax_crate_interface::impl_interface; -use ax_errno::{AxError, AxResult, ax_err}; use ax_memory_addr::{PhysAddr, VirtAddr}; use axvm_types::{ - AccessWidth, GuestPhysAddr, NestedPagingConfig, SysRegAddr, VCpuId, VMId, VmArchPerCpuOps, - VmArchVcpuOps, + AccessWidth, AxVmError as BackendError, AxVmResult as BackendResult, GuestPhysAddr, + NestedPagingConfig, SysRegAddr, VCpuId, VMId, VmArchPerCpuOps, VmArchVcpuOps, }; use super::{ArchOps, BoundVcpuExit, HypercallExit, MmioReadExit, MmioWriteExit, VcpuRunAction}; -use crate::host::{HostCpu, HostMemory, HostTime, default_host}; +use crate::{ + AxVmResult, ax_err, + host::{HostCpu, HostMemory, HostTime, default_host}, +}; mod capabilities; #[path = "../../architecture/cpu_up.rs"] @@ -72,7 +74,7 @@ impl ArchOps for Aarch64Arch { vm: &crate::AxVMRef, vcpu: &crate::vm::AxVCpuRef, exit: ::Exit, - ) -> AxResult> { + ) -> AxVmResult> { match exit { ArmVmExit::Hypercall { nr, args } => { super::handle_hypercall(vm, vcpu, HypercallExit { nr, args }) @@ -185,7 +187,7 @@ impl ArchOps for Aarch64Arch { vm: &crate::AxVMRef, vcpu: &crate::vm::AxVCpuRef, work: Self::DeferredRunWork, - ) -> AxResult { + ) -> AxVmResult { match work { Aarch64DeferredRunWork::ExternalInterrupt { vector } => { Self::after_external_interrupt(vm, vcpu, vector); @@ -222,34 +224,34 @@ impl VmArchVcpuOps for AxvmArmVcpu { type SetupConfig = ArmVcpuSetupConfig; type Exit = ArmVmExit; - fn new(vm_id: VMId, vcpu_id: VCpuId, config: Self::CreateConfig) -> AxResult { + fn new(vm_id: VMId, vcpu_id: VCpuId, config: Self::CreateConfig) -> BackendResult { arm_result(ArmVcpu::new(vm_id, vcpu_id, config)).map(Self) } - fn set_entry(&mut self, entry: GuestPhysAddr) -> AxResult { + fn set_entry(&mut self, entry: GuestPhysAddr) -> BackendResult { arm_result(self.0.set_entry(ax_guest_phys_addr_to_arm(entry))) } - fn set_nested_page_table(&mut self, config: NestedPagingConfig) -> AxResult { + fn set_nested_page_table(&mut self, config: NestedPagingConfig) -> BackendResult { arm_result( self.0 .set_nested_page_table(ax_nested_paging_to_arm(config)), ) } - fn setup(&mut self, config: Self::SetupConfig) -> AxResult { + fn setup(&mut self, config: Self::SetupConfig) -> BackendResult { arm_result(self.0.setup(config)) } - fn run(&mut self) -> AxResult { + fn run(&mut self) -> BackendResult { arm_result(self.0.run()) } - fn bind(&mut self) -> AxResult { + fn bind(&mut self) -> BackendResult { arm_result(self.0.bind()) } - fn unbind(&mut self) -> AxResult { + fn unbind(&mut self) -> BackendResult { arm_result(self.0.unbind()) } @@ -257,7 +259,7 @@ impl VmArchVcpuOps for AxvmArmVcpu { self.0.set_gpr(reg, val); } - fn inject_interrupt(&mut self, vector: usize) -> AxResult { + fn inject_interrupt(&mut self, vector: usize) -> BackendResult { arm_result(self.0.inject_interrupt(vector)) } @@ -269,7 +271,7 @@ impl VmArchVcpuOps for AxvmArmVcpu { pub(crate) struct AxvmArmPerCpu(ArmPerCpu); impl VmArchPerCpuOps for AxvmArmPerCpu { - fn new(cpu_id: usize) -> AxResult { + fn new(cpu_id: usize) -> BackendResult { arm_result(ArmPerCpu::new(cpu_id)).map(Self) } @@ -277,11 +279,11 @@ impl VmArchPerCpuOps for AxvmArmPerCpu { self.0.is_enabled() } - fn hardware_enable(&mut self) -> AxResult { + fn hardware_enable(&mut self) -> BackendResult { arm_result(self.0.hardware_enable::()) } - fn hardware_disable(&mut self) -> AxResult { + fn hardware_disable(&mut self) -> BackendResult { arm_result(self.0.hardware_disable()) } @@ -294,15 +296,15 @@ impl VmArchPerCpuOps for AxvmArmPerCpu { } } -fn arm_result(result: ArmVcpuResult) -> AxResult { +fn arm_result(result: ArmVcpuResult) -> BackendResult { result.map_err(arm_error_to_ax) } -fn arm_error_to_ax(err: ArmVcpuError) -> AxError { +fn arm_error_to_ax(err: ArmVcpuError) -> BackendError { match err { - ArmVcpuError::InvalidInput => AxError::InvalidInput, - ArmVcpuError::Unsupported => AxError::Unsupported, - ArmVcpuError::BadState => AxError::BadState, + ArmVcpuError::InvalidInput => BackendError::InvalidInput, + ArmVcpuError::Unsupported => BackendError::Unsupported, + ArmVcpuError::BadState => BackendError::BadState, } } @@ -344,13 +346,16 @@ mod tests { fn converts_arm_vcpu_errors_to_ax_errors() { assert_eq!( arm_error_to_ax(ArmVcpuError::InvalidInput), - AxError::InvalidInput + BackendError::InvalidInput ); assert_eq!( arm_error_to_ax(ArmVcpuError::Unsupported), - AxError::Unsupported + BackendError::Unsupported + ); + assert_eq!( + arm_error_to_ax(ArmVcpuError::BadState), + BackendError::BadState ); - assert_eq!(arm_error_to_ax(ArmVcpuError::BadState), AxError::BadState); } fn assert_arm_exit_type>() {} diff --git a/virtualization/axvm/src/arch/aarch64/vm.rs b/virtualization/axvm/src/arch/aarch64/vm.rs index 43b3972f18..ca91b02eac 100644 --- a/virtualization/axvm/src/arch/aarch64/vm.rs +++ b/virtualization/axvm/src/arch/aarch64/vm.rs @@ -3,12 +3,12 @@ use alloc::sync::Arc; use arm_vcpu::{ArmVcpuCreateConfig, ArmVcpuSetupConfig}; -use ax_errno::{AxResult, ax_err, ax_err_type}; use axdevice_base::DeviceRegistry as _; use axvm_types::{NestedPagingConfig, VMInterruptMode, VmArchVcpuOps}; use super::{Aarch64Arch, npt}; use crate::{ + AxVmResult, ax_err, ax_err_type, config::AxVMConfig, vm::{ AxVM, AxVMResources, @@ -24,7 +24,7 @@ use crate::{ }; impl Aarch64Arch { - pub(crate) fn create_vm_resources(config: AxVMConfig) -> AxResult { + pub(crate) fn create_vm_resources(config: AxVMConfig) -> AxVmResult { let placements = config.phys_cpu_ls.get_vcpu_affinities_pcpu_ids(); let levels = guest_page_table_levels(&placements)?; let page_table = npt::NestedPageTable::new(levels)?; @@ -33,7 +33,7 @@ impl Aarch64Arch { }) } - pub(crate) fn init_vm(vm: &AxVM, request: VmInitRequest<'_>) -> AxResult { + pub(crate) fn init_vm(vm: &AxVM, request: VmInitRequest<'_>) -> AxVmResult { match request { VmInitRequest::Default => { let factories = default_device_factories()?; @@ -52,7 +52,7 @@ fn init_vm_with( vm: &AxVM, factories: &axdevice::DeviceFactoryRegistry, interrupt_fabric: crate::InterruptFabric, -) -> AxResult { +) -> AxVmResult { complete_vm_init(vm, interrupt_fabric, |resources, interrupt_fabric| { let placements = vcpu_placements(resources); let dtb_addr = resources @@ -82,7 +82,7 @@ fn init_vm_with( fn build_vcpu_setup_config( config: &AxVMConfig, _memory_regions: &[crate::vm::VMMemoryRegion], -) -> AxResult<::SetupConfig> { +) -> AxVmResult<::SetupConfig> { let passthrough = config.interrupt_mode() == VMInterruptMode::Passthrough; Ok(ArmVcpuSetupConfig { passthrough_interrupt: passthrough, @@ -94,7 +94,7 @@ fn register_arch_devices( vm: &AxVM, config: &AxVMConfig, devices: &mut axdevice::AxVmDevices, -) -> AxResult { +) -> AxVmResult { if config.interrupt_mode() == VMInterruptMode::Passthrough { assign_passthrough_spis(vm, config, devices); } else { @@ -118,7 +118,7 @@ fn assign_passthrough_spis(vm: &AxVM, config: &AxVMConfig, devices: &axdevice::A } } -fn register_virtual_timers(devices: &mut axdevice::AxVmDevices) -> AxResult { +fn register_virtual_timers(devices: &mut axdevice::AxVmDevices) -> AxVmResult { for device in axdevice::create_vtimer_devices() { devices .register(Arc::from(device) as Arc) @@ -129,7 +129,7 @@ fn register_virtual_timers(devices: &mut axdevice::AxVmDevices) -> AxResult { Ok(()) } -fn guest_page_table_levels(vcpu_mappings: &[(usize, Option, usize)]) -> AxResult { +fn guest_page_table_levels(vcpu_mappings: &[(usize, Option, usize)]) -> AxVmResult { let mut selected = usize::MAX; for cpu_id in crate::architecture::ops::target_phys_cpu_ids(vcpu_mappings) { let levels = crate::percpu::cpu_max_guest_page_table_levels(cpu_id) @@ -155,7 +155,7 @@ fn nested_paging_config( root_paddr: ax_memory_addr::PhysAddr, levels: usize, vcpu_mappings: &[(usize, Option, usize)], -) -> AxResult { +) -> AxVmResult { let mut pa_bits = usize::MAX; for cpu_id in crate::architecture::ops::target_phys_cpu_ids(vcpu_mappings) { let bits = diff --git a/virtualization/axvm/src/arch/loongarch64/boot/fdt/guest_firmware_dtb.rs b/virtualization/axvm/src/arch/loongarch64/boot/fdt/guest_firmware_dtb.rs index dc8ecab222..5c0f65e144 100644 --- a/virtualization/axvm/src/arch/loongarch64/boot/fdt/guest_firmware_dtb.rs +++ b/virtualization/axvm/src/arch/loongarch64/boot/fdt/guest_firmware_dtb.rs @@ -1,10 +1,9 @@ use alloc::{format, vec::Vec}; -use ax_errno::{AxResult, ax_err_type}; use fdt_edit::{Fdt, Node, NodeId}; use super::property::{prop_null, prop_string, prop_string_list, prop_u32, prop_u32_array}; -use crate::arch::guest_platform::GuestPlatform; +use crate::{AxVmResult, arch::guest_platform::GuestPlatform, ax_err_type}; const PHANDLE_CPU0: u32 = 0x8000; const PHANDLE_CPUIC: u32 = 0x8001; @@ -13,7 +12,7 @@ const PHANDLE_PCH_PIC: u32 = 0x8003; const PHANDLE_PCH_MSI: u32 = 0x8004; const PHANDLE_GED_SYSCON: u32 = 0x8005; -pub fn build(platform: &GuestPlatform) -> AxResult> { +pub fn build(platform: &GuestPlatform) -> AxVmResult> { let mut fdt = Fdt::new(); let root = fdt.root_id(); set_prop(&mut fdt, root, prop_u32("#address-cells", 2))?; @@ -38,7 +37,7 @@ pub fn build(platform: &GuestPlatform) -> AxResult> { Ok(fdt.encode().as_ref().to_vec()) } -fn set_prop(fdt: &mut Fdt, node: NodeId, prop: fdt_edit::Property) -> AxResult { +fn set_prop(fdt: &mut Fdt, node: NodeId, prop: fdt_edit::Property) -> AxVmResult { fdt.node_mut(node) .ok_or_else(|| ax_err_type!(InvalidData, "FDT node id is invalid"))? .set_property(prop); @@ -49,7 +48,7 @@ fn add_child(fdt: &mut Fdt, parent: NodeId, name: &str) -> NodeId { fdt.add_node(parent, Node::new(name)) } -fn add_platform_bus(fdt: &mut Fdt, root: NodeId, platform: &GuestPlatform) -> AxResult { +fn add_platform_bus(fdt: &mut Fdt, root: NodeId, platform: &GuestPlatform) -> AxVmResult { let (bus_base, bus_size) = platform_bus_range(platform); let platform_bus = add_child(fdt, root, &format!("platform-bus@{bus_base:x}")); set_prop( @@ -101,7 +100,7 @@ fn platform_bus_range(platform: &GuestPlatform) -> (u64, u64) { (base, end.saturating_sub(base).max(PAGE_SIZE)) } -fn add_chosen(fdt: &mut Fdt, root: NodeId, platform: &GuestPlatform) -> AxResult { +fn add_chosen(fdt: &mut Fdt, root: NodeId, platform: &GuestPlatform) -> AxVmResult { let chosen = add_child(fdt, root, "chosen"); set_prop( fdt, @@ -113,7 +112,7 @@ fn add_chosen(fdt: &mut Fdt, root: NodeId, platform: &GuestPlatform) -> AxResult ) } -fn add_cpus(fdt: &mut Fdt, root: NodeId) -> AxResult { +fn add_cpus(fdt: &mut Fdt, root: NodeId) -> AxVmResult { let cpus = add_child(fdt, root, "cpus"); set_prop(fdt, cpus, prop_u32("#address-cells", 1))?; set_prop(fdt, cpus, prop_u32("#size-cells", 0))?; @@ -134,7 +133,7 @@ fn add_cpus(fdt: &mut Fdt, root: NodeId) -> AxResult { set_prop(fdt, cpu, prop_u32("phandle", PHANDLE_CPU0)) } -fn add_memory(fdt: &mut Fdt, root: NodeId, platform: &GuestPlatform) -> AxResult { +fn add_memory(fdt: &mut Fdt, root: NodeId, platform: &GuestPlatform) -> AxVmResult { for region in &platform.ram_regions { let memory = add_child(fdt, root, &format!("memory@{:x}", region.base)); set_prop(fdt, memory, prop_string("device_type", "memory"))?; @@ -143,7 +142,7 @@ fn add_memory(fdt: &mut Fdt, root: NodeId, platform: &GuestPlatform) -> AxResult Ok(()) } -fn add_interrupt_controllers(fdt: &mut Fdt, root: NodeId, platform: &GuestPlatform) -> AxResult { +fn add_interrupt_controllers(fdt: &mut Fdt, root: NodeId, platform: &GuestPlatform) -> AxVmResult { let cpuic = add_child(fdt, root, "cpuic"); set_prop(fdt, cpuic, prop_null("interrupt-controller"))?; set_prop(fdt, cpuic, prop_u32("#interrupt-cells", 1))?; @@ -231,7 +230,7 @@ fn add_interrupt_controllers(fdt: &mut Fdt, root: NodeId, platform: &GuestPlatfo set_prop(fdt, msi, prop_u32("phandle", PHANDLE_PCH_MSI)) } -fn add_power(fdt: &mut Fdt, root: NodeId, platform: &GuestPlatform) -> AxResult { +fn add_power(fdt: &mut Fdt, root: NodeId, platform: &GuestPlatform) -> AxVmResult { let ged = platform.firmware_devices.ged; let ged_node = add_child(fdt, root, &format!("ged@{:x}", ged.mmio.base)); set_prop(fdt, ged_node, prop_string("compatible", "syscon"))?; @@ -253,7 +252,7 @@ fn add_power(fdt: &mut Fdt, root: NodeId, platform: &GuestPlatform) -> AxResult set_prop(fdt, reboot, prop_u32("value", ged.reboot_value)) } -fn add_rtc(fdt: &mut Fdt, root: NodeId, platform: &GuestPlatform) -> AxResult { +fn add_rtc(fdt: &mut Fdt, root: NodeId, platform: &GuestPlatform) -> AxVmResult { let rtc = platform.firmware_devices.rtc; let rtc_node = add_child(fdt, root, &format!("rtc@{:x}", rtc.mmio.base)); set_prop( @@ -266,7 +265,7 @@ fn add_rtc(fdt: &mut Fdt, root: NodeId, platform: &GuestPlatform) -> AxResult { set_prop(fdt, rtc_node, prop_u32_array("interrupts", &[rtc.irq, 4])) } -fn add_serial(fdt: &mut Fdt, root: NodeId, platform: &GuestPlatform) -> AxResult { +fn add_serial(fdt: &mut Fdt, root: NodeId, platform: &GuestPlatform) -> AxVmResult { let serial = add_child( fdt, root, @@ -292,7 +291,7 @@ fn add_serial(fdt: &mut Fdt, root: NodeId, platform: &GuestPlatform) -> AxResult ) } -fn add_flash(fdt: &mut Fdt, root: NodeId, platform: &GuestPlatform) -> AxResult { +fn add_flash(fdt: &mut Fdt, root: NodeId, platform: &GuestPlatform) -> AxVmResult { let flash = platform.firmware_devices.flash; let flash_node = add_child(fdt, root, &format!("flash@{:x}", flash.banks[0].base)); set_prop(fdt, flash_node, prop_string("compatible", "cfi-flash"))?; @@ -316,14 +315,14 @@ fn add_flash(fdt: &mut Fdt, root: NodeId, platform: &GuestPlatform) -> AxResult ) } -fn add_fw_cfg(fdt: &mut Fdt, root: NodeId, platform: &GuestPlatform) -> AxResult { +fn add_fw_cfg(fdt: &mut Fdt, root: NodeId, platform: &GuestPlatform) -> AxVmResult { let fw_cfg = add_child(fdt, root, &format!("fw_cfg@{:x}", platform.fw_cfg.base)); set_prop(fdt, fw_cfg, prop_string("compatible", "qemu,fw-cfg-mmio"))?; prop_reg(fdt, fw_cfg, platform.fw_cfg.base, platform.fw_cfg.size)?; set_prop(fdt, fw_cfg, prop_null("dma-coherent")) } -fn prop_reg(fdt: &mut Fdt, node: NodeId, base: u64, size: u64) -> AxResult { +fn prop_reg(fdt: &mut Fdt, node: NodeId, base: u64, size: u64) -> AxVmResult { set_prop( fdt, node, diff --git a/virtualization/axvm/src/arch/loongarch64/boot/mod.rs b/virtualization/axvm/src/arch/loongarch64/boot/mod.rs index 250234803e..b0fe5b8e87 100644 --- a/virtualization/axvm/src/arch/loongarch64/boot/mod.rs +++ b/virtualization/axvm/src/arch/loongarch64/boot/mod.rs @@ -4,7 +4,6 @@ mod resources; use alloc::{boxed::Box, format, vec::Vec}; -use ax_errno::{AxResult, ax_err_type}; use axdevice::{ FwCfgInterruptConfig, FwCfgPciConfig, FwCfgPlatformConfig, FwCfgRamRegion, FwCfgSerialConfig, }; @@ -15,8 +14,9 @@ pub use resources::{ }; use crate::{ - AxVMRef, GuestPhysAddr, + AxVMRef, AxVmResult, GuestPhysAddr, architecture::BootImagePlatform, + ax_err, ax_err_type, boot::{ BootImageProvider, StaticVmImage, images::{ImageLoaderCore, load_vm_image_from_memory}, @@ -43,7 +43,7 @@ impl<'a> ImageLoader<'a> { )) } - pub fn load(&mut self) -> AxResult { + pub fn load(&mut self) -> AxVmResult { self.0.load() } } @@ -174,7 +174,7 @@ impl GuestPlatform { } } -pub fn load_firmware_fdt(vm: &AxVMRef, config: &AxVMCrateConfig) -> AxResult { +pub fn load_firmware_fdt(vm: &AxVMRef, config: &AxVMCrateConfig) -> AxVmResult { let platform = GuestPlatform::discover(vm, config); let fdt = fdt::guest_firmware_dtb::build(&platform)?; debug!( @@ -209,7 +209,7 @@ pub fn guest_irq_routes(vm: &AxVMRef, config: &AxVMCrateConfig) -> Vec AxResult<&axvmconfig::EmulatedDeviceConfig> { +pub fn emulated_fw_cfg(config: &AxVMCrateConfig) -> AxVmResult<&axvmconfig::EmulatedDeviceConfig> { config .devices .emu_devices @@ -222,7 +222,7 @@ impl BootImagePlatform for super::LoongArch64Arch { fn load_images_from_memory( loader: &mut ImageLoaderCore<'_>, images: StaticVmImage, - ) -> AxResult { + ) -> AxVmResult { ensure_uefi_boot(loader)?; load_uefi_firmware_dtb(loader)?; add_uefi_fw_cfg(loader, images.kernel, images.ramdisk)?; @@ -239,7 +239,7 @@ impl BootImagePlatform for super::LoongArch64Arch { } #[cfg(any(feature = "fs", feature = "host-fs"))] - fn load_images_from_filesystem(loader: &mut ImageLoaderCore<'_>) -> AxResult { + fn load_images_from_filesystem(loader: &mut ImageLoaderCore<'_>) -> AxVmResult { ensure_uefi_boot(loader)?; load_uefi_firmware_dtb(loader)?; @@ -266,15 +266,15 @@ impl BootImagePlatform for super::LoongArch64Arch { } } -fn ensure_uefi_boot(loader: &ImageLoaderCore<'_>) -> AxResult { +fn ensure_uefi_boot(loader: &ImageLoaderCore<'_>) -> AxVmResult { if loader.config.kernel.effective_boot_protocol() == VMBootProtocol::Uefi { Ok(()) } else { - ax_errno::ax_err!(Unsupported, "LoongArch guests require UEFI boot") + ax_err!(Unsupported, "LoongArch guests require UEFI boot") } } -fn load_uefi_firmware_dtb(loader: &ImageLoaderCore<'_>) -> AxResult { +fn load_uefi_firmware_dtb(loader: &ImageLoaderCore<'_>) -> AxVmResult { prepare_uefi_runtime_config(&loader.vm, &loader.config); load_firmware_fdt(&loader.vm, &loader.config) } @@ -283,7 +283,7 @@ fn add_uefi_fw_cfg( loader: &ImageLoaderCore<'_>, kernel: &'static [u8], ramdisk: Option<&'static [u8]>, -) -> AxResult { +) -> AxVmResult { let fw_cfg = emulated_fw_cfg(&loader.config)?; loader.vm.add_fw_cfg_device(crate::FwCfgDeviceConfig { base: GuestPhysAddr::from(fw_cfg.base_gpa), @@ -305,7 +305,7 @@ fn provider_firmware_image(loader: &ImageLoaderCore<'_>) -> Option<&'static [u8] .and_then(|image| image.bios) } -fn load_uefi_firmware_image(loader: &ImageLoaderCore<'_>, firmware: &[u8]) -> AxResult { +fn load_uefi_firmware_image(loader: &ImageLoaderCore<'_>, firmware: &[u8]) -> AxVmResult { let load_gpa = loader .bios_load_gpa .ok_or_else(|| ax_err_type!(NotFound, "LoongArch UEFI firmware load addr is missed"))?; @@ -320,7 +320,7 @@ fn load_uefi_firmware_image(loader: &ImageLoaderCore<'_>, firmware: &[u8]) -> Ax load_vm_image_from_memory(firmware, load_gpa, loader.vm.clone()) } -fn fill_vm_region(load_addr: GuestPhysAddr, size: usize, byte: u8, vm: AxVMRef) -> AxResult { +fn fill_vm_region(load_addr: GuestPhysAddr, size: usize, byte: u8, vm: AxVMRef) -> AxVmResult { let regions = vm.get_image_load_region(load_addr, size)?; let mut filled_size = 0; for region in regions { @@ -333,7 +333,7 @@ fn fill_vm_region(load_addr: GuestPhysAddr, size: usize, byte: u8, vm: AxVMRef) if filled_size == size { Ok(()) } else { - ax_errno::ax_err!( + ax_err!( InvalidData, format!("VM memory was only partially filled: {filled_size}/{size} bytes") ) diff --git a/virtualization/axvm/src/arch/loongarch64/boot/probe.rs b/virtualization/axvm/src/arch/loongarch64/boot/probe.rs index c9cae82565..1cb4f1e97c 100644 --- a/virtualization/axvm/src/arch/loongarch64/boot/probe.rs +++ b/virtualization/axvm/src/arch/loongarch64/boot/probe.rs @@ -93,7 +93,9 @@ struct HostResources { irq_routes: Vec, } -fn host_acpi_resources(acpi: &ax_driver::probe::acpi::System) -> ax_errno::AxResult { +fn host_acpi_resources( + acpi: &ax_driver::probe::acpi::System, +) -> axvm_types::AxVmResult { let defaults = QemuVirtDefaults::new(); let interrupt = acpi .routing() diff --git a/virtualization/axvm/src/arch/loongarch64/boot/resources.rs b/virtualization/axvm/src/arch/loongarch64/boot/resources.rs index 796480c99d..510a96fda8 100644 --- a/virtualization/axvm/src/arch/loongarch64/boot/resources.rs +++ b/virtualization/axvm/src/arch/loongarch64/boot/resources.rs @@ -1,13 +1,12 @@ use alloc::{collections::BTreeMap, format, string::String, vec::Vec}; -use ax_errno::{AxResult, ax_err_type}; use ax_kspin::SpinNoIrq as Mutex; use ax_lazyinit::LazyInit; use axvmconfig::AxVMCrateConfig; use super::UEFI_FIRMWARE_FDT_BASE; use crate::{ - AxVMRef, + AxVMRef, AxVmResult, ax_err_type, config::{AxVMConfig, PassThroughDeviceConfig}, }; @@ -43,7 +42,7 @@ pub fn get_guest_irq_routes(vm_id: usize) -> Vec { pub fn prepare_uefi_fdt_config( vm_config: &mut AxVMConfig, vm_create_config: &mut AxVMCrateConfig, -) -> AxResult { +) -> AxVmResult { info!( "VM[{}] uses LoongArch UEFI boot protocol, keeping firmware FDT at {:#x}", vm_config.id(), @@ -62,7 +61,7 @@ pub fn prepare_uefi_runtime_config(vm: &AxVMRef, vm_create_config: &AxVMCrateCon fn expand_root_passthrough( vm_config: &mut AxVMConfig, vm_create_config: &AxVMCrateConfig, -) -> AxResult { +) -> AxVmResult { let has_root_passthrough = vm_config .pass_through_devices() .iter() @@ -140,7 +139,7 @@ struct AcpiPassthroughRange { fn acpi_passthrough_ranges( acpi: &ax_driver::probe::acpi::System, -) -> AxResult> { +) -> AxVmResult> { let mut ranges = Vec::new(); for device in acpi.resource_devices().map_err(|err| { @@ -224,7 +223,7 @@ fn add_acpi_passthrough_range( name: String, base: u64, size: u64, -) -> AxResult { +) -> AxVmResult { if size == 0 { return Ok(()); } diff --git a/virtualization/axvm/src/arch/loongarch64/capabilities.rs b/virtualization/axvm/src/arch/loongarch64/capabilities.rs index cba7b09970..3b622c92ee 100644 --- a/virtualization/axvm/src/arch/loongarch64/capabilities.rs +++ b/virtualization/axvm/src/arch/loongarch64/capabilities.rs @@ -12,9 +12,9 @@ impl GuestBootPlatform for LoongArch64Arch { vm_config: &mut crate::config::AxVMConfig, vm_create_config: &mut axvmconfig::AxVMCrateConfig, _provider: &dyn crate::boot::BootImageProvider, - ) -> ax_errno::AxResult> { + ) -> crate::AxVmResult> { if vm_create_config.kernel.effective_boot_protocol() != axvmconfig::VMBootProtocol::Uefi { - return ax_errno::ax_err!( + return crate::ax_err!( Unsupported, "LoongArch AxVisor guests currently require UEFI boot" ); diff --git a/virtualization/axvm/src/arch/loongarch64/fdt.rs b/virtualization/axvm/src/arch/loongarch64/fdt.rs index eca32a8fd2..431d211aeb 100644 --- a/virtualization/axvm/src/arch/loongarch64/fdt.rs +++ b/virtualization/axvm/src/arch/loongarch64/fdt.rs @@ -1,9 +1,8 @@ //! LoongArch compatibility facade for UEFI guest FDT preparation. -use ax_errno::AxResult; use axvmconfig::{AxVMCrateConfig, VMBootProtocol}; -use crate::config::AxVMConfig; +use crate::{AxVmResult, ax_err, config::AxVMConfig}; pub fn init_guest_boot_resources() { super::boot::init(); @@ -12,12 +11,12 @@ pub fn init_guest_boot_resources() { pub fn handle_fdt_operations( vm_config: &mut AxVMConfig, vm_create_config: &mut AxVMCrateConfig, -) -> AxResult { +) -> AxVmResult { if vm_create_config.kernel.effective_boot_protocol() == VMBootProtocol::Uefi { return super::boot::prepare_uefi_fdt_config(vm_config, vm_create_config); } - ax_errno::ax_err!( + ax_err!( Unsupported, "LoongArch AxVisor guests currently require UEFI boot" ) diff --git a/virtualization/axvm/src/arch/loongarch64/mod.rs b/virtualization/axvm/src/arch/loongarch64/mod.rs index b42a41f2c6..1c23f1ed19 100644 --- a/virtualization/axvm/src/arch/loongarch64/mod.rs +++ b/virtualization/axvm/src/arch/loongarch64/mod.rs @@ -1,11 +1,10 @@ use alloc::boxed::Box; use core::time::Duration; -use ax_errno::{AxError, AxResult}; use ax_memory_addr::VirtAddr; use axvm_types::{ - AccessWidth, GuestPhysAddr, MappingFlags, NestedPagingConfig, VCpuId, VMId, VmArchPerCpuOps, - VmArchVcpuOps, + AccessWidth, AxVmError as BackendError, AxVmResult as BackendResult, GuestPhysAddr, + MappingFlags, NestedPagingConfig, VCpuId, VMId, VmArchPerCpuOps, VmArchVcpuOps, }; use loongarch_vcpu::{ LoongArchAccessFlags, LoongArchAccessWidth, LoongArchGuestPhysAddr, LoongArchHostOps, @@ -15,7 +14,10 @@ use loongarch_vcpu::{ }; use super::{ArchOps, BoundVcpuExit, HypercallExit, MmioReadExit, MmioWriteExit, VcpuRunAction}; -use crate::host::{HostMemory, HostTime, default_host}; +use crate::{ + AxVmError, AxVmResult, + host::{HostMemory, HostTime, default_host}, +}; pub(crate) mod boot; mod capabilities; @@ -111,7 +113,7 @@ impl ArchOps for LoongArch64Arch { vm: &crate::AxVMRef, vcpu: &crate::vm::AxVCpuRef, exit: ::Exit, - ) -> AxResult> { + ) -> AxVmResult> { match exit { LoongArchVmExit::Hypercall { nr, args } => { super::handle_hypercall(vm, vcpu, HypercallExit { nr, args }) @@ -167,7 +169,10 @@ impl ArchOps for LoongArch64Arch { waits_for_event: false, stop_reason: None, })), - _ => Err(AxError::Unsupported), + _ => Err(AxVmError::unsupported( + "handle LoongArch VM exit", + "unsupported VM exit reason", + )), } } @@ -175,7 +180,7 @@ impl ArchOps for LoongArch64Arch { vm: &crate::AxVMRef, vcpu: &crate::vm::AxVCpuRef, work: Self::DeferredRunWork, - ) -> AxResult { + ) -> AxVmResult { match work { LoongArchDeferredRunWork::ExternalInterrupt { vector } => { Self::after_external_interrupt(vm, vcpu, vector); @@ -201,7 +206,7 @@ fn handle_loongarch_nested_page_fault( vcpu: &crate::vm::AxVCpuRef, addr: LoongArchGuestPhysAddr, access_flags: LoongArchAccessFlags, -) -> AxResult> { +) -> AxVmResult> { let ax_addr = loong_guest_phys_addr_to_ax(addr); if vm.get_devices()?.find_mmio_dev(ax_addr).is_some() { let Some(decoded) = vcpu.get_arch_vcpu().decode_mmio_fault(addr, access_flags) else { @@ -316,8 +321,9 @@ impl LoongArchHostOps for AxvmLoongArchHostOps { pub(crate) struct AxvmLoongArchVcpu(LoongArchVcpu); impl AxvmLoongArchVcpu { - fn inject_external_interrupt(&mut self, vector: usize, physical_irq: usize) -> AxResult { + fn inject_external_interrupt(&mut self, vector: usize, physical_irq: usize) -> AxVmResult { loongarch_result(self.0.inject_external_interrupt(vector, physical_irq)) + .map_err(|error| AxVmError::interrupt("inject LoongArch external interrupt", error)) } fn has_enabled_pending_interrupt(&self) -> bool { @@ -342,34 +348,34 @@ impl VmArchVcpuOps for AxvmLoongArchVcpu { type SetupConfig = LoongArchVCpuSetupConfig; type Exit = LoongArchVmExit; - fn new(vm_id: VMId, vcpu_id: VCpuId, config: Self::CreateConfig) -> AxResult { + fn new(vm_id: VMId, vcpu_id: VCpuId, config: Self::CreateConfig) -> BackendResult { loongarch_result(LoongArchVcpu::new(vm_id, vcpu_id, config)).map(Self) } - fn set_entry(&mut self, entry: GuestPhysAddr) -> AxResult { + fn set_entry(&mut self, entry: GuestPhysAddr) -> BackendResult { loongarch_result(self.0.set_entry(ax_guest_phys_addr_to_loong(entry))) } - fn set_nested_page_table(&mut self, config: NestedPagingConfig) -> AxResult { + fn set_nested_page_table(&mut self, config: NestedPagingConfig) -> BackendResult { loongarch_result( self.0 .set_nested_page_table(ax_nested_paging_to_loong(config)), ) } - fn setup(&mut self, config: Self::SetupConfig) -> AxResult { + fn setup(&mut self, config: Self::SetupConfig) -> BackendResult { loongarch_result(self.0.setup(config)) } - fn run(&mut self) -> AxResult { + fn run(&mut self) -> BackendResult { loongarch_result(self.0.run()) } - fn bind(&mut self) -> AxResult { + fn bind(&mut self) -> BackendResult { loongarch_result(self.0.bind()) } - fn unbind(&mut self) -> AxResult { + fn unbind(&mut self) -> BackendResult { loongarch_result(self.0.unbind()) } @@ -377,7 +383,7 @@ impl VmArchVcpuOps for AxvmLoongArchVcpu { self.0.set_gpr(reg, val); } - fn inject_interrupt(&mut self, vector: usize) -> AxResult { + fn inject_interrupt(&mut self, vector: usize) -> BackendResult { loongarch_result(self.0.inject_interrupt(vector)) } @@ -389,7 +395,7 @@ impl VmArchVcpuOps for AxvmLoongArchVcpu { pub(crate) struct AxvmLoongArchPerCpu(LoongArchPerCpu); impl VmArchPerCpuOps for AxvmLoongArchPerCpu { - fn new(cpu_id: usize) -> AxResult { + fn new(cpu_id: usize) -> BackendResult { loongarch_result(LoongArchPerCpu::new(cpu_id)).map(Self) } @@ -397,11 +403,11 @@ impl VmArchPerCpuOps for AxvmLoongArchPerCpu { self.0.is_enabled() } - fn hardware_enable(&mut self) -> AxResult { + fn hardware_enable(&mut self) -> BackendResult { loongarch_result(self.0.hardware_enable()) } - fn hardware_disable(&mut self) -> AxResult { + fn hardware_disable(&mut self) -> BackendResult { loongarch_result(self.0.hardware_disable()) } @@ -410,15 +416,15 @@ impl VmArchPerCpuOps for AxvmLoongArchPerCpu { } } -fn loongarch_result(result: LoongArchVcpuResult) -> AxResult { +fn loongarch_result(result: LoongArchVcpuResult) -> BackendResult { result.map_err(loongarch_error_to_ax) } -fn loongarch_error_to_ax(err: LoongArchVcpuError) -> AxError { +fn loongarch_error_to_ax(err: LoongArchVcpuError) -> BackendError { match err { - LoongArchVcpuError::InvalidInput => AxError::InvalidInput, - LoongArchVcpuError::Unsupported => AxError::Unsupported, - LoongArchVcpuError::BadState => AxError::BadState, + LoongArchVcpuError::InvalidInput => BackendError::InvalidInput, + LoongArchVcpuError::Unsupported => BackendError::Unsupported, + LoongArchVcpuError::BadState => BackendError::BadState, } } @@ -506,15 +512,15 @@ mod tests { fn converts_loongarch_vcpu_errors_to_ax_errors() { assert_eq!( loongarch_error_to_ax(LoongArchVcpuError::InvalidInput), - AxError::InvalidInput + BackendError::InvalidInput ); assert_eq!( loongarch_error_to_ax(LoongArchVcpuError::Unsupported), - AxError::Unsupported + BackendError::Unsupported ); assert_eq!( loongarch_error_to_ax(LoongArchVcpuError::BadState), - AxError::BadState + BackendError::BadState ); } diff --git a/virtualization/axvm/src/arch/loongarch64/vm.rs b/virtualization/axvm/src/arch/loongarch64/vm.rs index 0c78c57974..e322c3fb44 100644 --- a/virtualization/axvm/src/arch/loongarch64/vm.rs +++ b/virtualization/axvm/src/arch/loongarch64/vm.rs @@ -1,11 +1,11 @@ //! LoongArch64 VM resource creation and initialization. -use ax_errno::AxResult; use axvm_types::{NestedPagingConfig, VmArchVcpuOps}; use loongarch_vcpu::{LoongArchVCpuCreateConfig, LoongArchVCpuSetupConfig}; use super::{LoongArch64Arch, loongarch_result, npt}; use crate::{ + AxVmError, AxVmResult, ax_err, config::AxVMConfig, vm::{ AxVM, AxVMResources, @@ -21,7 +21,7 @@ use crate::{ }; impl LoongArch64Arch { - pub(crate) fn create_vm_resources(config: AxVMConfig) -> AxResult { + pub(crate) fn create_vm_resources(config: AxVMConfig) -> AxVmResult { let placements = config.phys_cpu_ls.get_vcpu_affinities_pcpu_ids(); let levels = guest_page_table_levels(&placements); let page_table = npt::NestedPageTable::new(levels)?; @@ -30,7 +30,7 @@ impl LoongArch64Arch { 3 => 39, 4 => 48, _ => { - return ax_errno::ax_err!( + return ax_err!( InvalidInput, "unsupported LoongArch nested page-table levels" ); @@ -40,7 +40,7 @@ impl LoongArch64Arch { }) } - pub(crate) fn init_vm(vm: &AxVM, request: VmInitRequest<'_>) -> AxResult { + pub(crate) fn init_vm(vm: &AxVM, request: VmInitRequest<'_>) -> AxVmResult { match request { VmInitRequest::Default => { let factories = default_device_factories()?; @@ -59,7 +59,7 @@ fn init_vm_with( vm: &AxVM, factories: &axdevice::DeviceFactoryRegistry, interrupt_fabric: crate::InterruptFabric, -) -> AxResult { +) -> AxVmResult { complete_vm_init(vm, interrupt_fabric, |resources, interrupt_fabric| { let placements = vcpu_placements(resources); let state_count = placements @@ -67,7 +67,9 @@ fn init_vm_with( .map(|placement| placement.id) .max() .map_or(0, |vcpu_id| vcpu_id + 1); - let iocsr_state = loongarch_result(loongarch_vcpu::LoongArchIocsrState::new(state_count))?; + let iocsr_state = + loongarch_result(loongarch_vcpu::LoongArchIocsrState::new(state_count)) + .map_err(|error| AxVmError::vcpu("create LoongArch IOCSR state", error))?; let dtb_addr = resources .config() .image_config() @@ -99,7 +101,7 @@ fn init_vm_with( fn build_vcpu_setup_config( config: &AxVMConfig, _memory_regions: &[crate::vm::VMMemoryRegion], -) -> AxResult<::SetupConfig> { +) -> AxVmResult<::SetupConfig> { let passthrough = config.interrupt_mode() == axvm_types::VMInterruptMode::Passthrough; Ok(LoongArchVCpuSetupConfig { passthrough_interrupt: passthrough, diff --git a/virtualization/axvm/src/arch/mod.rs b/virtualization/axvm/src/arch/mod.rs index c1db6f0739..1cd6439475 100644 --- a/virtualization/axvm/src/arch/mod.rs +++ b/virtualization/axvm/src/arch/mod.rs @@ -1,9 +1,10 @@ //! Target architecture selection and stable internal dispatch. -use ax_errno::AxResult; - pub(crate) use crate::architecture::*; -use crate::architecture::{BootImagePlatform, GuestBootPlatform, HostTimePlatform}; +use crate::{ + AxVmResult, + architecture::{BootImagePlatform, GuestBootPlatform, HostTimePlatform}, +}; #[cfg(target_arch = "aarch64")] mod aarch64; @@ -92,21 +93,21 @@ pub(crate) fn prepare_guest_boot( vm_config: &mut crate::config::AxVMConfig, vm_create_config: &mut axvmconfig::AxVMCrateConfig, provider: &dyn crate::boot::BootImageProvider, -) -> AxResult> { +) -> AxVmResult> { CurrentArch::prepare_guest_boot(vm_config, vm_create_config, provider) } pub(crate) fn load_images_from_memory( loader: &mut crate::boot::images::ImageLoaderCore<'_>, images: crate::boot::StaticVmImage, -) -> AxResult { +) -> AxVmResult { CurrentArch::load_images_from_memory(loader, images) } #[cfg(any(feature = "fs", feature = "host-fs"))] pub(crate) fn load_images_from_filesystem( loader: &mut crate::boot::images::ImageLoaderCore<'_>, -) -> AxResult { +) -> AxVmResult { CurrentArch::load_images_from_filesystem(loader) } diff --git a/virtualization/axvm/src/arch/riscv64/capabilities.rs b/virtualization/axvm/src/arch/riscv64/capabilities.rs index 70f2a1700e..bc0c63d5c5 100644 --- a/virtualization/axvm/src/arch/riscv64/capabilities.rs +++ b/virtualization/axvm/src/arch/riscv64/capabilities.rs @@ -2,10 +2,12 @@ use alloc::{format, vec::Vec}; -use ax_errno::{AxResult, ax_err_type}; - use super::Riscv64Arch; -use crate::architecture::{BootImagePlatform, GuestBootPlatform, HostTimePlatform}; +use crate::{ + AxVmResult, + architecture::{BootImagePlatform, GuestBootPlatform, HostTimePlatform}, + ax_err_type, +}; impl HostTimePlatform for Riscv64Arch {} @@ -13,7 +15,7 @@ impl BootImagePlatform for Riscv64Arch { fn load_guest_dtb( loader: &crate::boot::images::ImageLoaderCore<'_>, dtb: &crate::boot::fdt::GuestDtbImage, - ) -> AxResult { + ) -> AxVmResult { let bytes = dtb.as_bytes(); let source = core::ptr::NonNull::new(bytes.as_ptr() as *mut u8) .ok_or_else(|| ax_err_type!(InvalidData, "Guest DTB pointer is null"))?; @@ -26,7 +28,7 @@ impl GuestBootPlatform for Riscv64Arch { vm_config: &mut crate::config::AxVMConfig, vm_create_config: &mut axvmconfig::AxVMCrateConfig, provider: &dyn crate::boot::BootImageProvider, - ) -> AxResult> { + ) -> AxVmResult> { super::fdt::core::prepare_dtb_guest(vm_config, vm_create_config, provider) } } @@ -47,7 +49,7 @@ pub(super) fn patch_runtime_fdt( fdt_bytes: &[u8], vm: &crate::AxVMRef, crate_config: &axvmconfig::AxVMCrateConfig, -) -> AxResult> { +) -> AxVmResult> { let host_fdt = super::fdt::core::try_get_host_fdt() .map(fdt_edit::Fdt::from_bytes) .transpose() @@ -71,7 +73,7 @@ pub(super) fn patch_provided_fdt( provided_dtb: &[u8], _host_dtb: Option<&[u8]>, _crate_config: &axvmconfig::AxVMCrateConfig, -) -> AxResult> { +) -> AxVmResult> { Ok(provided_dtb.to_vec()) } diff --git a/virtualization/axvm/src/arch/riscv64/fdt.rs b/virtualization/axvm/src/arch/riscv64/fdt.rs index 8aa7a815b9..a43119dcbf 100644 --- a/virtualization/axvm/src/arch/riscv64/fdt.rs +++ b/virtualization/axvm/src/arch/riscv64/fdt.rs @@ -2,9 +2,8 @@ use alloc::vec::Vec; -use ax_errno::AxResult; - use crate::{ + AxVmResult, boot::{BootImageProvider, fdt::GuestDtbImage}, config::AxVMConfig, }; @@ -37,7 +36,7 @@ pub(crate) fn host_phys_to_virt(paddr: ax_memory_addr::PhysAddr) -> ax_memory_ad pub(super) fn ensure_chosen_from_host( guest_dtb: Vec, host_fdt: Option<&fdt_edit::Fdt>, -) -> AxResult> { +) -> AxVmResult> { let Some(host_fdt) = host_fdt else { return Ok(guest_dtb); }; @@ -56,6 +55,6 @@ pub fn handle_fdt_operations( vm_config: &mut AxVMConfig, vm_create_config: &mut axvmconfig::AxVMCrateConfig, provider: &dyn BootImageProvider, -) -> AxResult> { +) -> AxVmResult> { core::prepare_dtb_guest(vm_config, vm_create_config, provider) } diff --git a/virtualization/axvm/src/arch/riscv64/images.rs b/virtualization/axvm/src/arch/riscv64/images.rs index 5a22968ad6..1dca9e626a 100644 --- a/virtualization/axvm/src/arch/riscv64/images.rs +++ b/virtualization/axvm/src/arch/riscv64/images.rs @@ -1,10 +1,9 @@ //! Public RISC-V image-loader facade preserving the DTB constructor contract. -use ax_errno::AxResult; use axvmconfig::AxVMCrateConfig; use crate::{ - AxVMRef, VMMemoryRegion, + AxVMRef, AxVmResult, VMMemoryRegion, boot::{BootImageProvider, fdt::GuestDtbImage, images::ImageLoaderCore}, }; @@ -27,7 +26,7 @@ impl<'a> ImageLoader<'a> { )) } - pub fn load(&mut self) -> AxResult { + pub fn load(&mut self) -> AxVmResult { self.0.load() } } diff --git a/virtualization/axvm/src/arch/riscv64/irq.rs b/virtualization/axvm/src/arch/riscv64/irq.rs index af994514dc..c0a4df36d2 100644 --- a/virtualization/axvm/src/arch/riscv64/irq.rs +++ b/virtualization/axvm/src/arch/riscv64/irq.rs @@ -16,25 +16,26 @@ use alloc::sync::Arc; -use ax_errno::{AxResult, ax_err}; use axdevice::{ DeviceBuildContext, DeviceBundle, DeviceFactory, DeviceFactoryRegistry, DeviceRegistration, MmioDeviceAdapter, }; -use axdevice_base::{IrqLineId, IrqSink}; -use axvm_types::{EmulatedDeviceConfig, EmulatedDeviceType, VMInterruptMode}; +use axdevice_base::{AxResult as DeviceResult, IrqLineId, IrqSink}; +use axvm_types::{ + AxVmError as BackendError, EmulatedDeviceConfig, EmulatedDeviceType, VMInterruptMode, +}; use riscv_vplic::{ PLIC_CONTEXT_CLAIM_COMPLETE_OFFSET, PLIC_CONTEXT_CTRL_OFFSET, PLIC_CONTEXT_STRIDE, VPlicGlobal, }; -use crate::irq::InterruptFabric; +use crate::{AxVmError, AxVmResult, ax_err, ax_err_type, irq::InterruptFabric}; struct RiscvPlicIrqSink { vplic: Arc, } impl IrqSink for RiscvPlicIrqSink { - fn set_level(&self, line: IrqLineId, asserted: bool) -> AxResult { + fn set_level(&self, line: IrqLineId, asserted: bool) -> DeviceResult { if asserted { self.vplic.set_pending(line.0) } else { @@ -42,7 +43,7 @@ impl IrqSink for RiscvPlicIrqSink { } } - fn pulse(&self, line: IrqLineId) -> AxResult { + fn pulse(&self, line: IrqLineId) -> DeviceResult { self.vplic.set_pending(line.0) } } @@ -63,24 +64,18 @@ impl DeviceFactory for RiscvPlicFactory { &self, config: &EmulatedDeviceConfig, _context: &DeviceBuildContext<'_>, - ) -> AxResult { + ) -> DeviceResult { if config.base_gpa != self.base_gpa || config.length != self.length || config.cfg_list.as_slice() != [self.contexts_num] { - return ax_err!( - InvalidInput, - format_args!( - "virtual PLIC configuration changed while building device '{}'", - config.name - ) - ); + return Err(BackendError::InvalidInput); } Ok(DeviceRegistration::Device(MmioDeviceAdapter::from_arc(self.vplic.clone())).into()) } } -fn validate_vplic_config(config: &EmulatedDeviceConfig) -> AxResult { +fn validate_vplic_config(config: &EmulatedDeviceConfig) -> AxVmResult { let [contexts_num] = config.cfg_list.as_slice() else { return ax_err!( InvalidInput, @@ -95,11 +90,11 @@ fn validate_vplic_config(config: &EmulatedDeviceConfig) -> AxResult { .and_then(|offset| offset.checked_add(PLIC_CONTEXT_CTRL_OFFSET)) .and_then(|offset| offset.checked_add(PLIC_CONTEXT_CLAIM_COMPLETE_OFFSET)) .and_then(|offset| config.base_gpa.checked_add(offset)) - .ok_or(ax_errno::AxError::InvalidInput)?; + .ok_or_else(|| ax_err_type!(InvalidInput, "virtual PLIC context range overflow"))?; let region_end = config .base_gpa .checked_add(config.length) - .ok_or(ax_errno::AxError::InvalidInput)?; + .ok_or_else(|| ax_err_type!(InvalidInput, "virtual PLIC region range overflow"))?; if region_end <= context_end { return ax_err!( InvalidInput, @@ -116,7 +111,7 @@ pub(crate) fn configure( factories: &mut DeviceFactoryRegistry, mode: VMInterruptMode, configs: &[EmulatedDeviceConfig], -) -> AxResult { +) -> AxVmResult { let mut vplic_configs = configs .iter() .filter(|config| config.emu_type == EmulatedDeviceType::PPPTGlobal); @@ -136,12 +131,14 @@ pub(crate) fn configure( Some(config.length), contexts_num, )); - factories.register(Arc::new(RiscvPlicFactory { - base_gpa: config.base_gpa, - length: config.length, - contexts_num, - vplic: vplic.clone(), - }))?; + factories + .register(Arc::new(RiscvPlicFactory { + base_gpa: config.base_gpa, + length: config.length, + contexts_num, + vplic: vplic.clone(), + })) + .map_err(|error| AxVmError::device("register virtual PLIC factory", error))?; InterruptFabric::with_sink(mode, Arc::new(RiscvPlicIrqSink { vplic })) } diff --git a/virtualization/axvm/src/arch/riscv64/mod.rs b/virtualization/axvm/src/arch/riscv64/mod.rs index 76ab6c6b0a..b6cb0f81b3 100644 --- a/virtualization/axvm/src/arch/riscv64/mod.rs +++ b/virtualization/axvm/src/arch/riscv64/mod.rs @@ -1,11 +1,11 @@ use alloc::vec::Vec; use ax_crate_interface::impl_interface; -use ax_errno::{AxError, AxResult}; use ax_memory_addr::{PhysAddr, VirtAddr}; use axvm_types::{ - AccessWidth, GuestPhysAddr, MappingFlags, NestedPagingConfig, VCpuId, VMId, VMInterruptMode, - VmArchPerCpuOps, VmArchVcpuOps, + AccessWidth, AxVmError as BackendError, AxVmResult as BackendResult, GuestPhysAddr, + MappingFlags, NestedPagingConfig, VCpuId, VMId, VMInterruptMode, VmArchPerCpuOps, + VmArchVcpuOps, }; use riscv_vcpu::{ GprIndex as RiscvGprIndex, RiscvAccessFlags, RiscvAccessWidth, RiscvGuestPhysAddr, @@ -16,7 +16,7 @@ use riscv_vplic::host::RiscvVplicHostIf; use super::{ArchOps, BoundVcpuExit, HypercallExit, MmioReadExit, MmioWriteExit, VcpuRunAction}; use crate::{ - StopReason, + AxVmResult, StopReason, architecture::ops::default_vcpu_affinities, host::{HostMemory, default_host}, }; @@ -112,7 +112,7 @@ impl ArchOps for Riscv64Arch { vm: &crate::AxVMRef, vcpu: &crate::vm::AxVCpuRef, exit: ::Exit, - ) -> AxResult> { + ) -> AxVmResult> { match exit { RiscvVmExit::Hypercall { nr, args } => { super::handle_hypercall(vm, vcpu, HypercallExit { nr, args }) @@ -202,7 +202,7 @@ impl ArchOps for Riscv64Arch { vm: &crate::AxVMRef, vcpu: &crate::vm::AxVCpuRef, work: Self::DeferredRunWork, - ) -> AxResult { + ) -> AxVmResult { match work { RiscvDeferredRunWork::ExternalInterrupt { vector } => { Self::after_external_interrupt(vm, vcpu, vector); @@ -220,7 +220,7 @@ fn handle_riscv_nested_page_fault( vcpu: &crate::vm::AxVCpuRef, addr: RiscvGuestPhysAddr, access_flags: RiscvAccessFlags, -) -> AxResult> { +) -> AxVmResult> { let ax_addr = riscv_guest_phys_addr_to_ax(addr); if vm.get_devices()?.find_mmio_dev(ax_addr).is_some() { let Some(decoded) = vcpu.get_arch_vcpu().decode_mmio_fault(addr, access_flags) else { @@ -289,34 +289,34 @@ impl VmArchVcpuOps for AxvmRiscvVcpu { type SetupConfig = (); type Exit = RiscvVmExit; - fn new(vm_id: VMId, vcpu_id: VCpuId, config: Self::CreateConfig) -> AxResult { + fn new(vm_id: VMId, vcpu_id: VCpuId, config: Self::CreateConfig) -> BackendResult { riscv_result(RiscvVCpu::new(vm_id, vcpu_id, config)).map(Self) } - fn set_entry(&mut self, entry: GuestPhysAddr) -> AxResult { + fn set_entry(&mut self, entry: GuestPhysAddr) -> BackendResult { riscv_result(self.0.set_entry(ax_guest_phys_addr_to_riscv(entry))) } - fn set_nested_page_table(&mut self, config: NestedPagingConfig) -> AxResult { + fn set_nested_page_table(&mut self, config: NestedPagingConfig) -> BackendResult { riscv_result( self.0 .set_nested_page_table(ax_nested_paging_to_riscv(config)), ) } - fn setup(&mut self, config: Self::SetupConfig) -> AxResult { + fn setup(&mut self, config: Self::SetupConfig) -> BackendResult { riscv_result(self.0.setup(config)) } - fn run(&mut self) -> AxResult { + fn run(&mut self) -> BackendResult { riscv_result(self.0.run()) } - fn bind(&mut self) -> AxResult { + fn bind(&mut self) -> BackendResult { riscv_result(self.0.bind()) } - fn unbind(&mut self) -> AxResult { + fn unbind(&mut self) -> BackendResult { riscv_result(self.0.unbind()) } @@ -324,7 +324,7 @@ impl VmArchVcpuOps for AxvmRiscvVcpu { self.0.set_gpr(reg, val); } - fn inject_interrupt(&mut self, vector: usize) -> AxResult { + fn inject_interrupt(&mut self, vector: usize) -> BackendResult { riscv_result(self.0.inject_interrupt(vector)) } @@ -336,7 +336,7 @@ impl VmArchVcpuOps for AxvmRiscvVcpu { pub(crate) struct AxvmRiscvPerCpu(RiscvPerCpu); impl VmArchPerCpuOps for AxvmRiscvPerCpu { - fn new(cpu_id: usize) -> AxResult { + fn new(cpu_id: usize) -> BackendResult { riscv_result(RiscvPerCpu::new(cpu_id)).map(Self) } @@ -344,11 +344,11 @@ impl VmArchPerCpuOps for AxvmRiscvPerCpu { self.0.is_enabled() } - fn hardware_enable(&mut self) -> AxResult { + fn hardware_enable(&mut self) -> BackendResult { riscv_result(self.0.hardware_enable()) } - fn hardware_disable(&mut self) -> AxResult { + fn hardware_disable(&mut self) -> BackendResult { riscv_result(self.0.hardware_disable()) } @@ -361,18 +361,18 @@ impl VmArchPerCpuOps for AxvmRiscvPerCpu { } } -fn riscv_result(result: RiscvVcpuResult) -> AxResult { +fn riscv_result(result: RiscvVcpuResult) -> BackendResult { result.map_err(riscv_error_to_ax) } -fn riscv_error_to_ax(err: RiscvVcpuError) -> AxError { +fn riscv_error_to_ax(err: RiscvVcpuError) -> BackendError { match err { - RiscvVcpuError::InvalidInput => AxError::InvalidInput, - RiscvVcpuError::Unsupported => AxError::Unsupported, - RiscvVcpuError::BadState => AxError::BadState, + RiscvVcpuError::InvalidInput => BackendError::InvalidInput, + RiscvVcpuError::Unsupported => BackendError::Unsupported, + RiscvVcpuError::BadState => BackendError::BadState, RiscvVcpuError::InvalidTrap | RiscvVcpuError::DecodeFailed - | RiscvVcpuError::GuestMemoryFault => AxError::InvalidData, + | RiscvVcpuError::GuestMemoryFault => BackendError::InvalidData, } } @@ -479,19 +479,19 @@ mod tests { fn converts_riscv_vcpu_errors_to_ax_errors() { assert_eq!( riscv_error_to_ax(RiscvVcpuError::InvalidInput), - AxError::InvalidInput + BackendError::InvalidInput ); assert_eq!( riscv_error_to_ax(RiscvVcpuError::Unsupported), - AxError::Unsupported + BackendError::Unsupported ); assert_eq!( riscv_error_to_ax(RiscvVcpuError::BadState), - AxError::BadState + BackendError::BadState ); assert_eq!( riscv_error_to_ax(RiscvVcpuError::DecodeFailed), - AxError::InvalidData + BackendError::InvalidData ); } diff --git a/virtualization/axvm/src/arch/riscv64/vm.rs b/virtualization/axvm/src/arch/riscv64/vm.rs index c525ff12db..a78558973d 100644 --- a/virtualization/axvm/src/arch/riscv64/vm.rs +++ b/virtualization/axvm/src/arch/riscv64/vm.rs @@ -1,11 +1,11 @@ //! RISC-V VM resource creation and initialization. -use ax_errno::{AxResult, ax_err}; use axvm_types::{NestedPagingConfig, VmArchVcpuOps}; use riscv_vcpu::RiscvVcpuCreateConfig; use super::{Riscv64Arch, irq, npt}; use crate::{ + AxVmResult, ax_err, config::AxVMConfig, vm::{ AxVM, AxVMResources, @@ -21,7 +21,7 @@ use crate::{ }; impl Riscv64Arch { - pub(crate) fn create_vm_resources(config: AxVMConfig) -> AxResult { + pub(crate) fn create_vm_resources(config: AxVMConfig) -> AxVmResult { let placements = config.phys_cpu_ls.get_vcpu_affinities_pcpu_ids(); let levels = guest_page_table_levels(&placements)?; let page_table = npt::NestedPageTable::new(levels)?; @@ -30,7 +30,7 @@ impl Riscv64Arch { }) } - pub(crate) fn init_vm(vm: &AxVM, request: VmInitRequest<'_>) -> AxResult { + pub(crate) fn init_vm(vm: &AxVM, request: VmInitRequest<'_>) -> AxVmResult { match request { VmInitRequest::Default => { let mut factories = default_device_factories()?; @@ -51,7 +51,7 @@ fn init_vm_with( vm: &AxVM, factories: &axdevice::DeviceFactoryRegistry, interrupt_fabric: crate::InterruptFabric, -) -> AxResult { +) -> AxVmResult { complete_vm_init(vm, interrupt_fabric, |resources, interrupt_fabric| { let placements = vcpu_placements(resources); let dtb_addr = resources @@ -80,11 +80,11 @@ fn init_vm_with( fn build_vcpu_setup_config( _config: &AxVMConfig, _memory_regions: &[crate::vm::VMMemoryRegion], -) -> AxResult<::SetupConfig> { +) -> AxVmResult<::SetupConfig> { Ok(()) } -fn guest_page_table_levels(vcpu_mappings: &[(usize, Option, usize)]) -> AxResult { +fn guest_page_table_levels(vcpu_mappings: &[(usize, Option, usize)]) -> AxVmResult { let mut levels = riscv_vcpu::max_guest_page_table_levels(); for cpu_id in crate::architecture::ops::target_phys_cpu_ids(vcpu_mappings) { levels = levels.min( @@ -101,7 +101,7 @@ fn guest_page_table_levels(vcpu_mappings: &[(usize, Option, usize)]) -> A fn nested_paging_config( root_paddr: ax_memory_addr::PhysAddr, levels: usize, -) -> AxResult { +) -> AxVmResult { match levels { 3 => Ok(NestedPagingConfig::new(root_paddr, 3, 41, 8)), 4 => Ok(NestedPagingConfig::new(root_paddr, 4, 50, 9)), diff --git a/virtualization/axvm/src/arch/x86_64/boot/mod.rs b/virtualization/axvm/src/arch/x86_64/boot/mod.rs index 45d6933ac5..916b541abf 100644 --- a/virtualization/axvm/src/arch/x86_64/boot/mod.rs +++ b/virtualization/axvm/src/arch/x86_64/boot/mod.rs @@ -2,13 +2,16 @@ use alloc::format; -use ax_errno::{AxResult, ax_err_type}; use axvm_types::GuestPhysAddr; use axvmconfig::{EmulatedDeviceType, VMBootProtocol, VmMemMappingType}; use super::X86_64Arch; +#[cfg(not(any(feature = "fs", feature = "host-fs")))] +use crate::ax_err; use crate::{ + AxVmError, AxVmResult, architecture::BootImagePlatform, + ax_err_type, boot::{ BootImageProvider, StaticVmImage, images::{ImageLoaderCore, load_vm_image_from_memory}, @@ -39,7 +42,7 @@ impl<'a> ImageLoader<'a> { )) } - pub fn load(&mut self) -> AxResult { + pub fn load(&mut self) -> AxVmResult { self.0.load() } } @@ -58,7 +61,7 @@ impl BootImagePlatform for X86_64Arch { fn load_images_from_memory( loader: &mut ImageLoaderCore<'_>, images: StaticVmImage, - ) -> AxResult { + ) -> AxVmResult { if should_direct_boot_linux(&loader.config) && let Some(header) = detect_linux_image(images.kernel) { @@ -73,7 +76,7 @@ impl BootImagePlatform for X86_64Arch { } #[cfg(any(feature = "fs", feature = "host-fs"))] - fn load_images_from_filesystem(loader: &mut ImageLoaderCore<'_>) -> AxResult { + fn load_images_from_filesystem(loader: &mut ImageLoaderCore<'_>) -> AxVmResult { if should_direct_boot_linux(&loader.config) { let probe = crate::boot::images::fs::kernel_read( &loader.config, @@ -135,7 +138,7 @@ fn load_linux_from_memory( header: linux::X86LinuxHeader, kernel: &[u8], ramdisk: Option<&[u8]>, -) -> AxResult { +) -> AxVmResult { adjust_linux_dma_identity_layout(loader); let payload = linux_payload(&header, kernel)?; let initrd = ramdisk @@ -166,7 +169,7 @@ fn load_linux_from_filesystem( loader: &mut ImageLoaderCore<'_>, header: linux::X86LinuxHeader, kernel: &[u8], -) -> AxResult { +) -> AxVmResult { adjust_linux_dma_identity_layout(loader); let payload = linux_payload(&header, kernel)?; let initrd = loader @@ -174,7 +177,7 @@ fn load_linux_from_filesystem( .kernel .ramdisk_path .as_deref() - .map(|path| -> AxResult<_> { + .map(|path| -> AxVmResult<_> { let size = crate::boot::images::fs::image_size(path, loader.provider)?; Ok(linux::X86LinuxRange::new( loader.ramdisk_load_gpa()?.as_usize(), @@ -198,7 +201,7 @@ fn load_linux_from_filesystem( Ok(()) } -fn load_boot_image_from_memory(loader: &ImageLoaderCore<'_>, bios: Option<&[u8]>) -> AxResult { +fn load_boot_image_from_memory(loader: &ImageLoaderCore<'_>, bios: Option<&[u8]>) -> AxVmResult { if !loader.config.kernel.enable_bios { return Ok(()); } @@ -225,7 +228,7 @@ fn load_boot_image_from_memory(loader: &ImageLoaderCore<'_>, bios: Option<&[u8]> } #[cfg(any(feature = "fs", feature = "host-fs"))] -fn load_boot_image_from_filesystem(loader: &ImageLoaderCore<'_>) -> AxResult { +fn load_boot_image_from_filesystem(loader: &ImageLoaderCore<'_>) -> AxVmResult { if !loader.config.kernel.enable_bios { return Ok(()); } @@ -255,7 +258,7 @@ fn load_boot_image_from_filesystem(loader: &ImageLoaderCore<'_>) -> AxResult { } } -fn load_uefi_from_configured_path(loader: &ImageLoaderCore<'_>) -> AxResult { +fn load_uefi_from_configured_path(loader: &ImageLoaderCore<'_>) -> AxVmResult { let path = loader .config .kernel @@ -271,7 +274,7 @@ fn load_uefi_from_configured_path(loader: &ImageLoaderCore<'_>) -> AxResult { #[cfg(not(any(feature = "fs", feature = "host-fs")))] { let _ = (path, load_gpa); - ax_errno::ax_err!( + ax_err!( Unsupported, "UEFI firmware path requires the fs feature when no firmware image buffer is available" ) @@ -303,7 +306,7 @@ fn load_linux_layout( header: linux::X86LinuxHeader, layout: linux::X86LinuxLoadLayout, kernel: &[u8], -) -> AxResult { +) -> AxVmResult { let boot_params = build_boot_params(loader, header, layout, kernel)?; let boot_stub = linux_boot::build_boot_image(&layout).map_err(|err| { ax_err_type!( @@ -335,7 +338,7 @@ fn build_boot_params( header: linux::X86LinuxHeader, layout: linux::X86LinuxLoadLayout, kernel: &[u8], -) -> AxResult<[u8; linux::BOOT_PARAMS_SIZE]> { +) -> AxVmResult<[u8; linux::BOOT_PARAMS_SIZE]> { let mut builder = boot_params::BootParamsBuilder::new( kernel, header, @@ -383,7 +386,7 @@ fn load_multiboot_info( loader: &ImageLoaderCore<'_>, bios_image: &[u8], bios_load_gpa: GuestPhysAddr, -) -> AxResult { +) -> AxVmResult { const INFO_GPA: usize = 0x6000; const MMAP_GPA: usize = 0x6040; let mem_base = loader.main_memory.gpa.as_usize() as u64; @@ -431,7 +434,7 @@ fn detect_linux_image(image: &[u8]) -> Option { linux::X86LinuxHeader::parse(image).ok() } -fn linux_payload<'a>(header: &linux::X86LinuxHeader, image: &'a [u8]) -> AxResult<&'a [u8]> { +fn linux_payload<'a>(header: &linux::X86LinuxHeader, image: &'a [u8]) -> AxVmResult<&'a [u8]> { image.get(header.payload_offset()..).ok_or_else(|| { ax_err_type!( InvalidInput, @@ -444,14 +447,14 @@ fn linux_payload<'a>(header: &linux::X86LinuxHeader, image: &'a [u8]) -> AxResul }) } -fn linux_layout_error(err: linux::X86LinuxLayoutError) -> ax_errno::AxError { +fn linux_layout_error(err: linux::X86LinuxLayoutError) -> AxVmError { ax_err_type!( InvalidInput, format!("invalid x86 Linux memory layout: {err:?}") ) } -fn builtin_bios_load_gpa(configured: Option) -> AxResult { +fn builtin_bios_load_gpa(configured: Option) -> AxVmResult { let default = GuestPhysAddr::from(multiboot::DEFAULT_BIOS_LOAD_GPA); match configured { Some(gpa) if gpa != default => Err(ax_err_type!( @@ -467,7 +470,7 @@ fn builtin_bios_load_gpa(configured: Option) -> AxResult AxResult { +fn validate_bios_patch_region(bios: &[u8]) -> AxVmResult { let patch_end = multiboot::AXVM_BIOS_EBX_IMM_OFFSET + core::mem::size_of::(); if bios.len() < patch_end || bios[multiboot::AXVM_BIOS_EBX_IMM_OFFSET - 1] != multiboot::MOV_EBX_IMM32_OPCODE diff --git a/virtualization/axvm/src/arch/x86_64/exit.rs b/virtualization/axvm/src/arch/x86_64/exit.rs index 4f50ef0437..8524fd12be 100644 --- a/virtualization/axvm/src/arch/x86_64/exit.rs +++ b/virtualization/axvm/src/arch/x86_64/exit.rs @@ -1,10 +1,12 @@ //! x86-only port, nested-fault, and deferred exit handling. -use ax_errno::AxResult; use axvm_types::{AccessWidth, GuestPhysAddr, MappingFlags, Port}; use super::{ArchOps, AxvmX86Vcpu, X86_64Arch}; -use crate::architecture::{BoundVcpuExit, VcpuRunAction}; +use crate::{ + AxVmError, AxVmResult, + architecture::{BoundVcpuExit, VcpuRunAction}, +}; #[derive(Clone, Copy, Debug)] pub(crate) enum DeferredRunWork { @@ -36,8 +38,11 @@ pub(crate) fn handle_io_read( vm: &crate::AxVM, vcpu: &crate::vm::AxVCpuRef, exit: IoReadExit, -) -> AxResult> { - let val = vm.get_devices()?.handle_port_read(exit.port, exit.width)?; +) -> AxVmResult> { + let val = vm + .get_devices()? + .handle_port_read(exit.port, exit.width) + .map_err(|error| AxVmError::device("read guest I/O port", error))?; vcpu.set_gpr(0, val); Ok(BoundVcpuExit::Continue) } @@ -45,9 +50,10 @@ pub(crate) fn handle_io_read( pub(crate) fn handle_io_write( vm: &crate::AxVM, exit: IoWriteExit, -) -> AxResult> { +) -> AxVmResult> { vm.get_devices()? - .handle_port_write(exit.port, exit.width, exit.data as usize)?; + .handle_port_write(exit.port, exit.width, exit.data as usize) + .map_err(|error| AxVmError::device("write guest I/O port", error))?; Ok(BoundVcpuExit::Continue) } @@ -55,7 +61,7 @@ pub(crate) fn finish( vm: &crate::AxVMRef, vcpu: &crate::vm::AxVCpuRef, work: DeferredRunWork, -) -> AxResult { +) -> AxVmResult { match work { DeferredRunWork::ExternalInterrupt { vector } => { X86_64Arch::after_external_interrupt(vm, vcpu, vector); diff --git a/virtualization/axvm/src/arch/x86_64/mod.rs b/virtualization/axvm/src/arch/x86_64/mod.rs index aa5bbe4bf5..7738e7bac6 100644 --- a/virtualization/axvm/src/arch/x86_64/mod.rs +++ b/virtualization/axvm/src/arch/x86_64/mod.rs @@ -6,11 +6,10 @@ use alloc::{boxed::Box, sync::Arc}; use core::{arch::asm, time::Duration}; -use ax_errno::{AxError, AxResult}; use axvm_types::{ - AccessWidth, EmulatedDeviceConfig, EmulatedDeviceType, GuestPhysAddr, InterruptTriggerMode, - MappingFlags, NestedPagingConfig, Port, SysRegAddr, VCpuId, VMId, VmArchPerCpuOps, - VmArchVcpuOps, + AccessWidth, AxVmError as BackendError, AxVmResult as BackendResult, EmulatedDeviceConfig, + EmulatedDeviceType, GuestPhysAddr, InterruptTriggerMode, MappingFlags, NestedPagingConfig, + Port, SysRegAddr, VCpuId, VMId, VmArchPerCpuOps, VmArchVcpuOps, }; use x86_vcpu::{ X86AccessFlags, X86AccessWidth, X86GuestPhysAddr, X86HostOps, X86HostPhysAddr, X86HostVirtAddr, @@ -24,7 +23,7 @@ use x86_vlapic::{ use super::{ArchOps, BoundVcpuExit, HypercallExit, MmioReadExit, MmioWriteExit, VcpuRunAction}; use crate::{ - StopReason, + AxVmError, AxVmResult, StopReason, host::{HostMemory, default_host}, manager, vcpu::get_current_vcpu, @@ -88,7 +87,7 @@ impl ArchOps for X86_64Arch { vm: &crate::AxVMRef, vcpu: &crate::vm::AxVCpuRef, exit: ::Exit, - ) -> AxResult> { + ) -> AxVmResult> { match exit { X86VmExit::Hypercall { nr, args } => { super::handle_hypercall(vm, vcpu, HypercallExit { nr, args }) @@ -208,7 +207,10 @@ impl ArchOps for X86_64Arch { })) } X86VmExit::Nothing => Ok(BoundVcpuExit::Continue), - _ => Err(AxError::Unsupported), + _ => Err(AxVmError::unsupported( + "handle x86 VM exit", + "unsupported VM exit reason", + )), } } @@ -216,7 +218,7 @@ impl ArchOps for X86_64Arch { vm: &crate::AxVMRef, vcpu: &crate::vm::AxVCpuRef, work: Self::DeferredRunWork, - ) -> AxResult { + ) -> AxVmResult { exit::finish(vm, vcpu, work) } } @@ -356,37 +358,37 @@ impl VmArchVcpuOps for AxvmX86Vcpu { type SetupConfig = X86VCpuSetupConfig; type Exit = X86VmExit; - fn new(vm_id: VMId, vcpu_id: VCpuId, config: Self::CreateConfig) -> AxResult { + fn new(vm_id: VMId, vcpu_id: VCpuId, config: Self::CreateConfig) -> BackendResult { x86_result(x86_vcpu::X86ArchVCpu::new_with_config( vm_id, vcpu_id, config, )) .map(Self) } - fn set_entry(&mut self, entry: GuestPhysAddr) -> AxResult { + fn set_entry(&mut self, entry: GuestPhysAddr) -> BackendResult { x86_result(self.0.set_entry(ax_guest_phys_addr_to_x86(entry))) } - fn set_nested_page_table(&mut self, config: NestedPagingConfig) -> AxResult { + fn set_nested_page_table(&mut self, config: NestedPagingConfig) -> BackendResult { x86_result( self.0 .set_nested_page_table(ax_nested_paging_to_x86(config)), ) } - fn setup(&mut self, config: Self::SetupConfig) -> AxResult { + fn setup(&mut self, config: Self::SetupConfig) -> BackendResult { x86_result(self.0.setup(config)) } - fn run(&mut self) -> AxResult { + fn run(&mut self) -> BackendResult { x86_result(self.0.run()) } - fn bind(&mut self) -> AxResult { + fn bind(&mut self) -> BackendResult { x86_result(self.0.bind()) } - fn unbind(&mut self) -> AxResult { + fn unbind(&mut self) -> BackendResult { x86_result(self.0.unbind()) } @@ -394,7 +396,7 @@ impl VmArchVcpuOps for AxvmX86Vcpu { self.0.set_gpr(reg, val); } - fn inject_interrupt(&mut self, vector: usize) -> AxResult { + fn inject_interrupt(&mut self, vector: usize) -> BackendResult { x86_result(self.0.inject_interrupt(vector)) } @@ -402,7 +404,7 @@ impl VmArchVcpuOps for AxvmX86Vcpu { &mut self, vector: usize, trigger: InterruptTriggerMode, - ) -> AxResult { + ) -> BackendResult { x86_result( self.0.inject_interrupt_with_trigger( vector, @@ -423,7 +425,7 @@ impl VmArchVcpuOps for AxvmX86Vcpu { pub(crate) struct AxvmX86PerCpu(x86_vcpu::X86ArchPerCpuState); impl VmArchPerCpuOps for AxvmX86PerCpu { - fn new(cpu_id: usize) -> AxResult { + fn new(cpu_id: usize) -> BackendResult { x86_result(x86_vcpu::X86ArchPerCpuState::new(cpu_id)).map(Self) } @@ -431,11 +433,11 @@ impl VmArchPerCpuOps for AxvmX86PerCpu { self.0.is_enabled() } - fn hardware_enable(&mut self) -> AxResult { + fn hardware_enable(&mut self) -> BackendResult { x86_result(self.0.hardware_enable()) } - fn hardware_disable(&mut self) -> AxResult { + fn hardware_disable(&mut self) -> BackendResult { x86_result(self.0.hardware_disable()) } } @@ -443,11 +445,13 @@ impl VmArchPerCpuOps for AxvmX86PerCpu { pub(crate) fn register_arch_device( config: &EmulatedDeviceConfig, devices: &mut axdevice::AxVmDevices, -) -> AxResult { +) -> AxVmResult { match config.emu_type { EmulatedDeviceType::Console => { let serial = Arc::new(axdevice::X86SerialPortDevice::::new()); - devices.add_x86_serial_dev(serial)?; + devices + .add_x86_serial_dev(serial) + .map_err(|error| AxVmError::device("register x86 serial device", error))?; info!("x86 16550 serial initialized for ports 0x3f8..=0x3ff"); } EmulatedDeviceType::X86IoApic => { @@ -455,7 +459,9 @@ pub(crate) fn register_arch_device( x86_vlapic::X86GuestPhysAddr::from_usize(config.base_gpa), Some(config.length), )); - devices.add_x86_ioapic_dev(ioapic)?; + devices + .add_x86_ioapic_dev(ioapic) + .map_err(|error| AxVmError::device("register x86 I/O APIC", error))?; info!( "x86 IO APIC initialized with base GPA {:#x} and length {:#x}", config.base_gpa, config.length @@ -463,7 +469,9 @@ pub(crate) fn register_arch_device( } EmulatedDeviceType::X86Pit => { let pit = Arc::new(axdevice::X86PitDevice::::new()); - devices.add_x86_pit_dev(pit)?; + devices + .add_x86_pit_dev(pit) + .map_err(|error| AxVmError::device("register x86 PIT", error))?; info!("x86 PIT initialized for ports 0x40..=0x43 and 0x61"); } _ => {} @@ -480,7 +488,7 @@ pub(crate) fn x86_apic_access_page_addr() -> axvm_types::HostPhysAddr { fn handle_x86_nested_page_fault( vm: &crate::AxVMRef, exit: NestedPageFaultExit, -) -> AxResult> { +) -> AxVmResult> { if vm.get_devices()?.find_mmio_dev(exit.addr).is_some() { warn!( "VM[{}] nested page fault at {:#x} maps MMIO but x86 core did not decode it", @@ -509,22 +517,22 @@ fn handle_x86_nested_page_fault( } } -fn x86_result(result: X86VcpuResult) -> AxResult { +fn x86_result(result: X86VcpuResult) -> BackendResult { result.map_err(x86_error_to_ax) } -fn x86_error_to_ax(err: X86VcpuError) -> AxError { +fn x86_error_to_ax(err: X86VcpuError) -> BackendError { match err { - X86VcpuError::InvalidInput => AxError::InvalidInput, - X86VcpuError::InvalidData => AxError::InvalidData, - X86VcpuError::Unsupported => AxError::Unsupported, - X86VcpuError::BadState => AxError::BadState, - X86VcpuError::NoMemory => AxError::NoMemory, - X86VcpuError::ResourceBusy => AxError::ResourceBusy, + X86VcpuError::InvalidInput => BackendError::InvalidInput, + X86VcpuError::InvalidData => BackendError::InvalidData, + X86VcpuError::Unsupported => BackendError::Unsupported, + X86VcpuError::BadState => BackendError::BadState, + X86VcpuError::NoMemory => BackendError::NoMemory, + X86VcpuError::ResourceBusy => BackendError::ResourceBusy, } } -fn ax_error_to_vlapic(_err: AxError) -> X86VlapicError { +fn ax_error_to_vlapic(_err: crate::AxVmError) -> X86VlapicError { X86VlapicError::BadState } @@ -620,12 +628,15 @@ mod tests { fn converts_x86_vcpu_errors_to_ax_errors() { assert_eq!( x86_error_to_ax(X86VcpuError::InvalidInput), - AxError::InvalidInput + BackendError::InvalidInput + ); + assert_eq!( + x86_error_to_ax(X86VcpuError::NoMemory), + BackendError::NoMemory ); - assert_eq!(x86_error_to_ax(X86VcpuError::NoMemory), AxError::NoMemory); assert_eq!( x86_error_to_ax(X86VcpuError::ResourceBusy), - AxError::ResourceBusy + BackendError::ResourceBusy ); } diff --git a/virtualization/axvm/src/arch/x86_64/port.rs b/virtualization/axvm/src/arch/x86_64/port.rs index d71daec1be..232320eb6f 100644 --- a/virtualization/axvm/src/arch/x86_64/port.rs +++ b/virtualization/axvm/src/arch/x86_64/port.rs @@ -1,7 +1,9 @@ //! Native x86 host I/O port passthrough devices. -use ax_errno::{AxResult, ax_err}; use axdevice_base::{AccessWidth, BaseDeviceOps, EmuDeviceType, Port, PortRange}; +use axvm_types::{AxVmError as BackendError, AxVmResult as BackendResult}; + +use crate::{AxVmResult, ax_err}; /// A host x86 I/O port range passed directly through to a guest. pub(crate) struct HostPortPassthrough { @@ -11,7 +13,7 @@ pub(crate) struct HostPortPassthrough { impl HostPortPassthrough { /// Creates a passthrough device for an inclusive host I/O port range. - pub(crate) fn new(base: u16, length: u16) -> AxResult { + pub(crate) fn new(base: u16, length: u16) -> AxVmResult { if length == 0 { return ax_err!(InvalidInput, "host port passthrough range is empty"); } @@ -38,22 +40,22 @@ impl BaseDeviceOps for HostPortPassthrough { PortRange::new(self.base, self.end()) } - fn handle_read(&self, port: Port, width: AccessWidth) -> AxResult { + fn handle_read(&self, port: Port, width: AccessWidth) -> BackendResult { match width { AccessWidth::Byte => Ok(unsafe { inb(port.number()) } as usize), AccessWidth::Word => Ok(unsafe { inw(port.number()) } as usize), AccessWidth::Dword => Ok(unsafe { inl(port.number()) } as usize), - AccessWidth::Qword => ax_err!(Unsupported, "x86 port I/O does not support qword read"), + AccessWidth::Qword => Err(BackendError::Unsupported), } } - fn handle_write(&self, port: Port, width: AccessWidth, value: usize) -> AxResult { + fn handle_write(&self, port: Port, width: AccessWidth, value: usize) -> BackendResult { match width { AccessWidth::Byte => unsafe { outb(port.number(), value as u8) }, AccessWidth::Word => unsafe { outw(port.number(), value as u16) }, AccessWidth::Dword => unsafe { outl(port.number(), value as u32) }, AccessWidth::Qword => { - return ax_err!(Unsupported, "x86 port I/O does not support qword write"); + return Err(BackendError::Unsupported); } } Ok(()) diff --git a/virtualization/axvm/src/arch/x86_64/vm.rs b/virtualization/axvm/src/arch/x86_64/vm.rs index f852a8aa2b..fe9f68a037 100644 --- a/virtualization/axvm/src/arch/x86_64/vm.rs +++ b/virtualization/axvm/src/arch/x86_64/vm.rs @@ -2,7 +2,6 @@ use alloc::sync::Arc; -use ax_errno::{AxResult, ax_err_type}; #[cfg(feature = "vmx")] use ax_memory_addr::PAGE_SIZE_4K; use axdevice_base::{BaseDeviceOps, DeviceRegistry as _, PortDeviceAdapter}; @@ -18,6 +17,7 @@ use x86_vcpu::{ use super::x86_apic_access_page_addr; use super::{X86_64Arch, npt, x86_result}; use crate::{ + AxVmError, AxVmResult, ax_err, ax_err_type, config::AxVMConfig, layout::GuestOwnedRegion, vm::{ @@ -34,7 +34,7 @@ use crate::{ }; impl X86_64Arch { - pub(crate) fn create_vm_resources(config: AxVMConfig) -> AxResult { + pub(crate) fn create_vm_resources(config: AxVMConfig) -> AxVmResult { let placements = config.phys_cpu_ls.get_vcpu_affinities_pcpu_ids(); let levels = guest_page_table_levels(&placements); let page_table = npt::NestedPageTable::new(levels)?; @@ -43,17 +43,14 @@ impl X86_64Arch { 3 => 39, 4 => 48, _ => { - return ax_errno::ax_err!( - InvalidInput, - "unsupported x86 nested page-table levels" - ); + return ax_err!(InvalidInput, "unsupported x86 nested page-table levels"); } }; Ok(NestedPagingConfig::new(root_paddr, levels, gpa_bits, 0)) }) } - pub(crate) fn init_vm(vm: &AxVM, request: VmInitRequest<'_>) -> AxResult { + pub(crate) fn init_vm(vm: &AxVM, request: VmInitRequest<'_>) -> AxVmResult { match request { VmInitRequest::Default => { let factories = default_device_factories()?; @@ -72,7 +69,7 @@ fn init_vm_with( vm: &AxVM, factories: &axdevice::DeviceFactoryRegistry, interrupt_fabric: crate::InterruptFabric, -) -> AxResult { +) -> AxVmResult { complete_vm_init(vm, interrupt_fabric, |resources, interrupt_fabric| { let placements = vcpu_placements(resources); let vcpus = PreparedVcpus::create(vm.id(), &placements, |_| Ok(X86VCpuCreateConfig))?; @@ -94,7 +91,7 @@ fn init_vm_with( fn build_vcpu_setup_config( config: &AxVMConfig, memory_regions: &[crate::vm::VMMemoryRegion], -) -> AxResult<::SetupConfig> { +) -> AxVmResult<::SetupConfig> { let mut setup_config = X86VCpuSetupConfig { emulate_com1: config .emu_devices() @@ -111,12 +108,13 @@ fn build_vcpu_setup_config( ..Default::default() }; for port in config.pass_through_ports() { - x86_result(setup_config.add_passthrough_port_range(port.base, port.length))?; + x86_result(setup_config.add_passthrough_port_range(port.base, port.length)) + .map_err(|error| AxVmError::vcpu("configure passthrough port range", error))?; } Ok(setup_config) } -fn register_arch_devices(config: &AxVMConfig, devices: &mut axdevice::AxVmDevices) -> AxResult { +fn register_arch_devices(config: &AxVMConfig, devices: &mut axdevice::AxVmDevices) -> AxVmResult { for port in config.pass_through_ports() { let passthrough = Arc::new(super::port::HostPortPassthrough::new( port.base, @@ -151,14 +149,17 @@ fn append_arch_owned_regions(regions: &mut alloc::vec::Vec) { let _ = regions; } -fn map_arch_address_space(resources: &mut AxVMResources) -> AxResult { +fn map_arch_address_space(resources: &mut AxVMResources) -> AxVmResult { #[cfg(feature = "vmx")] - resources.address_space.map_linear( - axvm_types::GuestPhysAddr::from(x86_vcpu::X86_APIC_ACCESS_GPA), - x86_apic_access_page_addr(), - PAGE_SIZE_4K, - MappingFlags::DEVICE | MappingFlags::READ | MappingFlags::WRITE, - )?; + resources + .address_space + .map_linear( + axvm_types::GuestPhysAddr::from(x86_vcpu::X86_APIC_ACCESS_GPA), + x86_apic_access_page_addr(), + PAGE_SIZE_4K, + MappingFlags::DEVICE | MappingFlags::READ | MappingFlags::WRITE, + ) + .map_err(|error| AxVmError::memory("map x86 APIC access page", error))?; #[cfg(not(feature = "vmx"))] let _ = resources; Ok(()) diff --git a/virtualization/axvm/src/architecture/capabilities.rs b/virtualization/axvm/src/architecture/capabilities.rs index 2cd187c217..e22a90c8d4 100644 --- a/virtualization/axvm/src/architecture/capabilities.rs +++ b/virtualization/axvm/src/architecture/capabilities.rs @@ -1,6 +1,6 @@ //! Small capability boundaries implemented by the selected guest architecture. -use ax_errno::AxResult; +use crate::AxVmResult; /// Guest firmware preparation performed before common VM memory loading. pub(crate) trait GuestBootPlatform { @@ -10,7 +10,7 @@ pub(crate) trait GuestBootPlatform { _vm_config: &mut crate::config::AxVMConfig, _vm_create_config: &mut axvmconfig::AxVMCrateConfig, _provider: &dyn crate::boot::BootImageProvider, - ) -> AxResult> { + ) -> AxVmResult> { Ok(None) } } @@ -26,21 +26,21 @@ pub(crate) trait BootImagePlatform { fn load_images_from_memory( loader: &mut crate::boot::images::ImageLoaderCore<'_>, images: crate::boot::StaticVmImage, - ) -> AxResult { + ) -> AxVmResult { loader.load_standard_images_from_memory(images, Self::load_guest_dtb) } #[cfg(any(feature = "fs", feature = "host-fs"))] fn load_images_from_filesystem( loader: &mut crate::boot::images::ImageLoaderCore<'_>, - ) -> AxResult { + ) -> AxVmResult { loader.load_standard_images_from_filesystem(Self::load_guest_dtb) } fn load_guest_dtb( _loader: &crate::boot::images::ImageLoaderCore<'_>, _dtb: &crate::boot::fdt::GuestDtbImage, - ) -> AxResult { + ) -> AxVmResult { Ok(()) } diff --git a/virtualization/axvm/src/architecture/cpu_up.rs b/virtualization/axvm/src/architecture/cpu_up.rs index 7d9e69f218..bcb83f346d 100644 --- a/virtualization/axvm/src/architecture/cpu_up.rs +++ b/virtualization/axvm/src/architecture/cpu_up.rs @@ -1,9 +1,11 @@ //! Shared secondary-vCPU boot flow for architectures that expose CPU-up exits. -use ax_errno::AxResult; use axvm_types::GuestPhysAddr; -use crate::architecture::{ArchOps, BoundVcpuExit, VcpuRunAction}; +use crate::{ + AxVmResult, + architecture::{ArchOps, BoundVcpuExit, VcpuRunAction}, +}; #[derive(Clone, Copy, Debug)] pub(crate) struct CpuUpExit { @@ -28,7 +30,7 @@ pub(crate) fn handle( vm: &crate::AxVMRef, vcpu: &crate::vm::AxVCpuRef, exit: CpuUpExit, -) -> AxResult> { +) -> AxVmResult> { let vm_id = vm.id(); let vcpu_id = vcpu.id(); info!( diff --git a/virtualization/axvm/src/architecture/exit.rs b/virtualization/axvm/src/architecture/exit.rs index 2c1d95c6f6..e786a0bc3b 100644 --- a/virtualization/axvm/src/architecture/exit.rs +++ b/virtualization/axvm/src/architecture/exit.rs @@ -1,16 +1,19 @@ //! Architecture-neutral handlers for exits shared by every guest architecture. -use ax_errno::AxResult; use axvm_types::VmArchVcpuOps; use super::{ArchOps, BoundVcpuExit, HypercallExit, MmioReadExit, MmioWriteExit, VcpuRunAction}; +use crate::{AxVmError, AxVmResult}; pub(crate) fn handle_mmio_read( vm: &crate::AxVM, vcpu: &crate::vm::AxVCpuRef, exit: MmioReadExit, -) -> AxResult> { - let raw = vm.get_devices()?.handle_mmio_read(exit.addr, exit.width)?; +) -> AxVmResult> { + let raw = vm + .get_devices()? + .handle_mmio_read(exit.addr, exit.width) + .map_err(|error| AxVmError::device("read guest MMIO", error))?; let masked = raw & crate::vm::width_mask(exit.width); let val = if exit.signed_ext { crate::vm::sign_extend_value(masked, exit.width) @@ -24,7 +27,7 @@ pub(crate) fn handle_mmio_read( pub(crate) fn handle_mmio_write( vm: &crate::AxVMRef, exit: MmioWriteExit, -) -> AxResult> { +) -> AxVmResult> { vm.handle_mmio_write(exit.addr, exit.width, exit.data as usize)?; A::after_mmio_write(vm); Ok(BoundVcpuExit::Continue) @@ -34,7 +37,7 @@ pub(crate) fn handle_hypercall( vm: &crate::AxVMRef, vcpu: &crate::vm::AxVCpuRef, exit: HypercallExit, -) -> AxResult> { +) -> AxVmResult> { debug!("Hypercall [{:#x}] args {:x?}", exit.nr, exit.args); match crate::runtime::hvc::HyperCall::new(vm.clone(), exit.nr, exit.args) { Ok(hypercall) => { diff --git a/virtualization/axvm/src/architecture/ops.rs b/virtualization/axvm/src/architecture/ops.rs index 144d9522b7..fdde47f157 100644 --- a/virtualization/axvm/src/architecture/ops.rs +++ b/virtualization/axvm/src/architecture/ops.rs @@ -2,12 +2,12 @@ use alloc::{format, vec::Vec}; -use ax_errno::{AxResult, ax_err}; use ax_memory_addr::VirtAddr; use axaddrspace::NestedPageTableOps; use axvm_types::{VmArchPerCpuOps, VmArchVcpuOps, VmVcpuState}; use super::{BoundVcpuExit, VcpuRunAction}; +use crate::{AxVmResult, ax_err}; pub(crate) trait ArchOps { type VCpu: VmArchVcpuOps; @@ -89,18 +89,18 @@ pub(crate) trait ArchOps { vm: &crate::AxVMRef, vcpu: &crate::vm::AxVCpuRef, exit: ::Exit, - ) -> AxResult>; + ) -> AxVmResult>; fn finish_deferred_run_work( vm: &crate::AxVMRef, vcpu: &crate::vm::AxVCpuRef, work: Self::DeferredRunWork, - ) -> AxResult; + ) -> AxVmResult; fn run_vcpu( vm: &crate::AxVMRef, vcpu: &crate::vm::AxVCpuRef, - ) -> AxResult + ) -> AxVmResult where Self: Sized, { @@ -118,7 +118,7 @@ pub(crate) trait ArchOps { } } - let run_result = vcpu.with_current_cpu_set(|| -> AxResult<_> { + let run_result = vcpu.with_current_cpu_set(|| -> AxVmResult<_> { loop { crate::runtime::vcpus::inject_pending_interrupts::(vm.id(), vcpu_id, vcpu); diff --git a/virtualization/axvm/src/architecture/sysreg.rs b/virtualization/axvm/src/architecture/sysreg.rs index 1ab7ff7ae1..a381e87fc1 100644 --- a/virtualization/axvm/src/architecture/sysreg.rs +++ b/virtualization/axvm/src/architecture/sysreg.rs @@ -1,9 +1,8 @@ //! Shared system-register device exits used by AArch64 and x86_64 guests. -use ax_errno::AxResult; use axvm_types::{AccessWidth, SysRegAddr, VmArchVcpuOps}; -use crate::architecture::BoundVcpuExit; +use crate::{AxVmError, AxVmResult, architecture::BoundVcpuExit}; #[derive(Clone, Copy, Debug)] pub(crate) struct SysRegReadExit { @@ -21,10 +20,11 @@ pub(crate) fn handle_read( vm: &crate::AxVM, vcpu: &crate::vm::AxVCpuRef, exit: SysRegReadExit, -) -> AxResult> { +) -> AxVmResult> { let val = vm .get_devices()? - .handle_sys_reg_read(exit.addr, AccessWidth::Qword)?; + .handle_sys_reg_read(exit.addr, AccessWidth::Qword) + .map_err(|error| AxVmError::device("read guest system register", error))?; vcpu.set_gpr(exit.reg, val); Ok(BoundVcpuExit::Continue) } @@ -32,8 +32,9 @@ pub(crate) fn handle_read( pub(crate) fn handle_write( vm: &crate::AxVM, exit: SysRegWriteExit, -) -> AxResult> { +) -> AxVmResult> { vm.get_devices()? - .handle_sys_reg_write(exit.addr, AccessWidth::Qword, exit.value as usize)?; + .handle_sys_reg_write(exit.addr, AccessWidth::Qword, exit.value as usize) + .map_err(|error| AxVmError::device("write guest system register", error))?; Ok(BoundVcpuExit::Continue) } diff --git a/virtualization/axvm/src/boot/fdt/core/create.rs b/virtualization/axvm/src/boot/fdt/core/create.rs index d6bc933669..eb7368bd61 100644 --- a/virtualization/axvm/src/boot/fdt/core/create.rs +++ b/virtualization/axvm/src/boot/fdt/core/create.rs @@ -15,19 +15,21 @@ use alloc::{string::String, vec::Vec}; use core::ptr::NonNull; -use ax_errno::{AxResult, ax_err_type}; use ax_memory_addr::MemoryAddr; use axvmconfig::AxVMCrateConfig; use fdt_edit::{Fdt, Node, NodeId}; use super::tree::{FdtTree, GuestMemorySpec}; -use crate::{AxVMRef, GuestPhysAddr, VMMemoryRegion, boot::images::load_vm_image_from_memory}; +use crate::{ + AxVMRef, AxVmResult, GuestPhysAddr, VMMemoryRegion, ax_err_type, + boot::images::load_vm_image_from_memory, +}; pub fn create_guest_fdt( fdt: &Fdt, passthrough_device_names: &[String], crate_config: &AxVMCrateConfig, -) -> AxResult> { +) -> AxVmResult> { let phys_cpu_ids = crate_config .base .phys_cpu_ids @@ -177,7 +179,7 @@ pub fn update_fdt( dtb_size: usize, vm: AxVMRef, crate_config: &AxVMCrateConfig, -) -> AxResult { +) -> AxVmResult { let patch_runtime = super::selected_guest_fdt_policy().patch_runtime; // SAFETY: `fdt_src` originates from `GuestDtbImage::as_bytes`, and the // caller supplies the exact slice length while the image remains borrowed. @@ -187,7 +189,7 @@ pub fn update_fdt( load_patched_fdt(vm, new_fdt_bytes) } -fn load_patched_fdt(vm: AxVMRef, new_fdt_bytes: Vec) -> AxResult { +fn load_patched_fdt(vm: AxVMRef, new_fdt_bytes: Vec) -> AxVmResult { let dest_addr = calculate_dtb_load_addr(vm.clone(), new_fdt_bytes.len())?; debug!( "New FDT will be loaded at {:x}, size: 0x{:x}", @@ -204,7 +206,7 @@ pub fn patch_guest_fdt_for_runtime( crate_config: &AxVMCrateConfig, initrd_start_size: Option<(u64, u64)>, create_chosen: bool, -) -> AxResult> { +) -> AxVmResult> { let mut tree = FdtTree::from_bytes(fdt_bytes)?; let memory_specs = guest_memory_specs(memory_regions, crate_config); tree.rebuild_memory_nodes(&memory_specs)?; @@ -217,7 +219,7 @@ pub fn patch_guest_fdt_for_runtime( Ok(tree.finish()) } -pub(crate) fn calculate_dtb_load_addr(vm: AxVMRef, fdt_size: usize) -> AxResult { +pub(crate) fn calculate_dtb_load_addr(vm: AxVMRef, fdt_size: usize) -> AxVmResult { const MB: usize = 1024 * 1024; let main_memory = diff --git a/virtualization/axvm/src/boot/fdt/core/mod.rs b/virtualization/axvm/src/boot/fdt/core/mod.rs index 5f6c8736c9..c12ffc2569 100644 --- a/virtualization/axvm/src/boot/fdt/core/mod.rs +++ b/virtualization/axvm/src/boot/fdt/core/mod.rs @@ -2,10 +2,10 @@ use alloc::{format, vec::Vec}; -use ax_errno::{AxResult, ax_err_type}; use axvmconfig::{AxVMCrateConfig, VMBootProtocol}; use crate::{ + AxVmResult, ax_err, ax_err_type, boot::{BootImageProvider, fdt::GuestDtbImage}, config::AxVMConfig, }; @@ -28,7 +28,7 @@ pub fn prepare_dtb_guest( vm_config: &mut AxVMConfig, vm_create_config: &mut AxVMCrateConfig, provider: &dyn BootImageProvider, -) -> AxResult> { +) -> AxVmResult> { if vm_create_config.kernel.effective_boot_protocol() == VMBootProtocol::Uefi { skip_guest_dtb(vm_config, vm_create_config); return Ok(None); @@ -58,7 +58,7 @@ fn build_guest_dtb( vm_create_config: &mut AxVMCrateConfig, provider: &dyn BootImageProvider, host_fdt_bytes: Option<&'static [u8]>, -) -> AxResult> { +) -> AxVmResult> { let provided_dtb = get_developer_provided_dtb(vm_config, vm_create_config, provider)?; match (host_fdt_bytes, provided_dtb) { @@ -99,7 +99,7 @@ fn build_guest_dtb( } } -fn parse_host_fdt(host_fdt_bytes: &'static [u8]) -> AxResult { +fn parse_host_fdt(host_fdt_bytes: &'static [u8]) -> AxVmResult { fdt_edit::Fdt::from_bytes(host_fdt_bytes) .map_err(|err| ax_err_type!(InvalidData, format!("Failed to parse host FDT: {err:#?}"))) } @@ -108,7 +108,7 @@ fn enrich_guest_config( vm_config: &mut AxVMConfig, vm_create_config: &mut AxVMCrateConfig, guest_dtb: Option<&GuestDtbImage>, -) -> AxResult { +) -> AxVmResult { let Some(dtb) = guest_dtb.map(GuestDtbImage::as_bytes) else { clear_unresolved_dtb_config(vm_config, vm_create_config); return Ok(()); @@ -148,7 +148,7 @@ fn get_developer_provided_dtb( vm_config: &AxVMConfig, crate_config: &AxVMCrateConfig, provider: &dyn BootImageProvider, -) -> AxResult>> { +) -> AxVmResult>> { match crate_config.kernel.image_location.as_deref() { Some("memory") => Ok(provider .static_vm_images() @@ -166,7 +166,7 @@ fn get_developer_provided_dtb( .as_deref() .map(|path| crate::boot::images::fs::read_full_image(path, provider)) .transpose(), - _ => ax_errno::ax_err!( + _ => ax_err!( InvalidInput, "Unsupported image_location; use \"memory\" or enable fs feature for \"fs\"" ), diff --git a/virtualization/axvm/src/boot/fdt/core/parser.rs b/virtualization/axvm/src/boot/fdt/core/parser.rs index e7dda1b467..1e835c5213 100644 --- a/virtualization/axvm/src/boot/fdt/core/parser.rs +++ b/virtualization/axvm/src/boot/fdt/core/parser.rs @@ -21,13 +21,12 @@ use alloc::{ vec::Vec, }; -use ax_errno::{AxResult, ax_err_type}; use axvmconfig::{ AxVMCrateConfig, PassThroughDeviceConfig, ReservedAddressConfig, VmMemConfig, VmMemMappingType, }; use fdt_edit::{Fdt, Node, NodeType, PciRange, PciSpace}; -use crate::{MappingFlags, config::AxVMConfig}; +use crate::{AxVmResult, MappingFlags, ax_err_type, config::AxVMConfig}; const PAGE_SIZE_4K: usize = 0x1000; @@ -48,7 +47,7 @@ pub fn setup_guest_fdt_from_vmm( fdt_bytes: &[u8], vm_cfg: &mut AxVMConfig, crate_config: &AxVMCrateConfig, -) -> AxResult> { +) -> AxVmResult> { let fdt = Fdt::from_bytes(fdt_bytes) .map_err(|e| ax_err_type!(InvalidData, format!("Failed to parse host FDT: {e:#?}")))?; @@ -217,7 +216,7 @@ pub fn reserve_excluded_device_ranges( vm_cfg: &mut AxVMConfig, crate_cfg: &AxVMCrateConfig, dtb: &[u8], -) -> AxResult { +) -> AxVmResult { let excluded_paths = excluded_device_paths(crate_cfg); if excluded_paths.is_empty() { return Ok(()); @@ -317,7 +316,7 @@ fn should_skip_passthrough_node( false } -pub fn parse_reserved_memory_regions(crate_cfg: &mut AxVMCrateConfig, dtb: &[u8]) -> AxResult { +pub fn parse_reserved_memory_regions(crate_cfg: &mut AxVMCrateConfig, dtb: &[u8]) -> AxVmResult { let fdt = Fdt::from_bytes(dtb).map_err(|e| { ax_err_type!( InvalidData, @@ -368,7 +367,7 @@ pub fn set_phys_cpu_sets( vm_cfg: &mut AxVMConfig, fdt: &Fdt, crate_config: &AxVMCrateConfig, -) -> AxResult { +) -> AxVmResult { let phys_cpu_ids = crate_config .base .phys_cpu_ids @@ -504,7 +503,7 @@ pub fn parse_passthrough_devices_address( vm_cfg: &mut AxVMConfig, crate_cfg: &AxVMCrateConfig, dtb: &[u8], -) -> AxResult { +) -> AxVmResult { let devices = vm_cfg.pass_through_devices().to_vec(); if !devices.is_empty() && devices[0].length != 0 { for (index, device) in devices.iter().enumerate() { @@ -581,7 +580,7 @@ pub fn parse_passthrough_devices_address( Ok(()) } -pub fn parse_vm_interrupt(vm_cfg: &mut AxVMConfig, dtb: &[u8]) -> AxResult { +pub fn parse_vm_interrupt(vm_cfg: &mut AxVMConfig, dtb: &[u8]) -> AxVmResult { let decode_interrupt = super::selected_guest_fdt_policy().decode_interrupt; let fdt = Fdt::from_bytes(dtb).map_err(|e| { ax_err_type!( @@ -621,7 +620,7 @@ pub fn update_provided_fdt( provided_dtb: &[u8], host_dtb: Option<&[u8]>, crate_config: &AxVMCrateConfig, -) -> AxResult> { +) -> AxVmResult> { let patch_provided = super::selected_guest_fdt_policy().patch_provided; patch_provided(provided_dtb, host_dtb, crate_config) } diff --git a/virtualization/axvm/src/boot/fdt/core/policy.rs b/virtualization/axvm/src/boot/fdt/core/policy.rs index 2ed33515a1..8559485e7c 100644 --- a/virtualization/axvm/src/boot/fdt/core/policy.rs +++ b/virtualization/axvm/src/boot/fdt/core/policy.rs @@ -2,11 +2,12 @@ use alloc::vec::Vec; -use ax_errno::AxResult; use axvmconfig::AxVMCrateConfig; -pub type RuntimeFdtPatch = fn(&[u8], &crate::AxVMRef, &AxVMCrateConfig) -> AxResult>; -pub type ProvidedFdtPatch = fn(&[u8], Option<&[u8]>, &AxVMCrateConfig) -> AxResult>; +use crate::AxVmResult; + +pub type RuntimeFdtPatch = fn(&[u8], &crate::AxVMRef, &AxVMCrateConfig) -> AxVmResult>; +pub type ProvidedFdtPatch = fn(&[u8], Option<&[u8]>, &AxVMCrateConfig) -> AxVmResult>; /// Architecture operations required by common guest FDT processing. #[derive(Clone, Copy)] diff --git a/virtualization/axvm/src/boot/fdt/core/tree.rs b/virtualization/axvm/src/boot/fdt/core/tree.rs index f47dcf63bd..ac206c9263 100644 --- a/virtualization/axvm/src/boot/fdt/core/tree.rs +++ b/virtualization/axvm/src/boot/fdt/core/tree.rs @@ -1,9 +1,10 @@ use alloc::{format, string::String, vec::Vec}; -use ax_errno::{AxResult, ax_err_type}; use fdt_edit::{Fdt, Node, NodeId, Property}; use fdt_raw::{Header, RegInfo}; +use crate::{AxVmResult, ax_err_type}; + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) struct GuestMemorySpec { pub(crate) base: u64, @@ -29,7 +30,7 @@ impl FdtTree { Self { fdt } } - pub(crate) fn from_bytes(bytes: &[u8]) -> AxResult { + pub(crate) fn from_bytes(bytes: &[u8]) -> AxVmResult { let fdt = Fdt::from_bytes(bytes) .map_err(|err| ax_err_type!(InvalidData, format!("Failed to parse FDT: {err:#?}")))?; Ok(Self::from_fdt(fdt)) @@ -60,7 +61,7 @@ impl FdtTree { .collect() } - pub(crate) fn ensure_path(&mut self, path: &str) -> AxResult { + pub(crate) fn ensure_path(&mut self, path: &str) -> AxVmResult { if let Some(id) = self.fdt.get_by_path_id(path) { return Ok(id); } @@ -82,7 +83,7 @@ impl FdtTree { Ok(parent) } - pub(crate) fn set_property(&mut self, node_id: NodeId, prop: Property) -> AxResult { + pub(crate) fn set_property(&mut self, node_id: NodeId, prop: Property) -> AxVmResult { let node = self .fdt .node_mut(node_id) @@ -95,7 +96,7 @@ impl FdtTree { self.fdt.add_node(parent, node) } - pub(crate) fn rebuild_memory_nodes(&mut self, regions: &[GuestMemorySpec]) -> AxResult { + pub(crate) fn rebuild_memory_nodes(&mut self, regions: &[GuestMemorySpec]) -> AxVmResult { let memory_paths = self .node_paths() .into_iter() @@ -124,7 +125,7 @@ impl FdtTree { Ok(()) } - pub(crate) fn patch_chosen(&mut self, initrd_start_size: Option<(u64, u64)>) -> AxResult { + pub(crate) fn patch_chosen(&mut self, initrd_start_size: Option<(u64, u64)>) -> AxVmResult { let chosen_id = self.ensure_path("/chosen")?; let chosen = self .fdt @@ -154,7 +155,7 @@ impl FdtTree { source_id: NodeId, dest_parent: NodeId, skip_cpu_cache_props: bool, - ) -> AxResult { + ) -> AxVmResult { let source_node = source .node(source_id) .ok_or_else(|| ax_err_type!(InvalidData, "source FDT node id is invalid"))?; @@ -175,7 +176,7 @@ impl FdtTree { pub(crate) fn clone_filtered( source: &Fdt, keep: impl Fn(NodeId, &str, &Node) -> bool, - ) -> AxResult { + ) -> AxVmResult { let mut dest = FdtTree::new(); dest.fdt.boot_cpuid_phys = source.boot_cpuid_phys; dest.fdt.memory_reservations = source.memory_reservations.clone(); diff --git a/virtualization/axvm/src/boot/fdt/mod.rs b/virtualization/axvm/src/boot/fdt/mod.rs index f3cc239b03..377cf6916e 100644 --- a/virtualization/axvm/src/boot/fdt/mod.rs +++ b/virtualization/axvm/src/boot/fdt/mod.rs @@ -50,7 +50,7 @@ fn test_runtime_patch( fdt: &[u8], _vm: &crate::AxVMRef, _config: &axvmconfig::AxVMCrateConfig, -) -> ax_errno::AxResult> { +) -> crate::AxVmResult> { Ok(fdt.to_vec()) } @@ -59,6 +59,6 @@ fn test_provided_patch( fdt: &[u8], _host_fdt: Option<&[u8]>, _config: &axvmconfig::AxVMCrateConfig, -) -> ax_errno::AxResult> { +) -> crate::AxVmResult> { Ok(fdt.to_vec()) } diff --git a/virtualization/axvm/src/boot/images/mod.rs b/virtualization/axvm/src/boot/images/mod.rs index 132250fb25..758fe14d91 100644 --- a/virtualization/axvm/src/boot/images/mod.rs +++ b/virtualization/axvm/src/boot/images/mod.rs @@ -2,12 +2,11 @@ use alloc::format; -use ax_errno::{AxResult, ax_err, ax_err_type}; use axvmconfig::AxVMCrateConfig; use byte_unit::Byte; use super::{BootImageProvider, StaticVmImage}; -use crate::{AxVMRef, GuestPhysAddr, VMMemoryRegion}; +use crate::{AxVMRef, AxVmError, AxVmResult, GuestPhysAddr, VMMemoryRegion, ax_err, ax_err_type}; mod linux; @@ -74,8 +73,11 @@ impl<'a> ImageLoaderCore<'a> { } } - pub(crate) fn load(&mut self) -> AxResult { - self.config.kernel.validate_boot_config()?; + pub(crate) fn load(&mut self) -> AxVmResult { + self.config + .kernel + .validate_boot_config() + .map_err(AxVmError::invalid_config)?; debug!( "Loading VM[{}] images into memory region: gpa={:#x}, hva={:#x}, size={:#}", self.vm.id(), @@ -102,8 +104,8 @@ impl<'a> ImageLoaderCore<'a> { pub(crate) fn load_standard_images_from_memory( &mut self, images: StaticVmImage, - load_guest_dtb: fn(&Self, &crate::boot::fdt::GuestDtbImage) -> AxResult, - ) -> AxResult { + load_guest_dtb: fn(&Self, &crate::boot::fdt::GuestDtbImage) -> AxVmResult, + ) -> AxVmResult { load_vm_image_from_memory(images.kernel, self.kernel_load_gpa, self.vm.clone())?; if let Some(ramdisk) = images.ramdisk { self.load_ramdisk_from_memory(ramdisk)?; @@ -117,8 +119,8 @@ impl<'a> ImageLoaderCore<'a> { #[cfg(any(feature = "fs", feature = "host-fs"))] pub(crate) fn load_standard_images_from_filesystem( &mut self, - load_guest_dtb: fn(&Self, &crate::boot::fdt::GuestDtbImage) -> AxResult, - ) -> AxResult { + load_guest_dtb: fn(&Self, &crate::boot::fdt::GuestDtbImage) -> AxVmResult, + ) -> AxVmResult { fs::load_vm_image( &self.config.kernel.kernel_path, self.kernel_load_gpa, @@ -135,7 +137,7 @@ impl<'a> ImageLoaderCore<'a> { Ok(()) } - pub(crate) fn load_ramdisk_from_memory(&self, ramdisk: &[u8]) -> AxResult { + pub(crate) fn load_ramdisk_from_memory(&self, ramdisk: &[u8]) -> AxVmResult { let load_gpa = self.ramdisk_load_gpa()?; self.record_ramdisk_size(ramdisk.len()); info!( @@ -146,7 +148,7 @@ impl<'a> ImageLoaderCore<'a> { load_vm_image_from_memory(ramdisk, load_gpa, self.vm.clone()) } - pub(crate) fn ramdisk_load_gpa(&self) -> AxResult { + pub(crate) fn ramdisk_load_gpa(&self) -> AxVmResult { self.ramdisk_load_gpa .ok_or_else(|| ax_err_type!(NotFound, "Ramdisk load addr is missed")) } @@ -159,7 +161,7 @@ impl<'a> ImageLoaderCore<'a> { }); } - fn load_boot_image_from_memory(&self, bios: Option<&[u8]>) -> AxResult { + fn load_boot_image_from_memory(&self, bios: Option<&[u8]>) -> AxVmResult { if !self.config.kernel.enable_bios { return Ok(()); } @@ -173,7 +175,7 @@ impl<'a> ImageLoaderCore<'a> { } #[cfg(any(feature = "fs", feature = "host-fs"))] - fn load_boot_image_from_filesystem(&self) -> AxResult { + fn load_boot_image_from_filesystem(&self) -> AxVmResult { if !self.config.kernel.enable_bios { return Ok(()); } @@ -187,7 +189,7 @@ impl<'a> ImageLoaderCore<'a> { } #[cfg(any(feature = "fs", feature = "host-fs"))] - pub(crate) fn load_ramdisk_from_filesystem(&self, ramdisk_path: &str) -> AxResult { + pub(crate) fn load_ramdisk_from_filesystem(&self, ramdisk_path: &str) -> AxVmResult { let load_gpa = self.ramdisk_load_gpa()?; let ramdisk_size = fs::image_size(ramdisk_path, self.provider)?; self.record_ramdisk_size(ramdisk_size); @@ -227,7 +229,7 @@ where fn memory_images_for_vm( config: &AxVMCrateConfig, provider: &dyn BootImageProvider, -) -> AxResult { +) -> AxVmResult { provider .static_vm_images() .iter() @@ -245,7 +247,7 @@ pub fn load_vm_image_from_memory( image_buffer: &[u8], load_addr: GuestPhysAddr, vm: AxVMRef, -) -> AxResult { +) -> AxVmResult { let mut buffer_pos = 0; let image_size = image_buffer.len(); let image_load_regions = vm.get_image_load_region(load_addr, image_size)?; @@ -282,16 +284,15 @@ pub fn load_vm_image_from_memory( pub mod fs { use alloc::{format, vec::Vec}; - use ax_errno::{AxResult, ax_err_type}; use axvmconfig::AxVMCrateConfig; - use crate::{AxVMRef, GuestPhysAddr, boot::BootImageProvider}; + use crate::{AxVMRef, AxVmResult, GuestPhysAddr, ax_err_type, boot::BootImageProvider}; pub fn kernel_read( config: &AxVMCrateConfig, provider: &dyn BootImageProvider, read_size: usize, - ) -> AxResult> { + ) -> AxVmResult> { provider.read_file_exact(&config.kernel.kernel_path, read_size) } @@ -300,7 +301,7 @@ pub mod fs { image_load_gpa: GuestPhysAddr, vm: AxVMRef, provider: &dyn BootImageProvider, - ) -> AxResult { + ) -> AxVmResult { let image = provider.read_file(image_path)?; let image_load_regions = vm.get_image_load_region(image_load_gpa, image.len())?; let mut offset = 0; @@ -319,11 +320,14 @@ pub mod fs { Ok(()) } - pub fn image_size(file_name: &str, provider: &dyn BootImageProvider) -> AxResult { + pub fn image_size(file_name: &str, provider: &dyn BootImageProvider) -> AxVmResult { provider.file_size(file_name) } - pub fn read_full_image(file_name: &str, provider: &dyn BootImageProvider) -> AxResult> { + pub fn read_full_image( + file_name: &str, + provider: &dyn BootImageProvider, + ) -> AxVmResult> { provider.read_file(file_name) } } diff --git a/virtualization/axvm/src/boot/mod.rs b/virtualization/axvm/src/boot/mod.rs index 67379dc316..afc6fd4a62 100644 --- a/virtualization/axvm/src/boot/mod.rs +++ b/virtualization/axvm/src/boot/mod.rs @@ -1,7 +1,7 @@ //! Guest boot image and platform planning. #[cfg(any(feature = "fs", feature = "host-fs"))] -use ax_errno::ax_err_type; +use crate::ax_err_type; pub mod fdt; pub mod guest_platform; @@ -43,14 +43,14 @@ pub trait BootImageProvider { } #[cfg(any(feature = "fs", feature = "host-fs"))] - fn read_file(&self, file_name: &str) -> ax_errno::AxResult>; + fn read_file(&self, file_name: &str) -> crate::AxVmResult>; #[cfg(any(feature = "fs", feature = "host-fs"))] fn read_file_exact( &self, file_name: &str, read_size: usize, - ) -> ax_errno::AxResult> { + ) -> crate::AxVmResult> { let buffer = self.read_file(file_name)?; if buffer.len() < read_size { return Err(ax_err_type!( @@ -62,7 +62,7 @@ pub trait BootImageProvider { } #[cfg(any(feature = "fs", feature = "host-fs"))] - fn file_size(&self, file_name: &str) -> ax_errno::AxResult { + fn file_size(&self, file_name: &str) -> crate::AxVmResult { self.read_file(file_name).map(|buffer| buffer.len()) } } diff --git a/virtualization/axvm/src/boot/prepared.rs b/virtualization/axvm/src/boot/prepared.rs index ca34bfd866..0804bd9207 100644 --- a/virtualization/axvm/src/boot/prepared.rs +++ b/virtualization/axvm/src/boot/prepared.rs @@ -1,10 +1,9 @@ //! Typed guest boot preparation shared by monitor integrations. -use ax_errno::AxResult; use axvmconfig::AxVMCrateConfig; use super::{BootImageProvider, fdt::GuestDtbImage, images::ImageLoaderCore}; -use crate::{AxVMRef, VMMemoryRegion, config::AxVMConfig}; +use crate::{AxVMRef, AxVmResult, VMMemoryRegion, config::AxVMConfig}; /// Architecture-prepared VM configuration and optional guest DTB. #[derive(Debug)] @@ -30,7 +29,7 @@ impl PreparedGuestBoot { main_memory: VMMemoryRegion, vm: AxVMRef, provider: &dyn BootImageProvider, - ) -> AxResult { + ) -> AxVmResult { let mut loader = ImageLoaderCore::new(main_memory, self.config, vm, provider, self.guest_dtb); loader.load() @@ -47,7 +46,7 @@ pub fn prepare_guest_boot( vm_config: &mut AxVMConfig, mut config: AxVMCrateConfig, provider: &dyn BootImageProvider, -) -> AxResult { +) -> AxVmResult { let guest_dtb = crate::arch::prepare_guest_boot(vm_config, &mut config, provider)?; Ok(PreparedGuestBoot { config, guest_dtb }) } diff --git a/virtualization/axvm/src/error.rs b/virtualization/axvm/src/error.rs new file mode 100644 index 0000000000..cc56f0bc9d --- /dev/null +++ b/virtualization/axvm/src/error.rs @@ -0,0 +1,288 @@ +//! AxVM-owned error contract. + +use alloc::{format, string::String}; +use core::fmt::Display; + +use crate::{VMId, VmStatus}; + +/// Result type returned by AxVM operations. +pub type AxVmResult = Result; + +/// Errors reported by AxVM to a hypervisor application. +#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)] +pub enum AxVmError { + /// The VM configuration is internally inconsistent or malformed. + #[error("invalid VM configuration: {detail}")] + InvalidConfig { detail: String }, + /// An operation received an invalid argument. + #[error("invalid input for {operation}: {detail}")] + InvalidInput { + operation: &'static str, + detail: String, + }, + /// Runtime state does not allow the requested operation. + #[error("invalid state for {operation}: {detail}")] + InvalidState { + operation: &'static str, + detail: String, + }, + /// A lifecycle transition is not valid. + #[error("invalid VM lifecycle transition during {operation}: {from:?} -> {to:?}")] + InvalidTransition { + from: VmStatus, + to: VmStatus, + operation: &'static str, + }, + /// No registered VM has the requested identifier. + #[error("VM {vm_id} was not found")] + VmNotFound { vm_id: VMId }, + /// A required VM resource is unavailable. + #[error("VM resource {resource} is unavailable: {detail}")] + ResourceUnavailable { + resource: &'static str, + detail: String, + }, + /// A VM resource conflicts with an existing resource. + #[error("VM resource {resource} conflicts: {detail}")] + ResourceConflict { + resource: &'static str, + detail: String, + }, + /// The requested operation is not implemented by this host or backend. + #[error("unsupported VM operation {operation}: {detail}")] + Unsupported { + operation: &'static str, + detail: String, + }, + /// Host memory allocation failed. + #[error("out of memory while {operation}")] + OutOfMemory { operation: &'static str }, + /// Guest boot preparation or image loading failed. + #[error("guest boot operation {operation} failed: {detail}")] + Boot { + operation: &'static str, + detail: String, + }, + /// Guest memory or nested-paging work failed. + #[error("VM memory operation {operation} failed: {detail}")] + Memory { + operation: &'static str, + detail: String, + }, + /// Virtual-device setup or emulation failed. + #[error("VM device operation {operation} failed: {detail}")] + Device { + operation: &'static str, + detail: String, + }, + /// A virtual CPU operation failed. + #[error("vCPU operation {operation} failed: {detail}")] + Vcpu { + operation: &'static str, + detail: String, + }, + /// Interrupt routing or injection failed. + #[error("VM interrupt operation {operation} failed: {detail}")] + Interrupt { + operation: &'static str, + detail: String, + }, + /// A host capability used by AxVM failed. + #[error("host operation {operation} failed: {detail}")] + Host { + operation: &'static str, + detail: String, + }, +} + +impl AxVmError { + pub(crate) const fn invalid_transition( + from: VmStatus, + to: VmStatus, + operation: &'static str, + ) -> Self { + Self::InvalidTransition { + from, + to, + operation, + } + } + + pub(crate) fn invalid_config(detail: impl Display) -> Self { + Self::InvalidConfig { + detail: format!("{detail}"), + } + } + + pub(crate) fn invalid_input(operation: &'static str, detail: impl Display) -> Self { + Self::InvalidInput { + operation, + detail: format!("{detail}"), + } + } + + pub(crate) fn invalid_state(operation: &'static str, detail: impl Display) -> Self { + Self::InvalidState { + operation, + detail: format!("{detail}"), + } + } + + pub(crate) fn resource_unavailable(resource: &'static str, detail: impl Display) -> Self { + Self::ResourceUnavailable { + resource, + detail: format!("{detail}"), + } + } + + pub(crate) fn resource_conflict(resource: &'static str, detail: impl Display) -> Self { + Self::ResourceConflict { + resource, + detail: format!("{detail}"), + } + } + + pub(crate) fn unsupported(operation: &'static str, detail: impl Display) -> Self { + Self::Unsupported { + operation, + detail: format!("{detail}"), + } + } + + pub(crate) fn memory(operation: &'static str, detail: impl Display) -> Self { + Self::Memory { + operation, + detail: format!("{detail}"), + } + } + + pub(crate) fn device(operation: &'static str, detail: impl Display) -> Self { + Self::Device { + operation, + detail: format!("{detail}"), + } + } + + pub(crate) fn vcpu(operation: &'static str, detail: impl Display) -> Self { + Self::Vcpu { + operation, + detail: format!("{detail}"), + } + } + + pub(crate) fn interrupt(operation: &'static str, detail: impl Display) -> Self { + Self::Interrupt { + operation, + detail: format!("{detail}"), + } + } + + pub(crate) fn host(operation: &'static str, detail: impl Display) -> Self { + Self::Host { + operation, + detail: format!("{detail}"), + } + } +} + +macro_rules! ax_err_type { + (InvalidInput $(, $detail:expr)?) => { + $crate::AxVmError::invalid_input(module_path!(), $crate::ax_err_type!(@detail $($detail)?)) + }; + (InvalidData $(, $detail:expr)?) => { + $crate::AxVmError::invalid_config($crate::ax_err_type!(@detail $($detail)?)) + }; + (BadState $(, $detail:expr)?) => { + $crate::AxVmError::invalid_state(module_path!(), $crate::ax_err_type!(@detail $($detail)?)) + }; + (NotFound $(, $detail:expr)?) => { + $crate::AxVmError::resource_unavailable("requested resource", $crate::ax_err_type!(@detail $($detail)?)) + }; + (AlreadyExists $(, $detail:expr)?) => { + $crate::AxVmError::resource_conflict("requested resource", $crate::ax_err_type!(@detail $($detail)?)) + }; + (AddrInUse $(, $detail:expr)?) => { + $crate::AxVmError::resource_conflict("address range", $crate::ax_err_type!(@detail $($detail)?)) + }; + (ResourceBusy $(, $detail:expr)?) => { + $crate::AxVmError::resource_conflict("requested resource", $crate::ax_err_type!(@detail $($detail)?)) + }; + (Unsupported $(, $detail:expr)?) => { + $crate::AxVmError::unsupported(module_path!(), $crate::ax_err_type!(@detail $($detail)?)) + }; + (NoMemory $(, $detail:expr)?) => { + $crate::AxVmError::OutOfMemory { operation: module_path!() } + }; + (Io $(, $detail:expr)?) => { + $crate::AxVmError::host(module_path!(), $crate::ax_err_type!(@detail $($detail)?)) + }; + (@detail $detail:expr) => { $detail }; + (@detail) => { "no additional detail" }; +} + +macro_rules! ax_err { + ($kind:ident $(, $detail:expr)? $(,)?) => { + Err($crate::ax_err_type!($kind $(, $detail)?)) + }; +} + +pub(crate) use ax_err; +pub(crate) use ax_err_type; + +#[cfg(test)] +mod tests { + use alloc::string::ToString; + + use super::*; + + #[test] + fn domain_errors_preserve_operation_context() { + let error = AxVmError::memory("map guest region", "address conflict"); + + assert!(matches!(error, AxVmError::Memory { .. })); + assert_eq!( + error.to_string(), + "VM memory operation map guest region failed: address conflict" + ); + } + + #[test] + fn lower_layer_failures_map_to_matching_domains() { + let cases = [ + AxVmError::memory("map stage-2 page", "invalid address"), + AxVmError::device("register UART", "address in use"), + AxVmError::vcpu("create vCPU", "backend rejected setup"), + AxVmError::interrupt("inject IRQ", "controller unavailable"), + AxVmError::host("enable virtualization", "unsupported CPU"), + ]; + + assert!(matches!(cases[0], AxVmError::Memory { .. })); + assert!(matches!(cases[1], AxVmError::Device { .. })); + assert!(matches!(cases[2], AxVmError::Vcpu { .. })); + assert!(matches!(cases[3], AxVmError::Interrupt { .. })); + assert!(matches!(cases[4], AxVmError::Host { .. })); + for error in cases { + let display = error.to_string(); + assert!(display.contains("operation")); + assert!(display.contains("failed")); + } + } + + #[test] + fn resource_and_capacity_errors_are_matchable() { + let conflict = AxVmError::resource_conflict("guest address range", "already mapped"); + let exhausted = AxVmError::OutOfMemory { + operation: "allocate guest RAM", + }; + + assert!(matches!(conflict, AxVmError::ResourceConflict { .. })); + assert_eq!( + conflict.to_string(), + "VM resource guest address range conflicts: already mapped" + ); + assert_eq!( + exhausted.to_string(), + "out of memory while allocate guest RAM" + ); + } +} diff --git a/virtualization/axvm/src/host/arceos.rs b/virtualization/axvm/src/host/arceos.rs index cb9069dd1e..c0d70cf909 100644 --- a/virtualization/axvm/src/host/arceos.rs +++ b/virtualization/axvm/src/host/arceos.rs @@ -7,7 +7,6 @@ use core::{ time::Duration, }; -use ax_errno::AxResult; use ax_memory_addr::PAGE_SIZE_4K; use ax_std::{ os::arceos::{api, modules}, @@ -15,7 +14,10 @@ use ax_std::{ }; use axvm_types::{HostPhysAddr, HostVirtAddr}; +#[cfg(any(feature = "fs", feature = "host-fs"))] +use crate::AxVmError; use crate::{ + AxVmResult, arch::{ArchOps, CurrentArch}, host::{HostCpu, HostMemory, HostPlatform, HostTime}, }; @@ -163,8 +165,9 @@ fn send_ipi_to_all_except_current(cpu_num: usize) { } #[cfg(any(feature = "fs", feature = "host-fs"))] -pub fn shutdown_host_filesystems() -> AxResult { - modules::ax_fs_ng::shutdown_filesystems()?; +pub fn shutdown_host_filesystems() -> AxVmResult { + modules::ax_fs_ng::shutdown_filesystems() + .map_err(|error| AxVmError::host("shut down host filesystems", error))?; let released = modules::ax_fs_ng::release_block_irqs_for_passthrough(); if released != 0 { info!("Released {released} host filesystem block IRQ registration(s) before passthrough"); @@ -177,7 +180,7 @@ impl HostPlatform for ArceOsHost { CurrentArch::has_hardware_support() } - fn enable_virtualization_on_current_cpu(&self) -> AxResult { + fn enable_virtualization_on_current_cpu(&self) -> AxVmResult { crate::timer::init_percpu(); crate::percpu::init_current_cpu()?; crate::percpu::enable_current_cpu()?; @@ -185,7 +188,7 @@ impl HostPlatform for ArceOsHost { Ok(()) } - fn enable_virtualization_on_all_cpus(&self) -> AxResult { + fn enable_virtualization_on_all_cpus(&self) -> AxVmResult { static CORES: AtomicUsize = AtomicUsize::new(0); info!("Enabling hardware virtualization support on all cores..."); diff --git a/virtualization/axvm/src/host/traits.rs b/virtualization/axvm/src/host/traits.rs index 34e9f2f687..35a737c26b 100644 --- a/virtualization/axvm/src/host/traits.rs +++ b/virtualization/axvm/src/host/traits.rs @@ -2,9 +2,10 @@ use core::time::Duration; -use ax_errno::AxResult; use axvm_types::{HostPhysAddr, HostVirtAddr}; +use crate::AxVmResult; + /// Host memory allocation and address translation. pub trait HostMemory { /// Allocate one 4 KiB host frame. @@ -57,8 +58,8 @@ pub trait HostPlatform { fn has_hardware_support(&self) -> bool; /// Enable virtualization on the current host CPU. - fn enable_virtualization_on_current_cpu(&self) -> AxResult; + fn enable_virtualization_on_current_cpu(&self) -> AxVmResult; /// Enable virtualization on every usable host CPU. - fn enable_virtualization_on_all_cpus(&self) -> AxResult; + fn enable_virtualization_on_all_cpus(&self) -> AxVmResult; } diff --git a/virtualization/axvm/src/irq/mod.rs b/virtualization/axvm/src/irq/mod.rs index 43abba80f4..fb4280d41c 100644 --- a/virtualization/axvm/src/irq/mod.rs +++ b/virtualization/axvm/src/irq/mod.rs @@ -16,10 +16,11 @@ use alloc::sync::Arc; -use ax_errno::{AxResult, ax_err}; use axdevice::IrqResolver; -use axdevice_base::{InterruptTriggerMode, IrqLine, IrqLineId, IrqSink}; -use axvm_types::VMInterruptMode; +use axdevice_base::{AxResult as DeviceResult, InterruptTriggerMode, IrqLine, IrqLineId, IrqSink}; +use axvm_types::{AxVmError as BackendError, VMInterruptMode}; + +use crate::{AxVmError, AxVmResult, ax_err}; /// Host platform hook for registering the RISC-V physical IRQ injector. #[ax_crate_interface::def_interface] @@ -69,7 +70,7 @@ impl InterruptFabric { } /// Creates a fabric that routes lines to `sink`. - pub fn with_sink(mode: VMInterruptMode, sink: Arc) -> AxResult { + pub fn with_sink(mode: VMInterruptMode, sink: Arc) -> AxVmResult { if mode == VMInterruptMode::NoIrq { return ax_err!( InvalidInput, @@ -92,34 +93,33 @@ impl InterruptFabric { self.sink.is_some() } - fn sink_for_line(&self, line: usize) -> AxResult<&Arc> { + fn sink_for_line(&self, _line: usize) -> DeviceResult<&Arc> { let Some(sink) = &self.sink else { if self.mode == VMInterruptMode::NoIrq { - return ax_err!( - InvalidInput, - format_args!("cannot signal IRQ line {line}: the VM interrupt mode is NoIrq") - ); + return Err(BackendError::InvalidInput); } - return ax_err!( - Unsupported, - format_args!("cannot signal IRQ line {line}: no VM interrupt backend is installed") - ); + return Err(BackendError::Unsupported); }; Ok(sink) } /// Sets the asserted state of a VM-local interrupt line. - pub fn set_level(&self, line: usize, asserted: bool) -> AxResult { - self.sink_for_line(line)? + pub fn set_level(&self, line: usize, asserted: bool) -> AxVmResult { + self.sink_for_line(line) + .map_err(|error| AxVmError::interrupt("resolve interrupt line", error))? .set_level(IrqLineId(line), asserted) + .map_err(|error| AxVmError::interrupt("set interrupt line level", error)) } /// Delivers one pulse on a VM-local interrupt line. - pub fn pulse(&self, line: usize) -> AxResult { - self.sink_for_line(line)?.pulse(IrqLineId(line)) + pub fn pulse(&self, line: usize) -> AxVmResult { + self.sink_for_line(line) + .map_err(|error| AxVmError::interrupt("resolve interrupt line", error))? + .pulse(IrqLineId(line)) + .map_err(|error| AxVmError::interrupt("pulse interrupt line", error)) } - pub(crate) fn validate_mode(&self, mode: VMInterruptMode) -> AxResult { + pub(crate) fn validate_mode(&self, mode: VMInterruptMode) -> AxVmResult { if self.mode != mode { return ax_err!( InvalidInput, @@ -140,7 +140,7 @@ impl Default for InterruptFabric { } impl IrqResolver for InterruptFabric { - fn resolve_irq(&self, line: usize, trigger: InterruptTriggerMode) -> AxResult { + fn resolve_irq(&self, line: usize, trigger: InterruptTriggerMode) -> DeviceResult { Ok(IrqLine::new( IrqLineId(line), trigger, diff --git a/virtualization/axvm/src/layout.rs b/virtualization/axvm/src/layout.rs index b0279f0c2f..67da52e62c 100644 --- a/virtualization/axvm/src/layout.rs +++ b/virtualization/axvm/src/layout.rs @@ -17,7 +17,6 @@ use alloc::{format, vec::Vec}; use core::cmp::{max, min}; -use ax_errno::{AxResult, ax_err_type}; use ax_memory_addr::{PAGE_SIZE_4K, align_down_4k}; use axdevice_base::Resource; use axvm_types::{ @@ -25,6 +24,8 @@ use axvm_types::{ PassThroughDeviceConfig, }; +use crate::{AxVmResult, ax_err_type}; + /// The ownership class of a guest physical range. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum VmRegionKind { @@ -217,7 +218,11 @@ pub struct GuestRegionPlanner { impl GuestRegionPlanner { /// Creates a planner for a guest physical address space. - pub fn new(policy: AddressSpacePolicy, guest_base: usize, guest_size: usize) -> AxResult { + pub fn new( + policy: AddressSpacePolicy, + guest_base: usize, + guest_size: usize, + ) -> AxVmResult { let guest_end = checked_end("guest address space", guest_base, guest_size)?; let windows = match policy { AddressSpacePolicy::Virtualized => Vec::new(), @@ -237,7 +242,7 @@ impl GuestRegionPlanner { } /// Reserves a VM-owned range and punches it out of passthrough windows. - pub fn reserve(&mut self, base: usize, length: usize, kind: VmRegionKind) -> AxResult { + pub fn reserve(&mut self, base: usize, length: usize, kind: VmRegionKind) -> AxVmResult { let (base, size) = normalize_guest_range(kind.name(), base, length)?; self.ensure_guest_range(kind.name(), base, size)?; let region = PlannedRegion { base, size, kind }; @@ -286,7 +291,7 @@ impl GuestRegionPlanner { base_gpa: usize, base_hpa: usize, length: usize, - ) -> AxResult { + ) -> AxVmResult { let (base_gpa, base_hpa, size) = normalize_linear_range("passthrough", base_gpa, base_hpa, length)?; self.ensure_guest_range("passthrough", base_gpa, size)?; @@ -348,12 +353,12 @@ impl GuestRegionPlanner { } /// Adds an explicit identity passthrough mapping. - pub fn add_identity_passthrough(&mut self, base_gpa: usize, length: usize) -> AxResult { + pub fn add_identity_passthrough(&mut self, base_gpa: usize, length: usize) -> AxVmResult { self.add_passthrough_mapping(base_gpa, base_gpa, length) } /// Finishes the layout and returns final stage-2 mappings. - pub fn finish(mut self) -> AxResult { + pub fn finish(mut self) -> AxVmResult { let mut mappings: Vec<_> = self .windows .drain(..) @@ -420,7 +425,7 @@ impl GuestRegionPlanner { self.windows = next_windows; } - fn ensure_guest_range(&self, name: &str, base: usize, size: usize) -> AxResult { + fn ensure_guest_range(&self, name: &str, base: usize, size: usize) -> AxVmResult { let end = checked_end(name, base, size)?; if base < self.guest_base || end > self.guest_end { return Err(ax_err_type!( @@ -444,7 +449,7 @@ pub(crate) fn build_address_layout( passthrough_addresses: &[PassThroughAddressConfig], owned_regions: &[GuestOwnedRegion], emulated_resources: &[Resource], -) -> AxResult { +) -> AxVmResult { let mut planner = GuestRegionPlanner::new(policy, guest_base, guest_size)?; for region in owned_regions { @@ -484,7 +489,7 @@ fn device_mapping_flags() -> MappingFlags { MappingFlags::DEVICE | MappingFlags::READ | MappingFlags::WRITE | MappingFlags::USER } -fn normalize_guest_range(name: &str, base: usize, length: usize) -> AxResult<(usize, usize)> { +fn normalize_guest_range(name: &str, base: usize, length: usize) -> AxVmResult<(usize, usize)> { let end = checked_end(name, base, length)?; let aligned_base = align_down_4k(base); let aligned_end = align_up_checked(end).ok_or_else(|| { @@ -501,7 +506,7 @@ fn normalize_linear_range( base_gpa: usize, base_hpa: usize, length: usize, -) -> AxResult<(usize, usize, usize)> { +) -> AxVmResult<(usize, usize, usize)> { let end_gpa = checked_end(name, base_gpa, length)?; checked_end(name, base_hpa, length)?; @@ -537,7 +542,7 @@ fn normalize_linear_range( Ok((aligned_gpa, aligned_hpa, aligned_size)) } -fn checked_end(name: &str, base: usize, length: usize) -> AxResult { +fn checked_end(name: &str, base: usize, length: usize) -> AxVmResult { if length == 0 { return Err(ax_err_type!( InvalidInput, @@ -575,7 +580,7 @@ fn same_linear_mapping(left: &VmStage2Mapping, right: &VmStage2Mapping) -> bool fn merge_linear_mappings( left: VmStage2Mapping, right: VmStage2Mapping, -) -> AxResult { +) -> AxVmResult { debug_assert!(same_linear_mapping(&left, &right)); let base_gpa = min(left.gpa.as_usize(), right.gpa.as_usize()); let end_gpa = max(left.gpa_end(), right.gpa_end()); diff --git a/virtualization/axvm/src/lib.rs b/virtualization/axvm/src/lib.rs index d1164e50d2..3fe64a03f8 100644 --- a/virtualization/axvm/src/lib.rs +++ b/virtualization/axvm/src/lib.rs @@ -26,6 +26,7 @@ extern crate log; mod arch; mod architecture; pub mod boot; +mod error; mod host; pub mod irq; pub mod layout; @@ -55,12 +56,14 @@ pub use axvm_types::{ AccessWidth, GuestPhysAddr, HostPhysAddr, InterruptTriggerMode, MappingFlags, Port, SysRegAddr, VMId, VmVcpuState, }; +pub use error::{AxVmError, AxVmResult}; +pub(crate) use error::{ax_err, ax_err_type}; pub(crate) use host::{ paging::HostPagingHandler, task::{AxTaskExt, AxTaskRef, TaskInner, WaitQueue, WaitQueueHandle as HostWaitQueueHandle}, }; pub use irq::InterruptFabric; -pub use lifecycle::{StopReason, VmLifecycleError, VmStatus}; +pub use lifecycle::{StopReason, VmStatus}; pub use manager::{ AxvmRuntime, current_vcpu_id, current_vm_id, get_vm_by_id, get_vm_list, inject_current_vcpu_interrupt, register_vm, diff --git a/virtualization/axvm/src/lifecycle/error.rs b/virtualization/axvm/src/lifecycle/error.rs deleted file mode 100644 index abcadd35a9..0000000000 --- a/virtualization/axvm/src/lifecycle/error.rs +++ /dev/null @@ -1,57 +0,0 @@ -use alloc::string::{String, ToString}; -use core::fmt; - -use ax_errno::{AxError, ax_err_type}; - -use super::{StopReason, VmStatus}; - -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum VmLifecycleError { - InvalidTransition { - from: VmStatus, - to: VmStatus, - op: &'static str, - }, - MissingResource(&'static str), - VcpuExit(String), - Destroy(String), -} - -pub type VmLifecycleResult = core::result::Result; - -impl VmLifecycleError { - pub fn invalid_transition(from: VmStatus, to: VmStatus, op: &'static str) -> Self { - Self::InvalidTransition { from, to, op } - } - - pub fn into_ax_error(self) -> AxError { - ax_err_type!(BadState, self.to_string()) - } -} - -impl fmt::Display for VmLifecycleError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - VmLifecycleError::InvalidTransition { from, to, op } => { - write!( - f, - "invalid VM lifecycle transition during {op}: {from:?} -> {to:?}" - ) - } - VmLifecycleError::MissingResource(name) => write!(f, "missing VM resource: {name}"), - VmLifecycleError::VcpuExit(err) => write!(f, "vCPU exited with error: {err}"), - VmLifecycleError::Destroy(err) => write!(f, "VM destroy failed: {err}"), - } - } -} - -impl From for VmLifecycleError { - fn from(reason: StopReason) -> Self { - match reason { - StopReason::Clean => VmLifecycleError::VcpuExit(String::from("clean stop")), - StopReason::SystemDown => VmLifecycleError::VcpuExit(String::from("guest system down")), - StopReason::Forced => VmLifecycleError::VcpuExit(String::from("forced stop")), - StopReason::Fault(message) => VmLifecycleError::VcpuExit(message), - } - } -} diff --git a/virtualization/axvm/src/lifecycle/machine.rs b/virtualization/axvm/src/lifecycle/machine.rs index 79e5819a79..dc626459d7 100644 --- a/virtualization/axvm/src/lifecycle/machine.rs +++ b/virtualization/axvm/src/lifecycle/machine.rs @@ -1,6 +1,7 @@ use alloc::string::{String, ToString}; -use super::{StopReason, VmLifecycleError, VmLifecycleResult, VmStatus}; +use super::{StopReason, VmStatus}; +use crate::{AxVmError, AxVmResult}; pub enum Machine { Ready(R), @@ -94,9 +95,9 @@ impl Machine { } } - pub fn start_with(&mut self, f: F) -> VmLifecycleResult + pub fn start_with(&mut self, f: F) -> AxVmResult where - F: FnOnce(&mut R) -> VmLifecycleResult, + F: FnOnce(&mut R) -> AxVmResult, { let old = core::mem::replace(self, Machine::Switching); match old { @@ -138,7 +139,7 @@ impl Machine { runtime: Some(runtime), reason, }; - Err(VmLifecycleError::invalid_transition( + Err(AxVmError::invalid_transition( VmStatus::Stopped, VmStatus::Running, "start", @@ -147,7 +148,7 @@ impl Machine { other => { let from = other.status(); *self = other; - Err(VmLifecycleError::invalid_transition( + Err(AxVmError::invalid_transition( from, VmStatus::Running, "start", @@ -156,7 +157,7 @@ impl Machine { } } - pub fn pause(&mut self) -> VmLifecycleResult { + pub fn pause(&mut self) -> AxVmResult { let old = core::mem::replace(self, Machine::Switching); match old { Machine::Running { resources, runtime } => { @@ -166,7 +167,7 @@ impl Machine { other => { let from = other.status(); *self = other; - Err(VmLifecycleError::invalid_transition( + Err(AxVmError::invalid_transition( from, VmStatus::Paused, "pause", @@ -175,7 +176,7 @@ impl Machine { } } - pub fn resume(&mut self) -> VmLifecycleResult { + pub fn resume(&mut self) -> AxVmResult { let old = core::mem::replace(self, Machine::Switching); match old { Machine::Paused { resources, runtime } => { @@ -185,7 +186,7 @@ impl Machine { other => { let from = other.status(); *self = other; - Err(VmLifecycleError::invalid_transition( + Err(AxVmError::invalid_transition( from, VmStatus::Running, "resume", @@ -194,9 +195,9 @@ impl Machine { } } - pub fn stop_with(&mut self, reason: StopReason, f: F) -> VmLifecycleResult + pub fn stop_with(&mut self, reason: StopReason, f: F) -> AxVmResult where - F: FnOnce(Option<&mut R>, &StopReason) -> VmLifecycleResult, + F: FnOnce(Option<&mut R>, &StopReason) -> AxVmResult, { let old = core::mem::replace(self, Machine::Switching); match old { @@ -215,7 +216,7 @@ impl Machine { } Machine::Running { resources, runtime } => { *self = Machine::Running { resources, runtime }; - Err(VmLifecycleError::invalid_transition( + Err(AxVmError::invalid_transition( VmStatus::Running, VmStatus::Stopped, "stop", @@ -223,7 +224,7 @@ impl Machine { } Machine::Pausing { resources, runtime } => { *self = Machine::Pausing { resources, runtime }; - Err(VmLifecycleError::invalid_transition( + Err(AxVmError::invalid_transition( VmStatus::Pausing, VmStatus::Stopped, "stop", @@ -231,7 +232,7 @@ impl Machine { } Machine::Paused { resources, runtime } => { *self = Machine::Paused { resources, runtime }; - Err(VmLifecycleError::invalid_transition( + Err(AxVmError::invalid_transition( VmStatus::Paused, VmStatus::Stopped, "stop", @@ -252,7 +253,7 @@ impl Machine { other => { let from = other.status(); *self = other; - Err(VmLifecycleError::invalid_transition( + Err(AxVmError::invalid_transition( from, VmStatus::Stopped, "stop", @@ -261,9 +262,9 @@ impl Machine { } } - pub fn request_stop_with(&mut self, reason: StopReason, f: F) -> VmLifecycleResult + pub fn request_stop_with(&mut self, reason: StopReason, f: F) -> AxVmResult where - F: FnOnce(Option<&mut R>, &StopReason) -> VmLifecycleResult, + F: FnOnce(Option<&mut R>, &StopReason) -> AxVmResult, { let old = core::mem::replace(self, Machine::Switching); match old { @@ -323,7 +324,7 @@ impl Machine { other => { let from = other.status(); *self = other; - Err(VmLifecycleError::invalid_transition( + Err(AxVmError::invalid_transition( from, VmStatus::Stopping, "request_stop", @@ -332,7 +333,7 @@ impl Machine { } } - pub fn finish_stop(&mut self) -> VmLifecycleResult { + pub fn finish_stop(&mut self) -> AxVmResult { let old = core::mem::replace(self, Machine::Switching); match old { Machine::Stopping { @@ -362,7 +363,7 @@ impl Machine { other => { let from = other.status(); *self = other; - Err(VmLifecycleError::invalid_transition( + Err(AxVmError::invalid_transition( from, VmStatus::Stopped, "finish_stop", @@ -378,9 +379,9 @@ impl Machine { } } - pub fn reset_with(&mut self, f: F) -> VmLifecycleResult + pub fn reset_with(&mut self, f: F) -> AxVmResult where - F: FnOnce(&mut R) -> VmLifecycleResult, + F: FnOnce(&mut R) -> AxVmResult, { let old = core::mem::replace(self, Machine::Switching); match old { @@ -408,7 +409,7 @@ impl Machine { runtime, reason, }; - Err(VmLifecycleError::invalid_transition( + Err(AxVmError::invalid_transition( VmStatus::Stopping, VmStatus::Ready, "reset", @@ -416,7 +417,7 @@ impl Machine { } Machine::Running { resources, runtime } => { *self = Machine::Running { resources, runtime }; - Err(VmLifecycleError::invalid_transition( + Err(AxVmError::invalid_transition( VmStatus::Running, VmStatus::Ready, "reset", @@ -424,7 +425,7 @@ impl Machine { } Machine::Paused { resources, runtime } => { *self = Machine::Paused { resources, runtime }; - Err(VmLifecycleError::invalid_transition( + Err(AxVmError::invalid_transition( VmStatus::Paused, VmStatus::Ready, "reset", @@ -440,7 +441,7 @@ impl Machine { runtime: Some(runtime), reason, }; - Err(VmLifecycleError::invalid_transition( + Err(AxVmError::invalid_transition( VmStatus::Stopped, VmStatus::Ready, "reset", @@ -449,7 +450,7 @@ impl Machine { other => { let from = other.status(); *self = other; - Err(VmLifecycleError::invalid_transition( + Err(AxVmError::invalid_transition( from, VmStatus::Ready, "reset", @@ -458,9 +459,9 @@ impl Machine { } } - pub fn destroy_with(&mut self, f: F) -> VmLifecycleResult + pub fn destroy_with(&mut self, f: F) -> AxVmResult where - F: FnOnce(Option) -> VmLifecycleResult, + F: FnOnce(Option) -> AxVmResult, { let old = core::mem::replace(self, Machine::Destroying); match old { @@ -475,7 +476,7 @@ impl Machine { } Machine::Running { resources, runtime } => { *self = Machine::Running { resources, runtime }; - Err(VmLifecycleError::invalid_transition( + Err(AxVmError::invalid_transition( VmStatus::Running, VmStatus::Destroyed, "destroy", @@ -483,7 +484,7 @@ impl Machine { } Machine::Pausing { resources, runtime } => { *self = Machine::Pausing { resources, runtime }; - Err(VmLifecycleError::invalid_transition( + Err(AxVmError::invalid_transition( VmStatus::Pausing, VmStatus::Destroyed, "destroy", @@ -491,7 +492,7 @@ impl Machine { } Machine::Paused { resources, runtime } => { *self = Machine::Paused { resources, runtime }; - Err(VmLifecycleError::invalid_transition( + Err(AxVmError::invalid_transition( VmStatus::Paused, VmStatus::Destroyed, "destroy", @@ -507,7 +508,7 @@ impl Machine { runtime, reason, }; - Err(VmLifecycleError::invalid_transition( + Err(AxVmError::invalid_transition( VmStatus::Stopping, VmStatus::Destroyed, "destroy", @@ -523,7 +524,7 @@ impl Machine { runtime: Some(runtime), reason, }; - Err(VmLifecycleError::invalid_transition( + Err(AxVmError::invalid_transition( VmStatus::Stopped, VmStatus::Destroyed, "destroy", @@ -595,10 +596,10 @@ mod tests { let err = machine.resume().unwrap_err(); assert!(matches!( err, - VmLifecycleError::InvalidTransition { + AxVmError::InvalidTransition { from: VmStatus::Ready, to: VmStatus::Running, - op: "resume" + operation: "resume" } )); assert_eq!(machine.status(), VmStatus::Ready); @@ -636,10 +637,10 @@ mod tests { assert!(matches!( err, - VmLifecycleError::InvalidTransition { + AxVmError::InvalidTransition { from: VmStatus::Running, to: VmStatus::Ready, - op: "reset" + operation: "reset" } )); assert_eq!(machine.status(), VmStatus::Running); @@ -656,10 +657,10 @@ mod tests { assert!(matches!( err, - VmLifecycleError::InvalidTransition { + AxVmError::InvalidTransition { from: VmStatus::Running, to: VmStatus::Destroyed, - op: "destroy" + operation: "destroy" } )); assert_eq!(machine.status(), VmStatus::Running); @@ -680,10 +681,10 @@ mod tests { assert!(matches!( err, - VmLifecycleError::InvalidTransition { + AxVmError::InvalidTransition { from: VmStatus::Stopped, to: VmStatus::Running, - op: "start" + operation: "start" } )); assert_eq!(machine.status(), VmStatus::Stopped); diff --git a/virtualization/axvm/src/lifecycle/mod.rs b/virtualization/axvm/src/lifecycle/mod.rs index 1f09d4755f..656b61596e 100644 --- a/virtualization/axvm/src/lifecycle/mod.rs +++ b/virtualization/axvm/src/lifecycle/mod.rs @@ -1,9 +1,7 @@ //! VM lifecycle state machine. -pub mod error; pub mod machine; pub mod status; -pub use error::{VmLifecycleError, VmLifecycleResult}; pub(crate) use machine::Machine; pub use status::{StopReason, VmStatus}; diff --git a/virtualization/axvm/src/manager.rs b/virtualization/axvm/src/manager.rs index 69ae065928..e42d5ebf99 100644 --- a/virtualization/axvm/src/manager.rs +++ b/virtualization/axvm/src/manager.rs @@ -4,12 +4,13 @@ extern crate alloc; use alloc::{collections::BTreeMap, vec::Vec}; -use ax_errno::{AxResult, ax_err, ax_err_type}; use ax_kspin::SpinNoIrq as Mutex; use axvm_types::VMId; use crate::{ + AxVmError, AxVmResult, arch::ArchVCpu, + ax_err, host::{HostPlatform, default_host}, vcpu::get_current_vcpu, vm::AxVMRef, @@ -75,7 +76,7 @@ pub(crate) fn active_vcpu_mask(vm_id: VMId) -> Option { } /// Inject a virtual interrupt into a VM's vCPU. -pub(crate) fn inject_interrupt(vm_id: VMId, vcpu_id: usize, vector: usize) -> AxResult { +pub(crate) fn inject_interrupt(vm_id: VMId, vcpu_id: usize, vector: usize) -> AxVmResult { crate::runtime::vcpus::queue_interrupt(vm_id, vcpu_id, vector) } @@ -84,7 +85,7 @@ pub(crate) fn inject_interrupt(vm_id: VMId, vcpu_id: usize, vector: usize) -> Ax dead_code, reason = "only the LoongArch IRQ backend injects external VM interrupts" )] -pub(crate) fn inject_vm_vcpu_interrupt(vm_id: VMId, vcpu_id: usize, vector: usize) -> AxResult { +pub(crate) fn inject_vm_vcpu_interrupt(vm_id: VMId, vcpu_id: usize, vector: usize) -> AxVmResult { use crate::AsVCpuTask; let current = crate::host::task::current_task(); @@ -109,15 +110,16 @@ pub fn current_vcpu_id() -> Option { } /// Inject a virtual interrupt into the vCPU currently executing on this CPU. -pub fn inject_current_vcpu_interrupt(vector: usize) -> AxResult { - let vcpu = get_current_vcpu::() - .ok_or_else(|| ax_err_type!(BadState, "current vCPU is not set"))?; +pub fn inject_current_vcpu_interrupt(vector: usize) -> AxVmResult { + let vcpu = get_current_vcpu::().ok_or_else(|| { + AxVmError::resource_unavailable("current vCPU", "current vCPU is not set") + })?; vcpu.inject_interrupt(vector) } impl AxvmRuntime { /// Create a new AxVM runtime backed by the default ArceOS host adapter. - pub fn new() -> AxResult { + pub fn new() -> AxVmResult { let host = default_host(); if !host.has_hardware_support() { return ax_err!(Unsupported, "hardware virtualization is not supported"); @@ -142,22 +144,22 @@ impl AxvmRuntime { } /// Start a VM selected from the runtime registry. - pub fn start_vm(vm_id: VMId) -> AxResult { + pub fn start_vm(vm_id: VMId) -> AxVmResult { crate::runtime::start_vm(vm_id) } /// Stop a VM selected from the runtime registry. - pub fn stop_vm(vm_id: VMId) -> AxResult { + pub fn stop_vm(vm_id: VMId) -> AxVmResult { crate::runtime::stop_vm(vm_id) } /// Resume a VM selected from the runtime registry. - pub fn resume_vm(vm_id: VMId) -> AxResult { + pub fn resume_vm(vm_id: VMId) -> AxVmResult { crate::runtime::resume_vm(vm_id) } /// Reset a VM selected from the runtime registry. - pub fn reset_vm(vm_id: VMId) -> AxResult { + pub fn reset_vm(vm_id: VMId) -> AxVmResult { crate::runtime::reset_vm(vm_id) } diff --git a/virtualization/axvm/src/npt.rs b/virtualization/axvm/src/npt.rs index d804377b03..2a7c7bfb20 100644 --- a/virtualization/axvm/src/npt.rs +++ b/virtualization/axvm/src/npt.rs @@ -14,14 +14,13 @@ use core::marker::PhantomData; -use ax_errno::{ax_err, ax_err_type}; use ax_memory_addr::{PhysAddr, VirtAddr}; use ax_memory_set::{MappingError, MappingResult}; use axaddrspace::{NestedPageTableOps, PageSize}; use axvm_types::{GuestPhysAddr, MappingFlags}; use page_table_generic as ptg; -use crate::host::PagingHandler; +use crate::{AxVmError, AxVmResult, ax_err, host::PagingHandler}; struct GenericFrameAllocator(PhantomData H>); @@ -197,7 +196,7 @@ where M4: ptg::TableMeta, H: PagingHandler + 'static, { - pub(crate) fn new(level: usize) -> ax_errno::AxResult { + pub(crate) fn new(level: usize) -> AxVmResult { match level { 3 => { if !SUPPORT_L3 { @@ -444,10 +443,12 @@ where } } -pub(crate) fn map_new_error(err: ptg::PagingError) -> ax_errno::AxError { +pub(crate) fn map_new_error(err: ptg::PagingError) -> AxVmError { match err { - ptg::PagingError::NoMemory => ax_err_type!(NoMemory), - _ => ax_err_type!(BadState), + ptg::PagingError::NoMemory => AxVmError::OutOfMemory { + operation: "allocate nested page table", + }, + _ => AxVmError::memory("create nested page table", err), } } diff --git a/virtualization/axvm/src/percpu.rs b/virtualization/axvm/src/percpu.rs index db61e127e2..261297765b 100644 --- a/virtualization/axvm/src/percpu.rs +++ b/virtualization/axvm/src/percpu.rs @@ -2,11 +2,10 @@ use core::sync::atomic::{AtomicUsize, Ordering}; -use ax_errno::AxResult; use axvm_types::VmArchPerCpuOps; use crate::{ - AxVMPerCpu, + AxVMPerCpu, AxVmResult, host::{HostCpu, default_host}, }; @@ -51,7 +50,7 @@ pub(crate) fn cpu_guest_phys_addr_bits(cpu_id: usize) -> Option { .filter(|bits| *bits != 0) } -pub(crate) fn init_current_cpu() -> AxResult { +pub(crate) fn init_current_cpu() -> AxVmResult { // SAFETY: Called once per CPU during hypervisor initialization before any // vCPU task uses this CPU-local virtualization state. #[allow(static_mut_refs)] @@ -59,7 +58,7 @@ pub(crate) fn init_current_cpu() -> AxResult { percpu.init(default_host().this_cpu_id()) } -pub(crate) fn enable_current_cpu() -> AxResult { +pub(crate) fn enable_current_cpu() -> AxVmResult { // SAFETY: The per-CPU value belongs to the currently pinned CPU. #[allow(static_mut_refs)] let percpu = unsafe { AXVM_PER_CPU.current_ref_mut_raw() }; diff --git a/virtualization/axvm/src/runtime/hvc.rs b/virtualization/axvm/src/runtime/hvc.rs index 979e00af3b..c6aefdabfe 100644 --- a/virtualization/axvm/src/runtime/hvc.rs +++ b/virtualization/axvm/src/runtime/hvc.rs @@ -12,11 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -use ax_errno::{AxResult, ax_err, ax_err_type}; -use axhvc::{HyperCallCode, HyperCallResult}; +use axhvc::HyperCallCode; use crate::{ - GuestPhysAddr, MappingFlags, + AxVmError, AxVmResult, GuestPhysAddr, MappingFlags, ax_err, ax_err_type, runtime::{ VMRef, ivc::{self, IVCChannel}, @@ -30,7 +29,7 @@ pub struct HyperCall { } impl HyperCall { - pub fn new(vm: VMRef, code: u64, args: [u64; 6]) -> AxResult { + pub fn new(vm: VMRef, code: u64, args: [u64; 6]) -> AxVmResult { let code = HyperCallCode::try_from(code as u32).map_err(|e| { warn!("Invalid hypercall code: {code} e {e:?}"); ax_err_type!(InvalidInput) @@ -39,7 +38,7 @@ impl HyperCall { Ok(Self { vm, code, args }) } - pub fn execute(&self) -> HyperCallResult { + pub fn execute(&self) -> AxVmResult { match self.code { HyperCallCode::HIVCPublishChannel => { let key = self.args[0] as usize; @@ -60,7 +59,9 @@ impl HyperCall { let (shm_base_gpa, shm_region_size) = self.vm.alloc_ivc_channel(requested_size)?; let ivc_channel = - match IVCChannel::alloc(self.vm.id(), key, shm_region_size, shm_base_gpa) { + match IVCChannel::alloc(self.vm.id(), key, shm_region_size, shm_base_gpa) + .map_err(|error| AxVmError::memory("allocate IVC channel", error)) + { Ok(channel) => channel, Err(err) => { if let Err(release_err) = diff --git a/virtualization/axvm/src/runtime/ivc.rs b/virtualization/axvm/src/runtime/ivc.rs index 4578991625..8e3d9cd40c 100644 --- a/virtualization/axvm/src/runtime/ivc.rs +++ b/virtualization/axvm/src/runtime/ivc.rs @@ -19,10 +19,9 @@ use alloc::{ vec::Vec, }; -use ax_errno::AxResult; use ax_kspin::SpinNoIrq as Mutex; -use crate::{GuestPhysAddr, HostPhysAddr, host::PagingHandler}; +use crate::{AxVmError, AxVmResult, GuestPhysAddr, HostPhysAddr, ax_err_type, host::PagingHandler}; /// A global btree map to store IVC channels, /// indexed by (publisher_vm_id, channel_key). @@ -32,7 +31,7 @@ static IVC_CHANNELS: Mutex> = Mutex::ne pub const MAX_IVC_CHANNEL_SIZE: usize = 4096; -pub fn insert_channel(publisher_vm_id: usize, channel: HostIVCChannel) -> AxResult<()> { +pub fn insert_channel(publisher_vm_id: usize, channel: HostIVCChannel) -> AxVmResult<()> { let mut channels = IVC_CHANNELS.lock(); let channel_key = (publisher_vm_id, channel.key); match channels.entry(channel_key) { @@ -40,16 +39,13 @@ pub fn insert_channel(publisher_vm_id: usize, channel: HostIVCChannel) -> AxResu entry.insert(channel); Ok(()) } - Entry::Occupied(_) => Err(ax_errno::ax_err_type!( - AlreadyExists, - "IVC channel already exists" - )), + Entry::Occupied(_) => Err(ax_err_type!(AlreadyExists, "IVC channel already exists")), } } -pub fn ensure_channel_absent(publisher_vm_id: usize, key: usize) -> AxResult<()> { +pub fn ensure_channel_absent(publisher_vm_id: usize, key: usize) -> AxVmResult<()> { if IVC_CHANNELS.lock().contains_key(&(publisher_vm_id, key)) { - Err(ax_errno::ax_err_type!( + Err(ax_err_type!( AlreadyExists, format!( "IVC channel for publisher VM {} with key {} already exists", @@ -66,11 +62,11 @@ pub fn ensure_channel_absent(publisher_vm_id: usize, key: usize) -> AxResult<()> /// (by setting its base GPA to None). /// If the channel is successfully unpublished, it will return the base GPA and size of the channel. /// If the channel does not exist, it will return an error. -pub fn unpublish_channel(publisher_vm_id: usize, key: usize) -> AxResult<(GuestPhysAddr, usize)> { +pub fn unpublish_channel(publisher_vm_id: usize, key: usize) -> AxVmResult<(GuestPhysAddr, usize)> { let mut channels = IVC_CHANNELS.lock(); let channel_key = (publisher_vm_id, key); let channel = channels.get_mut(&channel_key).ok_or_else(|| { - ax_errno::ax_err_type!( + ax_err_type!( NotFound, format!( "IVC channel for publisher VM {} with key {} not found", @@ -79,7 +75,7 @@ pub fn unpublish_channel(publisher_vm_id: usize, key: usize) -> AxResult<(GuestP ) })?; let base_gpa = channel.base_gpa_in_publisher().ok_or_else(|| { - ax_errno::ax_err_type!( + ax_err_type!( NotFound, format!( "IVC channel for publisher VM {} with key {} has no base GPA, it may have been \ @@ -103,10 +99,10 @@ pub fn prepare_subscribe_channel( publisher_vm_id: usize, key: usize, subscriber_vm_id: usize, -) -> AxResult { +) -> AxVmResult { let channels = IVC_CHANNELS.lock(); let channel = channels.get(&(publisher_vm_id, key)).ok_or_else(|| { - ax_errno::ax_err_type!( + ax_err_type!( NotFound, format!( "IVC channel for publisher VM {} with key {} not found", @@ -116,7 +112,7 @@ pub fn prepare_subscribe_channel( })?; if channel.is_unpublished() { - return Err(ax_errno::ax_err_type!( + return Err(ax_err_type!( NotFound, format!( "IVC channel for publisher VM {} with key {} has been unpublished", @@ -125,7 +121,7 @@ pub fn prepare_subscribe_channel( )); } if channel.has_subscriber(subscriber_vm_id) { - return Err(ax_errno::ax_err_type!( + return Err(ax_err_type!( AlreadyExists, format!( "VM[{}] has already subscribed to publisher VM[{}] Key {:#x}", @@ -144,10 +140,10 @@ pub fn subscribe_to_channel_of_publisher( key: usize, subscriber_vm_id: usize, subscriber_gpa: GuestPhysAddr, -) -> AxResult<(HostPhysAddr, usize)> { +) -> AxVmResult<(HostPhysAddr, usize)> { let mut channels = IVC_CHANNELS.lock(); let channel = channels.get_mut(&(publisher_vm_id, key)).ok_or_else(|| { - ax_errno::ax_err_type!( + ax_err_type!( NotFound, format!( "IVC channel for publisher VM [{}] key {:#x} not found", @@ -156,7 +152,7 @@ pub fn subscribe_to_channel_of_publisher( ) })?; if channel.is_unpublished() { - return Err(ax_errno::ax_err_type!( + return Err(ax_err_type!( NotFound, format!( "IVC channel for publisher VM [{}] key {:#x} has been unpublished", @@ -165,7 +161,7 @@ pub fn subscribe_to_channel_of_publisher( )); } if channel.has_subscriber(subscriber_vm_id) { - return Err(ax_errno::ax_err_type!( + return Err(ax_err_type!( AlreadyExists, format!( "VM[{}] has already subscribed to publisher VM[{}] Key {:#x}", @@ -185,14 +181,14 @@ pub fn unsubscribe_from_channel_of_publisher( publisher_vm_id: usize, key: usize, subscriber_vm_id: usize, -) -> AxResult<(GuestPhysAddr, usize)> { +) -> AxVmResult<(GuestPhysAddr, usize)> { let mut channels = IVC_CHANNELS.lock(); let (base_gpa, size) = if let Some(channel) = channels.get_mut(&(publisher_vm_id, key)) { // Remove the subscriber VM ID from the channel. if let Some(subscriber_gpa) = channel.remove_subscriber(subscriber_vm_id) { Ok((subscriber_gpa, channel.size())) } else { - Err(ax_errno::ax_err_type!( + Err(ax_err_type!( NotFound, format!( "VM[{}] tries to unsubscribe non-existed channel publisher VM[{}] Key {:#x}", @@ -201,7 +197,7 @@ pub fn unsubscribe_from_channel_of_publisher( )) } } else { - Err(ax_errno::ax_err_type!( + Err(ax_err_type!( NotFound, format!("IVC channel for publisher VM {} not found", publisher_vm_id) )) @@ -311,7 +307,7 @@ impl IVCChannel { key: usize, shared_region_size: usize, base_gpa: GuestPhysAddr, - ) -> AxResult { + ) -> AxVmResult { // TODO: support larger shared region sizes with alloc_frames API. if shared_region_size > MAX_IVC_CHANNEL_SIZE { warn!( @@ -320,8 +316,8 @@ impl IVCChannel { ); } let shared_region_size = shared_region_size.min(MAX_IVC_CHANNEL_SIZE); - let shared_region_base = H::alloc_frame().ok_or_else(|| { - ax_errno::ax_err_type!(NoMemory, "Failed to allocate shared region frame") + let shared_region_base = H::alloc_frame().ok_or(AxVmError::OutOfMemory { + operation: "allocate IVC shared region frame", })?; let mut channel = IVCChannel { diff --git a/virtualization/axvm/src/runtime/mod.rs b/virtualization/axvm/src/runtime/mod.rs index 1e30038dc8..8a9ea1f48b 100644 --- a/virtualization/axvm/src/runtime/mod.rs +++ b/virtualization/axvm/src/runtime/mod.rs @@ -18,9 +18,7 @@ pub(crate) mod vcpus; use core::sync::atomic::{AtomicUsize, Ordering}; -use ax_errno::{AxResult, ax_err, ax_err_type}; - -use crate::{StopReason, VmStatus}; +use crate::{AxVmError, AxVmResult, StopReason, VmStatus, ax_err}; /// The instantiated VM ref type (by `Arc`). pub type VMRef = crate::AxVMRef; @@ -78,8 +76,8 @@ fn reset_starts_counted_runtime(previous_status: VmStatus) -> bool { ) } -pub fn start_vm(vm_id: usize) -> AxResult { - let vm = crate::get_vm_by_id(vm_id).ok_or_else(|| ax_err_type!(NotFound, "VM not found"))?; +pub fn start_vm(vm_id: usize) -> AxVmResult { + let vm = vm_by_id(vm_id)?; let status = vm.status(); if !matches!(status, VmStatus::Ready | VmStatus::Stopped) { return ax_err!(BadState, "VM cannot be started from its current state"); @@ -91,22 +89,22 @@ pub fn start_vm(vm_id: usize) -> AxResult { Ok(()) } -pub fn stop_vm(vm_id: usize) -> AxResult { - let vm = crate::get_vm_by_id(vm_id).ok_or_else(|| ax_err_type!(NotFound, "VM not found"))?; +pub fn stop_vm(vm_id: usize) -> AxVmResult { + let vm = vm_by_id(vm_id)?; vm.stop(StopReason::Forced)?; vcpus::notify_all_vcpus(vm_id); Ok(()) } -pub fn resume_vm(vm_id: usize) -> AxResult { - let vm = crate::get_vm_by_id(vm_id).ok_or_else(|| ax_err_type!(NotFound, "VM not found"))?; +pub fn resume_vm(vm_id: usize) -> AxVmResult { + let vm = vm_by_id(vm_id)?; vm.resume()?; vcpus::notify_all_vcpus(vm_id); Ok(()) } -pub fn reset_vm(vm_id: usize) -> AxResult { - let vm = crate::get_vm_by_id(vm_id).ok_or_else(|| ax_err_type!(NotFound, "VM not found"))?; +pub fn reset_vm(vm_id: usize) -> AxVmResult { + let vm = vm_by_id(vm_id)?; let previous_status = vm.status(); vm.reset()?; if reset_starts_counted_runtime(previous_status) { @@ -125,6 +123,14 @@ pub fn register_vm(vm: VMRef) -> bool { crate::manager::push_existing_vm(vm) } +fn vm_by_id(vm_id: usize) -> AxVmResult { + crate::get_vm_by_id(vm_id).ok_or_else(|| missing_vm_error(vm_id)) +} + +const fn missing_vm_error(vm_id: usize) -> AxVmError { + AxVmError::VmNotFound { vm_id } +} + #[cfg(test)] mod tests { use super::*; @@ -144,4 +150,10 @@ mod tests { ); } } + + #[test] + fn missing_vm_is_reported_with_its_id() { + let vm_id = usize::MAX; + assert_eq!(missing_vm_error(vm_id), AxVmError::VmNotFound { vm_id }); + } } diff --git a/virtualization/axvm/src/runtime/vcpus.rs b/virtualization/axvm/src/runtime/vcpus.rs index dad9b3d24c..278c3696e3 100644 --- a/virtualization/axvm/src/runtime/vcpus.rs +++ b/virtualization/axvm/src/runtime/vcpus.rs @@ -14,11 +14,10 @@ use alloc::format; -use ax_errno::{AxResult, ax_err_type}; - use crate::{ - AsVCpuTask, GuestPhysAddr, StopReason, VCpuTask, VmStatus, VmVcpuState, + AsVCpuTask, AxVmResult, GuestPhysAddr, StopReason, VCpuTask, VmStatus, VmVcpuState, arch::{ArchOps, CurrentArch, VcpuRunAction}, + ax_err_type, runtime::{VCpuRef, VMRef, sub_running_vm_count}, vm::VmRuntimeHandle, }; @@ -84,7 +83,7 @@ pub(crate) fn notify_all_vcpus(vm_id: usize) { } } -pub(crate) fn queue_interrupt(vm_id: usize, vcpu_id: usize, vector: usize) -> AxResult { +pub(crate) fn queue_interrupt(vm_id: usize, vcpu_id: usize, vector: usize) -> AxVmResult { let vm = crate::get_vm_by_id(vm_id) .ok_or_else(|| ax_err_type!(NotFound, format!("VM[{vm_id}] not found")))?; if !matches!(vm.status(), VmStatus::Running | VmStatus::Paused) { @@ -112,7 +111,7 @@ pub(crate) fn queue_external_interrupt( vcpu_id: usize, vector: usize, physical_irq: usize, -) -> AxResult { +) -> AxVmResult { let vm = crate::get_vm_by_id(vm_id) .ok_or_else(|| ax_err_type!(NotFound, format!("VM[{vm_id}] not found")))?; if !matches!(vm.status(), VmStatus::Running | VmStatus::Paused) { @@ -200,7 +199,7 @@ pub(crate) fn vcpu_on( vcpu_id: usize, entry_point: GuestPhysAddr, arg: usize, -) -> AxResult { +) -> AxVmResult { let vcpu = vm .vcpu_list() .get(vcpu_id) diff --git a/virtualization/axvm/src/vcpu.rs b/virtualization/axvm/src/vcpu.rs index 626533f0c1..456343a780 100644 --- a/virtualization/axvm/src/vcpu.rs +++ b/virtualization/axvm/src/vcpu.rs @@ -17,12 +17,13 @@ use alloc::format; use core::{cell::UnsafeCell, mem::MaybeUninit}; -use ax_errno::{AxResult, ax_err}; use ax_kspin::SpinNoIrq as Mutex; use axvm_types::{ GuestPhysAddr, NestedPagingConfig, VCpuId, VMId, VmArchPerCpuOps, VmArchVcpuOps, VmVcpuState, }; +use crate::{AxVmError, AxVmResult, ax_err}; + /// Mutable runtime state of a virtual CPU. pub struct AxVCpuInnerMut { state: VmVcpuState, @@ -48,7 +49,7 @@ impl AxVCpu { vcpu_id: VCpuId, phys_cpu_set: Option, arch_config: A::CreateConfig, - ) -> AxResult { + ) -> AxVmResult { Ok(Self { inner_const: AxVCpuInnerConst { vm_id, @@ -58,7 +59,10 @@ impl AxVCpu { inner_mut: Mutex::new(AxVCpuInnerMut { state: VmVcpuState::Created, }), - arch_vcpu: UnsafeCell::new(A::new(vm_id, vcpu_id, arch_config)?), + arch_vcpu: UnsafeCell::new( + A::new(vm_id, vcpu_id, arch_config) + .map_err(|error| AxVmError::vcpu("create vCPU", error))?, + ), }) } @@ -68,11 +72,17 @@ impl AxVCpu { entry: GuestPhysAddr, nested_paging: NestedPagingConfig, arch_config: A::SetupConfig, - ) -> AxResult { + ) -> AxVmResult { self.manipulate_arch_vcpu(VmVcpuState::Created, VmVcpuState::Free, |arch_vcpu| { - arch_vcpu.set_entry(entry)?; - arch_vcpu.set_nested_page_table(nested_paging)?; - arch_vcpu.setup(arch_config)?; + arch_vcpu + .set_entry(entry) + .map_err(|error| AxVmError::vcpu("set vCPU entry", error))?; + arch_vcpu + .set_nested_page_table(nested_paging) + .map_err(|error| AxVmError::vcpu("set nested page table", error))?; + arch_vcpu + .setup(arch_config) + .map_err(|error| AxVmError::vcpu("set up vCPU", error))?; Ok(()) }) } @@ -103,9 +113,9 @@ impl AxVCpu { from: VmVcpuState, to: VmVcpuState, f: F, - ) -> AxResult + ) -> AxVmResult where - F: FnOnce() -> AxResult, + F: FnOnce() -> AxVmResult, { { let inner_mut = self.inner_mut.lock(); @@ -156,9 +166,9 @@ impl AxVCpu { from: VmVcpuState, to: VmVcpuState, f: F, - ) -> AxResult + ) -> AxVmResult where - F: FnOnce(&mut A) -> AxResult, + F: FnOnce(&mut A) -> AxVmResult, { self.with_state_transition(from, to, || { self.with_current_cpu_set(|| f(self.get_arch_vcpu())) @@ -166,7 +176,7 @@ impl AxVCpu { } /// Transitions the vCPU state without calling the architecture backend. - pub fn transition_state(&self, from: VmVcpuState, to: VmVcpuState) -> AxResult { + pub fn transition_state(&self, from: VmVcpuState, to: VmVcpuState) -> AxVmResult { self.with_state_transition(from, to, || Ok(())) } @@ -177,24 +187,30 @@ impl AxVCpu { } /// Runs the vCPU until a VM exit. - pub fn run(&self) -> AxResult { + pub fn run(&self) -> AxVmResult { self.transition_state(VmVcpuState::Ready, VmVcpuState::Running)?; self.manipulate_arch_vcpu(VmVcpuState::Running, VmVcpuState::Ready, |arch_vcpu| { - arch_vcpu.run() + arch_vcpu + .run() + .map_err(|error| AxVmError::vcpu("run vCPU", error)) }) } /// Binds the vCPU to the current physical CPU. - pub fn bind(&self) -> AxResult { + pub fn bind(&self) -> AxVmResult { self.manipulate_arch_vcpu(VmVcpuState::Free, VmVcpuState::Ready, |arch_vcpu| { - arch_vcpu.bind() + arch_vcpu + .bind() + .map_err(|error| AxVmError::vcpu("bind vCPU", error)) }) } /// Unbinds the vCPU from the current physical CPU. - pub fn unbind(&self) -> AxResult { + pub fn unbind(&self) -> AxVmResult { self.manipulate_arch_vcpu(VmVcpuState::Ready, VmVcpuState::Free, |arch_vcpu| { - arch_vcpu.unbind() + arch_vcpu + .unbind() + .map_err(|error| AxVmError::vcpu("unbind vCPU", error)) }) } @@ -203,8 +219,10 @@ impl AxVCpu { dead_code, reason = "only non-x86 guest firmware updates secondary vCPU entries" )] - pub fn set_entry(&self, entry: GuestPhysAddr) -> AxResult { - self.get_arch_vcpu().set_entry(entry) + pub fn set_entry(&self, entry: GuestPhysAddr) -> AxVmResult { + self.get_arch_vcpu() + .set_entry(entry) + .map_err(|error| AxVmError::vcpu("set vCPU entry", error)) } /// Sets a guest general-purpose register. @@ -213,8 +231,10 @@ impl AxVCpu { } /// Injects an interrupt into the vCPU. - pub fn inject_interrupt(&self, vector: usize) -> AxResult { - self.get_arch_vcpu().inject_interrupt(vector) + pub fn inject_interrupt(&self, vector: usize) -> AxVmResult { + self.get_arch_vcpu() + .inject_interrupt(vector) + .map_err(|error| AxVmError::interrupt("inject vCPU interrupt", error)) } /// Sets the guest return value. @@ -280,12 +300,15 @@ impl AxPerCpu { } /// Initializes this per-CPU state. - pub fn init(&mut self, cpu_id: usize) -> AxResult { + pub fn init(&mut self, cpu_id: usize) -> AxVmResult { if self.cpu_id.is_some() { ax_err!(BadState, "per-CPU state is already initialized") } else { self.cpu_id = Some(cpu_id); - self.arch.write(A::new(cpu_id)?); + self.arch + .write(A::new(cpu_id).map_err(|error| { + AxVmError::host("initialize per-CPU virtualization", error) + })?); Ok(()) } } @@ -308,13 +331,17 @@ impl AxPerCpu { } /// Enables virtualization on the current CPU. - pub fn hardware_enable(&mut self) -> AxResult { - self.arch_checked_mut().hardware_enable() + pub fn hardware_enable(&mut self) -> AxVmResult { + self.arch_checked_mut() + .hardware_enable() + .map_err(|error| AxVmError::host("enable hardware virtualization", error)) } /// Disables virtualization on the current CPU. - pub fn hardware_disable(&mut self) -> AxResult { - self.arch_checked_mut().hardware_disable() + pub fn hardware_disable(&mut self) -> AxVmResult { + self.arch_checked_mut() + .hardware_disable() + .map_err(|error| AxVmError::host("disable hardware virtualization", error)) } } diff --git a/virtualization/axvm/src/vm/memory.rs b/virtualization/axvm/src/vm/memory.rs index 476a0da77e..770c666eea 100644 --- a/virtualization/axvm/src/vm/memory.rs +++ b/virtualization/axvm/src/vm/memory.rs @@ -3,10 +3,10 @@ use alloc::vec::Vec; use core::alloc::Layout; -use ax_errno::{AxResult, ax_err_type}; use axvm_types::{GuestPhysAddr, VmMemConfig, VmMemMappingType}; use super::{AxVM, VMMemoryRegion}; +use crate::{AxVmResult, ax_err_type}; const VM_MEMORY_ALIGN: usize = 2 * 1024 * 1024; @@ -18,7 +18,7 @@ pub struct PreparedMemoryLayout { } impl PreparedMemoryLayout { - fn new(regions: Vec) -> AxResult { + fn new(regions: Vec) -> AxVmResult { let main_memory = regions .first() .cloned() @@ -42,8 +42,8 @@ impl PreparedMemoryLayout { pub(crate) trait MemoryRegionMapper { fn prepared_memory_regions(&self) -> Vec; - fn allocate_memory_region(&self, layout: Layout, gpa: Option) -> AxResult<()>; - fn map_reserved_memory_region(&self, layout: Layout, gpa: Option) -> AxResult; + fn allocate_memory_region(&self, layout: Layout, gpa: Option) -> AxVmResult<()>; + fn map_reserved_memory_region(&self, layout: Layout, gpa: Option) -> AxVmResult; } impl MemoryRegionMapper for AxVM { @@ -51,11 +51,11 @@ impl MemoryRegionMapper for AxVM { self.memory_regions() } - fn allocate_memory_region(&self, layout: Layout, gpa: Option) -> AxResult<()> { + fn allocate_memory_region(&self, layout: Layout, gpa: Option) -> AxVmResult<()> { self.alloc_memory_region(layout, gpa).map(|_| ()) } - fn map_reserved_memory_region(&self, layout: Layout, gpa: Option) -> AxResult { + fn map_reserved_memory_region(&self, layout: Layout, gpa: Option) -> AxVmResult { self.map_reserved_memory_region(layout, gpa) } } @@ -70,7 +70,7 @@ impl<'a, M: MemoryRegionMapper + ?Sized> MemoryLayoutBuilder<'a, M> { Self { mapper, configs } } - pub(crate) fn prepare(&self) -> AxResult { + pub(crate) fn prepare(&self) -> AxVmResult { let existing = self.mapper.prepared_memory_regions(); if !existing.is_empty() { return PreparedMemoryLayout::new(existing); @@ -100,7 +100,7 @@ pub(crate) struct MemoryRegionPlan { } impl MemoryRegionPlan { - pub(crate) fn from_config(config: &VmMemConfig) -> AxResult { + pub(crate) fn from_config(config: &VmMemConfig) -> AxVmResult { let layout = Layout::from_size_align(config.size, VM_MEMORY_ALIGN).map_err(|err| { ax_err_type!( InvalidInput, @@ -155,7 +155,7 @@ mod tests { &self, layout: Layout, gpa: Option, - ) -> AxResult<()> { + ) -> AxVmResult<()> { let gpa = gpa.unwrap_or_else(|| GuestPhysAddr::from(0x5000_0000)); self.regions.borrow_mut().push(VMMemoryRegion { gpa, @@ -170,7 +170,7 @@ mod tests { &self, layout: Layout, gpa: Option, - ) -> AxResult { + ) -> AxVmResult { let gpa = gpa.ok_or_else(|| ax_err_type!(InvalidInput, "reserved GPA is required"))?; self.map_reserved_calls .set(self.map_reserved_calls.get() + 1); diff --git a/virtualization/axvm/src/vm/mod.rs b/virtualization/axvm/src/vm/mod.rs index c193bdc94b..417d588618 100644 --- a/virtualization/axvm/src/vm/mod.rs +++ b/virtualization/axvm/src/vm/mod.rs @@ -21,24 +21,26 @@ use core::{ }; use ax_cpumask::CpuMask; -use ax_errno::{AxError, AxResult, ax_err, ax_err_type}; use ax_kspin::SpinNoIrq as Mutex; use ax_memory_addr::align_up_4k; use axaddrspace::AddrSpace; use axdevice::{AxVmDevices, FwCfg, FwCfgPlatformConfig}; use axdevice_base::AccessWidth; use axvm_types::{ - GuestPhysAddr, HostPhysAddr, HostVirtAddr, MappingFlags, NestedPagingConfig, VmVcpuState, + AxVmError as BackendError, GuestPhysAddr, HostPhysAddr, HostVirtAddr, MappingFlags, + NestedPagingConfig, VmVcpuState, }; use crate::{ + AxVmError, AxVmResult, arch::ArchNestedPageTable, + ax_err, ax_err_type, boot::{GuestBootDescription, GuestFdtBuilder}, config::{AxVMConfig, PhysCpuList, VMInterruptMode}, host::paging::virt_to_phys, irq::InterruptFabric, layout::VmAddressLayout, - lifecycle::{Machine, StopReason, VmLifecycleError, VmStatus}, + lifecycle::{Machine, StopReason, VmStatus}, vcpu::AxVCpu, }; @@ -86,7 +88,7 @@ pub(crate) fn sign_extend_value(value: usize, width: AccessWidth) -> usize { } } -fn write_guest_bytes_to_chunks(chunks: &mut [&mut [u8]], data: &[u8]) -> AxResult { +fn write_guest_bytes_to_chunks(chunks: &mut [&mut [u8]], data: &[u8]) -> AxVmResult { if data.is_empty() { return Ok(()); } @@ -158,7 +160,7 @@ impl VmRuntimeHandle { self.pending_interrupts.lock().entry(vcpu_id).or_default(); } - pub(crate) fn queue_interrupt(&self, vcpu_id: usize, vector: usize) -> AxResult { + pub(crate) fn queue_interrupt(&self, vcpu_id: usize, vector: usize) -> AxVmResult { let task = self .vcpu_task_list .lock() @@ -182,7 +184,7 @@ impl VmRuntimeHandle { vcpu_id: usize, vector: usize, physical_irq: usize, - ) -> AxResult { + ) -> AxVmResult { let task = self .vcpu_task_list .lock() @@ -266,13 +268,14 @@ impl AxVMResources { pub(crate) fn from_page_table( config: AxVMConfig, page_table: ArchNestedPageTable, - build_nested_paging: impl FnOnce(HostPhysAddr) -> AxResult, - ) -> AxResult { + build_nested_paging: impl FnOnce(HostPhysAddr) -> AxVmResult, + ) -> AxVmResult { let address_space = AddrSpace::new_empty( page_table, GuestPhysAddr::from(VM_ASPACE_BASE), VM_ASPACE_SIZE, - )?; + ) + .map_err(|error| AxVmError::memory("create guest address space", error))?; let nested_paging = build_nested_paging(address_space.page_table_root())?; Ok(Self { address_space, @@ -292,37 +295,39 @@ impl AxVMResources { &self.config } - fn vcpu_list(&self) -> AxResult<&[AxVCpuRef]> { + fn vcpu_list(&self) -> AxVmResult<&[AxVCpuRef]> { self.vcpu_list .as_deref() .ok_or_else(|| ax_err_type!(BadState, "VM vCPU resources are not prepared")) } - fn devices(&self) -> AxResult> { + fn devices(&self) -> AxVmResult> { self.devices .clone() .ok_or_else(|| ax_err_type!(BadState, "VM devices are not prepared")) } - fn interrupt_fabric(&self) -> AxResult<&InterruptFabric> { + fn interrupt_fabric(&self) -> AxVmResult<&InterruptFabric> { self.interrupt_fabric .as_ref() .ok_or_else(|| ax_err_type!(BadState, "VM interrupt fabric is not prepared")) } - fn reset_transient_resources(&mut self) -> AxResult { + fn reset_transient_resources(&mut self) -> AxVmResult { let memory_regions = self.memory_regions.clone(); self.address_space.clear(); for region in &memory_regions { - self.address_space.map_linear( - region.gpa, - region.host_paddr(), - region.size(), - MappingFlags::READ - | MappingFlags::WRITE - | MappingFlags::EXECUTE - | MappingFlags::USER, - )?; + self.address_space + .map_linear( + region.gpa, + region.host_paddr(), + region.size(), + MappingFlags::READ + | MappingFlags::WRITE + | MappingFlags::EXECUTE + | MappingFlags::USER, + ) + .map_err(|error| AxVmError::memory("restore guest memory mapping", error))?; } self.vcpu_list = None; self.devices = None; @@ -401,7 +406,7 @@ impl AxVM { /// /// Returns an error if nested paging is unsupported for the selected host /// CPUs or if the initial stage-2 address space cannot be allocated. - pub fn new(config: AxVMConfig) -> AxResult { + pub fn new(config: AxVMConfig) -> AxVmResult { let id = config.id(); let name = config.name(); let resources = crate::arch::CurrentArch::create_vm_resources(config)?; @@ -439,9 +444,9 @@ impl AxVM { .unwrap_or(VMInterruptMode::NoIrq) } - fn with_resources(&self, f: F) -> AxResult + fn with_resources(&self, f: F) -> AxVmResult where - F: FnOnce(&AxVMResources) -> AxResult, + F: FnOnce(&AxVMResources) -> AxVmResult, { let machine = self.machine.lock(); let resources = machine @@ -450,9 +455,9 @@ impl AxVM { f(resources) } - fn with_resources_mut(&self, f: F) -> AxResult + fn with_resources_mut(&self, f: F) -> AxVmResult where - F: FnOnce(&mut AxVMResources) -> AxResult, + F: FnOnce(&mut AxVMResources) -> AxVmResult, { let mut machine = self.machine.lock(); let resources = machine @@ -461,9 +466,9 @@ impl AxVM { f(resources) } - pub(crate) fn with_runtime(&self, f: F) -> AxResult + pub(crate) fn with_runtime(&self, f: F) -> AxVmResult where - F: FnOnce(&Arc) -> AxResult, + F: FnOnce(&Arc) -> AxVmResult, { let machine = self.machine.lock(); let runtime = machine @@ -510,7 +515,7 @@ impl AxVM { } /// Returns the root address of the nested page table for the VM. - pub fn nested_page_table_root(&self) -> AxResult { + pub fn nested_page_table_root(&self) -> AxVmResult { self.with_resources(|resources| Ok(resources.address_space.page_table_root())) } @@ -527,7 +532,7 @@ impl AxVM { } /// Stores a guest DTB as VM-owned boot-description state. - pub fn set_guest_device_tree(&self, load_gpa: GuestPhysAddr, bytes: Vec) -> AxResult { + pub fn set_guest_device_tree(&self, load_gpa: GuestPhysAddr, bytes: Vec) -> AxVmResult { self.with_resources_mut(|resources| { resources.config.set_dtb_load_gpa(load_gpa); resources @@ -549,7 +554,7 @@ impl AxVM { &self, image_load_gpa: GuestPhysAddr, image_size: usize, - ) -> AxResult> { + ) -> AxVmResult> { let image_load_hva = self.with_resources(|resources| { resources .address_space @@ -562,7 +567,7 @@ impl AxVM { } /// Starts the VM by transitioning to Running state. - pub fn start(self: &Arc) -> AxResult { + pub fn start(self: &Arc) -> AxVmResult { if self.status() == VmStatus::Stopped { if let Some(runtime) = self.take_stopped_runtime() { runtime.join_all_vcpu_tasks(self.id()); @@ -576,21 +581,18 @@ impl AxVM { let primary_task = crate::runtime::vcpus::build_vcpu_task(self, primary_vcpu); let runtime = Arc::new(VmRuntimeHandle::new()); - self.machine - .lock() - .start_with(|resources| { - resources - .vcpu_list() - .map_err(|_| VmLifecycleError::MissingResource("vCPU list"))?; - resources - .devices() - .map_err(|_| VmLifecycleError::MissingResource("devices"))?; - resources - .interrupt_fabric() - .map_err(|_| VmLifecycleError::MissingResource("interrupt fabric"))?; - Ok(runtime.clone()) - }) - .map_err(VmLifecycleError::into_ax_error)?; + self.machine.lock().start_with(|resources| { + resources + .vcpu_list() + .map_err(|error| AxVmError::resource_unavailable("vCPU list", error))?; + resources + .devices() + .map_err(|error| AxVmError::resource_unavailable("devices", error))?; + resources + .interrupt_fabric() + .map_err(|error| AxVmError::resource_unavailable("interrupt fabric", error))?; + Ok(runtime.clone()) + })?; let task = crate::host::task::spawn_task(primary_task); runtime.add_vcpu_task(0, task); @@ -618,38 +620,26 @@ impl AxVM { } /// Pauses a running VM. - pub fn pause(&self) -> AxResult { - self.machine - .lock() - .pause() - .map_err(VmLifecycleError::into_ax_error) + pub fn pause(&self) -> AxVmResult { + self.machine.lock().pause() } /// Resumes a paused VM. - pub fn resume(&self) -> AxResult { - self.machine - .lock() - .resume() - .map_err(VmLifecycleError::into_ax_error) + pub fn resume(&self) -> AxVmResult { + self.machine.lock().resume() } /// Requests a stop. Running vCPUs observe the Stopping state and exit. - pub fn stop(&self, reason: StopReason) -> AxResult { + pub fn stop(&self, reason: StopReason) -> AxVmResult { info!("Stopping VM[{}]: {reason:?}", self.id()); - self.machine - .lock() - .request_stop_with(reason, |_, _| Ok(())) - .map_err(VmLifecycleError::into_ax_error) + self.machine.lock().request_stop_with(reason, |_, _| Ok(())) } - pub(crate) fn finish_stop(&self) -> AxResult { - self.machine - .lock() - .finish_stop() - .map_err(VmLifecycleError::into_ax_error) + pub(crate) fn finish_stop(&self) -> AxVmResult { + self.machine.lock().finish_stop() } - fn wait_until_stopped(&self) -> AxResult { + fn wait_until_stopped(&self) -> AxVmResult { const MAX_YIELDS: usize = 10_000; for _ in 0..MAX_YIELDS { match self.status() { @@ -671,7 +661,7 @@ impl AxVM { ) } - fn stop_and_join_runtime(&self, reason: StopReason) -> AxResult { + fn stop_and_join_runtime(&self, reason: StopReason) -> AxVmResult { match self.status() { VmStatus::Running | VmStatus::Paused => { self.stop(reason)?; @@ -705,29 +695,26 @@ impl AxVM { /// Resets the VM by discarding runtime-only state, rebuilding vCPUs/devices, /// and starting from a fresh `Running` state. - pub fn reset(self: &Arc) -> AxResult { + pub fn reset(self: &Arc) -> AxVmResult { info!("Resetting VM[{}]", self.id()); self.stop_and_join_runtime(StopReason::Forced)?; - self.machine - .lock() - .reset_with(|resources| { - resources - .reset_transient_resources() - .map_err(|_| VmLifecycleError::MissingResource("reset resources")) - }) - .map_err(VmLifecycleError::into_ax_error)?; + self.machine.lock().reset_with(|resources| { + resources + .reset_transient_resources() + .map_err(|error| AxVmError::resource_unavailable("reset resources", error)) + })?; self.prepare()?; self.start() } /// Returns this VM's emulated devices. - pub fn get_devices(&self) -> AxResult> { + pub fn get_devices(&self) -> AxVmResult> { self.with_resources(|resources| resources.devices()) } /// Pulses a prepared VM interrupt fabric line without exposing the fabric. - pub fn pulse_interrupt(&self, irq_id: usize) -> AxResult { + pub fn pulse_interrupt(&self, irq_id: usize) -> AxVmResult { match self.status() { VmStatus::Running | VmStatus::Paused => { self.with_resources(|resources| resources.interrupt_fabric()?.pulse(irq_id)) @@ -747,7 +734,7 @@ impl AxVM { } /// Queue a QEMU fw_cfg device that will be attached during VM initialization. - pub fn add_fw_cfg_device(&self, config: FwCfgDeviceConfig) -> AxResult { + pub fn add_fw_cfg_device(&self, config: FwCfgDeviceConfig) -> AxVmResult { let mut pending = self.pending_fw_cfg.lock(); if pending.is_some() { return ax_err!( @@ -775,7 +762,7 @@ impl AxVM { Ok(()) } - fn add_special_emulated_devices(&self, devices: &mut AxVmDevices) -> AxResult { + fn add_special_emulated_devices(&self, devices: &mut AxVmDevices) -> AxVmResult { if let Some(pending) = self.pending_fw_cfg.lock().take() { debug!( "VM[{}] adding fw_cfg MMIO device at [{:#x},{:#x})", @@ -783,15 +770,17 @@ impl AxVM { pending.base.as_usize(), pending.base.as_usize() + pending.size ); - devices.add_fw_cfg_dev(Arc::new(FwCfg::new( - pending.base, - pending.size, - pending.kernel, - pending.initrd, - pending.cmdline.as_deref(), - pending.cpu_num, - pending.platform, - )))?; + devices + .add_fw_cfg_dev(Arc::new(FwCfg::new( + pending.base, + pending.size, + pending.kernel, + pending.initrd, + pending.cmdline.as_deref(), + pending.cpu_num, + pending.platform, + ))) + .map_err(|error| AxVmError::device("register fw_cfg device", error))?; } Ok(()) } @@ -801,20 +790,33 @@ impl AxVM { addr: GuestPhysAddr, width: AccessWidth, data: usize, - ) -> AxResult { + ) -> AxVmResult { let devices = self.get_devices()?; if let Some(fw_cfg) = devices.fw_cfg_for_dma_addr(addr) { - if let Some(desc_addr) = fw_cfg.write_dma_address(addr, width, data)? { - fw_cfg.process_dma( - desc_addr, - |gpa, buffer| self.read_from_guest(gpa, buffer), - |gpa, buffer| self.write_to_guest(gpa, buffer), - )?; + if let Some(desc_addr) = fw_cfg + .write_dma_address(addr, width, data) + .map_err(|error| AxVmError::device("write fw_cfg DMA address", error))? + { + fw_cfg + .process_dma( + desc_addr, + |gpa, buffer| { + self.read_from_guest(gpa, buffer) + .map_err(|_| BackendError::InvalidData) + }, + |gpa, buffer| { + self.write_to_guest(gpa, buffer) + .map_err(|_| BackendError::InvalidData) + }, + ) + .map_err(|error| AxVmError::device("process fw_cfg DMA request", error))?; } return Ok(()); } - devices.handle_mmio_write(addr, width, data)?; + devices + .handle_mmio_write(addr, width, data) + .map_err(|error| AxVmError::device("write guest MMIO", error))?; Ok(()) } @@ -949,7 +951,7 @@ impl AxVM { &self, targets: CpuMask, irq: usize, - ) -> AxResult { + ) -> AxVmResult { for vcpu in self.vcpu_list() { if targets.get(vcpu.id()) { crate::runtime::vcpus::queue_interrupt(self.id(), vcpu.id(), irq)?; @@ -978,23 +980,29 @@ impl AxVM { hpa: HostPhysAddr, size: usize, flags: MappingFlags, - ) -> AxResult { + ) -> AxVmResult { self.with_resources_mut(|resources| { - resources.address_space.map_linear(gpa, hpa, size, flags)?; + resources + .address_space + .map_linear(gpa, hpa, size, flags) + .map_err(|error| AxVmError::memory("map guest memory region", error))?; Ok(()) }) } /// Unmaps a region of guest physical memory. - pub fn unmap_region(&self, gpa: GuestPhysAddr, size: usize) -> AxResult { + pub fn unmap_region(&self, gpa: GuestPhysAddr, size: usize) -> AxVmResult { self.with_resources_mut(|resources| { - resources.address_space.unmap(gpa, size)?; + resources + .address_space + .unmap(gpa, size) + .map_err(|error| AxVmError::memory("unmap guest memory region", error))?; Ok(()) }) } /// Reads an object of type `T` from the guest physical address. - pub fn read_from_guest_of(&self, gpa_ptr: GuestPhysAddr) -> AxResult { + pub fn read_from_guest_of(&self, gpa_ptr: GuestPhysAddr) -> AxVmResult { let size = core::mem::size_of::(); // Ensure the address is properly aligned for the type. @@ -1040,7 +1048,7 @@ impl AxVM { } /// Reads raw bytes from guest physical memory. - pub fn read_from_guest(&self, gpa_ptr: GuestPhysAddr, buffer: &mut [u8]) -> AxResult { + pub fn read_from_guest(&self, gpa_ptr: GuestPhysAddr, buffer: &mut [u8]) -> AxVmResult { self.with_resources(|resources| { let Some(chunks) = resources .address_space @@ -1067,7 +1075,7 @@ 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 { + pub fn write_to_guest_of(&self, gpa_ptr: GuestPhysAddr, data: &T) -> AxVmResult { let bytes = unsafe { core::slice::from_raw_parts(data as *const T as *const u8, core::mem::size_of::()) }; @@ -1075,7 +1083,7 @@ impl AxVM { } /// Writes raw bytes into guest physical memory. - pub fn write_to_guest(&self, gpa_ptr: GuestPhysAddr, data: &[u8]) -> AxResult { + pub fn write_to_guest(&self, gpa_ptr: GuestPhysAddr, data: &[u8]) -> AxVmResult { if data.is_empty() { return Ok(()); } @@ -1097,11 +1105,14 @@ impl AxVM { /// ## 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)> { + /// * `AxVmResult<(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) -> AxVmResult<(GuestPhysAddr, usize)> { // Ensure the expected size is aligned to 4K. let size = align_up_4k(expected_size); - let gpa = self.get_devices()?.alloc_ivc_channel(size)?; + let gpa = self + .get_devices()? + .alloc_ivc_channel(size) + .map_err(|error| AxVmError::memory("reserve IVC guest address range", error))?; Ok((gpa, size)) } @@ -1110,9 +1121,11 @@ impl AxVM { /// * `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.get_devices()?.release_ivc_channel(gpa, size)?; + /// * `AxVmResult<()>` - An empty result indicating success or failure. + pub fn release_ivc_channel(&self, gpa: GuestPhysAddr, size: usize) -> AxVmResult { + self.get_devices()? + .release_ivc_channel(gpa, size) + .map_err(|error| AxVmError::memory("release IVC guest address range", error))?; Ok(()) } @@ -1121,7 +1134,7 @@ impl AxVM { &self, layout: Layout, gpa: Option, - ) -> AxResult<&[u8]> { + ) -> AxVmResult<&[u8]> { assert!( layout.size() > 0, "Cannot allocate zero-sized memory region" @@ -1129,7 +1142,9 @@ impl AxVM { let hva = unsafe { alloc::alloc::alloc_zeroed(layout) }; if hva.is_null() { - return Err(AxError::NoMemory); + return Err(AxVmError::OutOfMemory { + operation: "allocate IVC channel", + }); } let s = unsafe { core::slice::from_raw_parts_mut(hva, layout.size()) }; let hva = HostVirtAddr::from_mut_ptr_of(hva); @@ -1139,15 +1154,18 @@ impl AxVM { let gpa = gpa.unwrap_or_else(|| hpa.as_usize().into()); if let Err(err) = self.with_resources_mut(|resources| { - resources.address_space.map_linear( - gpa, - hpa, - layout.size(), - MappingFlags::READ - | MappingFlags::WRITE - | MappingFlags::EXECUTE - | MappingFlags::USER, - )?; + resources + .address_space + .map_linear( + gpa, + hpa, + layout.size(), + MappingFlags::READ + | MappingFlags::WRITE + | MappingFlags::EXECUTE + | MappingFlags::USER, + ) + .map_err(|error| AxVmError::memory("map allocated guest memory", error))?; resources.memory_regions.push(VMMemoryRegion { gpa, hva, @@ -1172,7 +1190,7 @@ impl AxVM { } /// Prepares all memory regions configured for this VM. - pub fn prepare_memory_layout(&self) -> AxResult { + pub fn prepare_memory_layout(&self) -> AxVmResult { let memory_regions = self.with_resources(|resources| Ok(resources.config.memory_regions().to_vec()))?; let layout = memory::MemoryLayoutBuilder::new(self, &memory_regions).prepare()?; @@ -1187,7 +1205,7 @@ impl AxVM { &self, layout: Layout, gpa: Option, - ) -> AxResult { + ) -> AxVmResult { assert!( layout.size() > 0, "Cannot allocate zero-sized memory region" @@ -1195,15 +1213,18 @@ impl AxVM { let gpa = gpa.ok_or_else(|| ax_err_type!(InvalidInput, "Reserved memory GPA is required"))?; self.with_resources_mut(|resources| { - resources.address_space.map_linear( - gpa, - gpa.as_usize().into(), - layout.size(), - MappingFlags::READ - | MappingFlags::WRITE - | MappingFlags::EXECUTE - | MappingFlags::USER, - )?; + resources + .address_space + .map_linear( + gpa, + gpa.as_usize().into(), + layout.size(), + MappingFlags::READ + | MappingFlags::WRITE + | MappingFlags::EXECUTE + | MappingFlags::USER, + ) + .map_err(|error| AxVmError::memory("map reserved guest memory", error))?; let hva = gpa.as_usize().into(); resources.memory_regions.push(VMMemoryRegion { gpa, @@ -1216,7 +1237,7 @@ impl AxVM { } /// Destroys the VM and releases all lifecycle-owned resources. - pub fn destroy(&self) -> AxResult { + pub fn destroy(&self) -> AxVmResult { let vm_id = self.id(); match self.status() { VmStatus::Running | VmStatus::Paused | VmStatus::Stopping => { @@ -1232,15 +1253,12 @@ impl AxVM { self.stop_and_join_runtime(StopReason::Forced)?; } } - self.machine - .lock() - .destroy_with(|resources| { - if let Some(mut resources) = resources { - Self::cleanup_resource_set(vm_id, &mut resources); - } - Ok(()) - }) - .map_err(VmLifecycleError::into_ax_error) + self.machine.lock().destroy_with(|resources| { + if let Some(mut resources) = resources { + Self::cleanup_resource_set(vm_id, &mut resources); + } + Ok(()) + }) } fn cleanup_resource_set(vm_id: usize, resources: &mut AxVMResources) { @@ -1332,7 +1350,7 @@ mod tests { let err = write_guest_bytes_to_chunks(&mut chunks, &[1, 2, 3]).unwrap_err(); - assert_eq!(err, AxError::InvalidInput); + assert!(matches!(err, AxVmError::InvalidInput { .. })); assert_eq!(only, [1, 2]); } diff --git a/virtualization/axvm/src/vm/prepare.rs b/virtualization/axvm/src/vm/prepare.rs index 4f57cd84ba..6f7ef326d6 100644 --- a/virtualization/axvm/src/vm/prepare.rs +++ b/virtualization/axvm/src/vm/prepare.rs @@ -6,12 +6,11 @@ pub(crate) mod vcpus; use alloc::{format, sync::Arc}; -use ax_errno::{AxResult, ax_err, ax_err_type}; use axdevice::{DeviceFactoryRegistry, register_builtin_factories}; use self::{devices::PreparedDevices, vcpus::PreparedVcpus}; use super::{AxVM, AxVMResources}; -use crate::irq::InterruptFabric; +use crate::{AxVmError, AxVmResult, ax_err, ax_err_type, irq::InterruptFabric}; pub(crate) enum VmInitRequest<'a> { Default, @@ -34,7 +33,7 @@ impl PreparedVm { impl AxVM { /// Sets up the VM before booting. - pub fn prepare(&self) -> AxResult { + pub fn prepare(&self) -> AxVmResult { crate::arch::CurrentArch::init_vm(self, VmInitRequest::Default) } @@ -43,7 +42,7 @@ impl AxVM { &self, factories: &DeviceFactoryRegistry, interrupt_fabric: InterruptFabric, - ) -> AxResult { + ) -> AxVmResult { crate::arch::CurrentArch::init_vm( self, VmInitRequest::Provided { @@ -54,17 +53,18 @@ impl AxVM { } } -pub(crate) fn default_device_factories() -> AxResult { +pub(crate) fn default_device_factories() -> AxVmResult { let mut factories = DeviceFactoryRegistry::new(); - register_builtin_factories(&mut factories)?; + register_builtin_factories(&mut factories) + .map_err(|error| AxVmError::device("register built-in device factories", error))?; Ok(factories) } pub(crate) fn complete_vm_init( vm: &AxVM, interrupt_fabric: InterruptFabric, - initialize: impl FnOnce(&mut AxVMResources, &InterruptFabric) -> AxResult, -) -> AxResult { + initialize: impl FnOnce(&mut AxVMResources, &InterruptFabric) -> AxVmResult, +) -> AxVmResult { let mut machine = vm.machine.lock(); if !matches!( machine.status(), @@ -103,7 +103,7 @@ pub(crate) fn complete_vm_init( Ok(()) } -pub(crate) fn validate_guest_dtb(resources: &AxVMResources) -> AxResult { +pub(crate) fn validate_guest_dtb(resources: &AxVMResources) -> AxVmResult { if resources.config.image_config().dtb_load_gpa.is_some() && resources.boot_description.device_tree().is_none() { diff --git a/virtualization/axvm/src/vm/prepare/address_space.rs b/virtualization/axvm/src/vm/prepare/address_space.rs index c829feeb37..57f4d50aa9 100644 --- a/virtualization/axvm/src/vm/prepare/address_space.rs +++ b/virtualization/axvm/src/vm/prepare/address_space.rs @@ -2,19 +2,21 @@ use alloc::vec::Vec; -use ax_errno::AxResult; use axdevice::AxVmDevices; use axdevice_base::Resource; use super::super::{AxVM, AxVMResources, VM_ASPACE_BASE, VM_ASPACE_SIZE}; -use crate::layout::{GuestOwnedRegion, VmRegionKind, build_address_layout}; +use crate::{ + AxVmError, AxVmResult, + layout::{GuestOwnedRegion, VmRegionKind, build_address_layout}, +}; pub(crate) fn map_guest_address_space( vm: &AxVM, resources: &mut AxVMResources, devices: &AxVmDevices, owned_regions: &[GuestOwnedRegion], -) -> AxResult { +) -> AxVmResult { let emulated_resources = devices .devices() .flat_map(|device| device.resources().iter().cloned()) @@ -40,12 +42,10 @@ pub(crate) fn map_guest_address_space( mapping.hpa.as_usize() + mapping.size, mapping.flags ); - resources.address_space.map_linear( - mapping.gpa, - mapping.hpa, - mapping.size, - mapping.flags, - )?; + resources + .address_space + .map_linear(mapping.gpa, mapping.hpa, mapping.size, mapping.flags) + .map_err(|error| AxVmError::memory("map guest address space", error))?; } resources.address_layout = Some(address_layout); diff --git a/virtualization/axvm/src/vm/prepare/devices.rs b/virtualization/axvm/src/vm/prepare/devices.rs index 5f24eee57a..8b783bff81 100644 --- a/virtualization/axvm/src/vm/prepare/devices.rs +++ b/virtualization/axvm/src/vm/prepare/devices.rs @@ -1,10 +1,9 @@ //! Device construction for VM preparation. -use ax_errno::AxResult; use axdevice::{AxVmDeviceConfig, AxVmDevices, DeviceBuildContext, DeviceFactoryRegistry}; use super::super::{AxVM, AxVMResources}; -use crate::irq::InterruptFabric; +use crate::{AxVmError, AxVmResult, irq::InterruptFabric}; pub(crate) struct PreparedDevices { pub(crate) devices: AxVmDevices, @@ -15,7 +14,7 @@ impl PreparedDevices { resources: &AxVMResources, factories: &DeviceFactoryRegistry, interrupt_fabric: &InterruptFabric, - ) -> AxResult { + ) -> AxVmResult { let build_context = DeviceBuildContext::new(interrupt_fabric); let devices = AxVmDevices::build_with_factories( AxVmDeviceConfig { @@ -23,12 +22,13 @@ impl PreparedDevices { }, factories, &build_context, - )?; + ) + .map_err(|error| AxVmError::device("build VM devices", error))?; Ok(Self { devices }) } - pub(crate) fn register_special_devices(&mut self, vm: &AxVM) -> AxResult { + pub(crate) fn register_special_devices(&mut self, vm: &AxVM) -> AxVmResult { vm.add_special_emulated_devices(&mut self.devices) } diff --git a/virtualization/axvm/src/vm/prepare/vcpus.rs b/virtualization/axvm/src/vm/prepare/vcpus.rs index 2012650198..2cfb6ced60 100644 --- a/virtualization/axvm/src/vm/prepare/vcpus.rs +++ b/virtualization/axvm/src/vm/prepare/vcpus.rs @@ -2,10 +2,10 @@ use alloc::{boxed::Box, sync::Arc, vec::Vec}; -use ax_errno::AxResult; use axvm_types::VmArchVcpuOps; use super::super::{AxVCpuRef, AxVMResources, VCpu}; +use crate::AxVmResult; #[derive(Clone, Copy, Debug)] pub(crate) struct VcpuPlacement { @@ -24,9 +24,10 @@ impl PreparedVcpus { placements: &[VcpuPlacement], mut build_config: impl FnMut( VcpuPlacement, - ) - -> AxResult<::CreateConfig>, - ) -> AxResult { + ) -> AxVmResult< + ::CreateConfig, + >, + ) -> AxVmResult { debug!("id: {vm_id}, vCPU placements: {placements:#x?}"); let mut vcpus = Vec::with_capacity(placements.len()); @@ -58,9 +59,10 @@ impl PreparedVcpus { mut build_config: impl FnMut( &crate::config::AxVMConfig, &[crate::vm::VMMemoryRegion], - ) - -> AxResult<::SetupConfig>, - ) -> AxResult { + ) -> AxVmResult< + ::SetupConfig, + >, + ) -> AxVmResult { for vcpu in &self.vcpus { let setup_config = build_config(&resources.config, &resources.memory_regions)?; let entry = if vcpu.id() == 0 { diff --git a/virtualization/axvm/tests/error_contract.rs b/virtualization/axvm/tests/error_contract.rs new file mode 100644 index 0000000000..dc9a773047 --- /dev/null +++ b/virtualization/axvm/tests/error_contract.rs @@ -0,0 +1,91 @@ +// Copyright 2026 The Axvisor Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[test] +fn axvm_owns_its_public_error_contract() { + let manifest = include_str!("../Cargo.toml"); + let crate_root = include_str!("../src/lib.rs"); + let error = include_str!("../src/error.rs"); + + assert!(!manifest.contains("ax-errno")); + assert!(manifest.contains("thiserror = { workspace = true }")); + assert!(crate_root.contains("pub use error::{AxVmError, AxVmResult};")); + assert!(error.contains("thiserror::Error")); + assert!(error.contains("pub enum AxVmError")); + assert!(error.contains("pub type AxVmResult")); +} + +#[test] +fn axvm_sources_do_not_name_axerrno() { + let source_root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src"); + let mut pending = vec![source_root]; + let mut violations = Vec::new(); + + while let Some(path) = pending.pop() { + for entry in std::fs::read_dir(&path).expect("AxVM source directory must be readable") { + let entry = entry.expect("AxVM source entry must be readable"); + let path = entry.path(); + if path.is_dir() { + pending.push(path); + continue; + } + if path.extension().is_some_and(|extension| extension == "rs") { + let source = std::fs::read_to_string(&path).expect("AxVM source must be readable"); + if source.contains("ax_errno") { + violations.push(path); + } + } + } + } + + assert!( + violations.is_empty(), + "AxVM sources must not name ax_errno directly: {violations:?}" + ); +} + +#[test] +fn public_failure_interfaces_name_axvm_result() { + let runtime = include_str!("../src/runtime/mod.rs"); + let vm = include_str!("../src/vm/mod.rs"); + let prepare = include_str!("../src/vm/prepare.rs"); + let boot = include_str!("../src/boot/mod.rs"); + + assert!(runtime.contains("pub fn start_vm(vm_id: usize) -> AxVmResult")); + assert!(vm.contains("pub fn new(config: AxVMConfig) -> AxVmResult")); + assert!(prepare.contains("pub fn prepare(&self) -> AxVmResult")); + assert!(boot.contains("fn read_file(&self, file_name: &str) -> crate::AxVmResult")); +} + +#[test] +fn axvisor_uses_anyhow_without_axerrno() { + let axvisor_root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../os/axvisor"); + let manifest = std::fs::read_to_string(axvisor_root.join("Cargo.toml")) + .expect("AxVisor manifest must be readable"); + let manager = std::fs::read_to_string(axvisor_root.join("src/manager.rs")) + .expect("AxVisor manager source must be readable"); + let config = std::fs::read_to_string(axvisor_root.join("src/config.rs")) + .expect("AxVisor config source must be readable"); + let shell = std::fs::read_to_string(axvisor_root.join("src/shell/command/vm.rs")) + .expect("AxVisor shell source must be readable"); + + assert!(!manifest.contains("ax-errno")); + assert!(manifest.contains("anyhow.workspace = true")); + assert!(!manager.contains("ax_errno")); + assert!(!config.contains("ax_errno")); + assert!(manager.contains("use anyhow::{Context, Result};")); + assert!(config.contains("AxVmError::Boot")); + assert!(shell.contains("{err:#}")); + assert!(!shell.contains("Err(\"Failed to boot VM\")")); +}