diff --git a/Cargo.lock b/Cargo.lock index 3f3fd7c462..0e2e77c278 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1537,6 +1537,7 @@ dependencies = [ "arm_vgic", "ax-cpumask", "ax-crate-interface", + "ax-driver", "ax-errno 0.6.0", "ax-hal", "ax-kernel-guard", @@ -1555,9 +1556,12 @@ dependencies = [ "axhvc", "axplat-dyn", "axvm-types", + "axvmconfig", "byte-unit", "cfg-if", "extern-trait", + "fdt-parser", + "hashbrown 0.14.5", "irq-framework", "log", "loongarch_vcpu", diff --git a/docs/docs/architecture/axvisor.md b/docs/docs/architecture/axvisor.md index 319938d73e..9c36abeed0 100644 --- a/docs/docs/architecture/axvisor.md +++ b/docs/docs/architecture/axvisor.md @@ -118,7 +118,7 @@ Guest 的存在方式是"配置驱动的 VM 实例化过程",而非代码中 ### vCPU 作为 ArceOS task -每个 vCPU 最终被包装成 ArceOS task,进入独立的等待队列与运行循环。主 vCPU 在 `setup_vm_primary_vcpu()` 中首先被分配 task,初始为阻塞状态,直到 `notify_primary_vcpu()` 唤醒。`vcpu_run()` 中不断调用 `vm.run_vcpu()` 并处理不同的 `AxVCpuExitReason`。 +每个 vCPU 最终被包装成 ArceOS task,进入独立的等待队列与运行循环。主 vCPU 在 `setup_vm_primary_vcpu()` 中首先被分配 task,初始为阻塞状态,直到 `notify_primary_vcpu()` 唤醒。`vcpu_run()` 中不断调用 `vm.run_vcpu()` 并处理不同的 `VmExit`。 AxVisor 的并发模型可理解为: @@ -232,7 +232,7 @@ sequenceDiagram ### vCPU 运行循环 -`vcpu_run()` 是 AxVisor 动态行为最密集的入口,不断处理不同的 `AxVCpuExitReason`: +`vcpu_run()` 是 AxVisor 动态行为最密集的入口,不断处理不同的 `VmExit`: ```mermaid flowchart TD diff --git a/docs/docs/components/crates/arm-vcpu.md b/docs/docs/components/crates/arm-vcpu.md index 97f8396f49..06171be430 100644 --- a/docs/docs/components/crates/arm-vcpu.md +++ b/docs/docs/components/crates/arm-vcpu.md @@ -48,7 +48,7 @@ ```mermaid flowchart TD A["Aarch64VCpu::new"] --> B["setup / init_hv"] - B --> C["set_entry / set_ept_root"] + B --> C["set_entry / set_nested_page_table_root"] C --> D["run()"] D --> E["save_host_sp_el0"] E --> F["restore_vm_system_regs"] @@ -57,17 +57,17 @@ flowchart TD H --> I["exception.S 保存 Guest 上下文"] I --> J["vmexit_trampoline 切回宿主栈"] J --> K["vmexit_handler"] - K --> L["生成 AxVCpuExitReason"] + K --> L["生成 VmExit"] ``` 可以进一步拆解为: 1. `new()` 只构造基本上下文,并把 DTB 地址写到约定参数寄存器。 2. `setup()` / `init_hv()` 配置 `SPSR_EL2`、`VTCR_EL2`、`HCR_EL2`、`VMPIDR_EL2` 等虚拟化状态。 -3. `set_entry()` 设置 guest 入口 PC;`set_ept_root()` 设置 `VTTBR_EL2`。 +3. `set_entry()` 设置 guest 入口 PC;`set_nested_page_table_root()` 设置 `VTTBR_EL2`。 4. `run()` 先保存宿主 `SP_EL0`,再恢复 guest 系统寄存器,最后通过裸函数 `run_guest()` 跳到 `context_vm_entry` 并 `eret` 进入 guest。 5. guest 一旦因同步异常、IRQ 或系统寄存器 trap 回到 EL2,`exception.S` 会把 guest 上下文写回 `Aarch64VCpu`,再通过 `vmexit_trampoline` 切回宿主栈。 -6. `vmexit_handler()` 把硬件 trap 归类为 `AxVCpuExitReason`,再交给上层 hypervisor 处理。 +6. `vmexit_handler()` 把硬件 trap 归类为 `VmExit`,再交给上层 hypervisor 处理。 ### 1.5 VM exit 与内建处理逻辑 `arm_vcpu` 的 VM exit 路径不是简单“返回异常号”,而是做了明确分类: @@ -79,7 +79,7 @@ flowchart TD 这说明 `arm_vcpu` 不只是“上下文切换器”,它同时承担了第一层 trap 解码器的角色。 ### 1.6 与 GIC、中断与 Stage-2 页表的关系 -- `set_ept_root()` 实际写的是 `VTTBR_EL2`,即 Stage-2 根页表基址。 +- `set_nested_page_table_root()` 实际写的是 `VTTBR_EL2`,即 Stage-2 根页表基址。 - `VTCR_EL2` 会根据 `ID_AA64MMFR0_EL1` 探测宿主支持的物理地址位宽和页表层级。 - `HCR_EL2` 会根据配置选择虚拟中断或物理直通中断行为。 - 真正的“虚拟中断注入”并不在本 crate 内建模,而是调用 `axvisor_api::arch::hardware_inject_virtual_interrupt()`,与 `arm_vgic` 形成分工。 @@ -88,25 +88,25 @@ flowchart TD ### 功能概览 - 创建和维护 AArch64 guest 上下文。 - 在 EL2 和 guest EL1/EL0 之间切换执行。 -- 解析常见 VM exit 并生成 `AxVCpuExitReason`。 +- 解析常见 VM exit 并生成 `VmExit`。 - 提供 per-CPU EL2 本地状态管理。 - 与宿主 HAL / 中断注入接口协作完成 IRQ 路径。 ### 使用场景 - `Aarch64VCpu::new()` / `setup()`:构造 vCPU 并初始化 EL2 虚拟化寄存器。 - `set_entry()`:设置 guest 起始执行地址。 -- `set_ept_root()`:安装 Stage-2 根页表。 +- `set_nested_page_table_root()`:安装 Stage-2 根页表。 - `run()`:进入 guest 并等待下一次 VM exit。 - `inject_interrupt()`:调用宿主侧架构 API 注入虚拟中断。 ### 使用方式 -`arm_vcpu` 并不直接作为最终业务 API 暴露给用户,典型用法是由 `axvm` 绑定为当前架构的 `AxArchVCpuImpl`: +`arm_vcpu` 并不直接作为最终业务 API 暴露给用户,典型用法是由 `axvm` 绑定为当前架构的 `ArchVCpu`: ```rust let mut vcpu = Aarch64VCpu::::new(vcpu_id, dtb_addr)?; vcpu.setup(config)?; vcpu.set_entry(entry_gpa); -vcpu.set_ept_root(stage2_root); +vcpu.set_nested_page_table_root(stage2_root); let exit = vcpu.run()?; ``` @@ -164,7 +164,7 @@ arm_vcpu = { workspace = true } ### 单元测试 - `VTCR_EL2` 参数计算与寄存器位域解析。 - ESR/ISS/FAR/HPFAR 到 GPA 的合成逻辑。 -- `AxVCpuExitReason` 映射中那些可纯 Rust 验证的分支。 +- `VmExit` 映射中那些可纯 Rust 验证的分支。 ### 集成测试 - Axvisor + aarch64 场景下的 guest 启动、HVC、SMC、MMIO、IPI 与定时器路径。 diff --git a/docs/docs/components/crates/ax-cpumask.md b/docs/docs/components/crates/ax-cpumask.md index f0187172c8..be2d72ca90 100644 --- a/docs/docs/components/crates/ax-cpumask.md +++ b/docs/docs/components/crates/ax-cpumask.md @@ -48,7 +48,7 @@ ### 使用场景 - `CpuMask::new()` / `set()`:`ax-task/src/api.rs` 用来构造 `AxCpuMask`。 - `get()` / `is_empty()`:`ax-task/src/run_queue.rs` 用于根据 affinity 选择运行队列。 -- `CpuMask`:`virtualization/axvm/src/vm.rs` 和 `os/axvisor/src/vmm/vcpus.rs` 直接使用,用来描述 vCPU 目标集合。 +- `CpuMask`:`virtualization/axvm/src/vm.rs` 和 `virtualization/axvm/src/runtime/vcpus.rs` 直接使用,用来描述 vCPU 目标集合。 ### 边界说明 - `ax-cpumask` 不负责验证 CPU 是否在线;它只存位。 diff --git a/docs/docs/components/crates/ax-timer-list.md b/docs/docs/components/crates/ax-timer-list.md index eb419d233a..6130405770 100644 --- a/docs/docs/components/crates/ax-timer-list.md +++ b/docs/docs/components/crates/ax-timer-list.md @@ -4,7 +4,7 @@ > 类型:库 crate > 分层:组件层 / 定时事件队列基础件 > 版本:`0.1.0` -> 文档依据:`Cargo.toml`、`README.md`、`src/lib.rs`、`os/axvisor/src/vmm/timer.rs` +> 文档依据:`Cargo.toml`、`README.md`、`src/lib.rs`、`virtualization/axvm/src/timer.rs` `ax-timer-list` 提供一个按截止时间排序的定时事件容器。它内部用 `BinaryHeap` 实现最小堆语义,向上暴露“插入事件、取消事件、取出最早到期事件”的接口。它是容器型叶子基础件:不是硬件定时器驱动、不是中断时钟源,也不是完整的定时器子系统。 @@ -53,7 +53,7 @@ flowchart TD - 逐个弹出已经到期的事件。 ### 使用场景 -- `TimerList::set()`:`os/axvisor/src/vmm/timer.rs` 在注册 VMM timer 时调用。 +- `TimerList::set()`:`virtualization/axvm/src/timer.rs` 在注册 VMM timer 时调用。 - `cancel()`:Axvisor 按 token 取消已登记的事件。 - `expire_one()`:Axvisor 的 `check_events()` 循环不断取出已到期事件,再由上层显式执行回调。 - `TimerEventFn::new()`:本 crate 测试里用于快速包装闭包事件。 diff --git a/docs/docs/components/crates/axdevice.md b/docs/docs/components/crates/axdevice.md index 272549dc15..4e118e70ed 100644 --- a/docs/docs/components/crates/axdevice.md +++ b/docs/docs/components/crates/axdevice.md @@ -146,7 +146,7 @@ flowchart TD `axvm` 才是 `axdevice` 的主要上层: - `axvm` 负责构建客户机地址空间,建立线性映射和 Stage-2/EPT。 -- `axvm` 负责在 vCPU 退出循环中把 `AxVCpuExitReason` 变成对 `AxVmDevices::handle_*()` 的调用。 +- `axvm` 负责在 vCPU 退出循环中把 `VmExit` 变成对 `AxVmDevices::handle_*()` 的调用。 - 某些附加设备(如系统寄存器设备)也可能在 `axvm` 层再通过 `add_sys_reg_dev()` 注入。 换言之,`axdevice` 是 per-VM device table,而 `axvm` 是把这个 table 纳入 VM 执行模型的调度者。 diff --git a/docs/docs/components/crates/axhvc.md b/docs/docs/components/crates/axhvc.md index 87d3bf1812..cdea032ae6 100644 --- a/docs/docs/components/crates/axhvc.md +++ b/docs/docs/components/crates/axhvc.md @@ -4,7 +4,7 @@ > 类型:库 crate > 分层:组件层 / HyperCall ABI 定义组件 > 版本:`0.2.0` -> 文档依据:当前仓库源码、`Cargo.toml`、`README.md`、`src/lib.rs`、`os/axvisor/src/vmm/hvc.rs` +> 文档依据:当前仓库源码、`Cargo.toml`、`README.md`、`src/lib.rs`、`virtualization/axvm/src/runtime/hvc.rs` `axhvc` 的真实定位是 **AxVisor HyperCall ABI 描述层**。它负责把“来宾发起的 hypercall 编号”定义为稳定的 Rust 类型,并给出统一的结果类型;它并不负责 trap 入口、寄存器编组、权限校验、实际功能实现,也不是完整的 hypercall 子系统。 @@ -69,7 +69,7 @@ ### 1.5 与 Axvisor 当前实现的真实关系 -当前仓库里的真实消费者是 `os/axvisor/src/vmm/hvc.rs`。那里会: +当前仓库里的真实消费者是 `virtualization/axvm/src/runtime/hvc.rs`。那里会: 1. 从 trap 参数里拿到原始 hypercall 编号 2. 用 `HyperCallCode::try_from(code as u32)` 做检查 @@ -136,7 +136,7 @@ 实际调用链为: -- `axhvc` -> `os/axvisor/src/vmm/hvc.rs` +- `axhvc` -> `virtualization/axvm/src/runtime/hvc.rs` ### 3.3 关系解读 @@ -212,7 +212,7 @@ crate 目录里没有单独测试。当前主要依赖: | --- | --- | --- | --- | | ArceOS | 当前仓库未见直接接线 | 共享 ABI 组件 | 本身不属于 ArceOS 运行时主线 | | StarryOS | 当前仓库未见直接接线 | 共享 ABI 组件 | 尚未看到直接集成 | -| Axvisor | HyperCall 入口边界 | HyperCall 编号与返回值定义层 | 被 `os/axvisor/src/vmm/hvc.rs` 直接使用 | +| Axvisor | HyperCall 入口边界 | HyperCall 编号与返回值定义层 | 被 `virtualization/axvm/src/runtime/hvc.rs` 直接使用 | ## 总结 diff --git a/docs/docs/components/crates/axvm.md b/docs/docs/components/crates/axvm.md index 725c7babd3..9ba548a320 100644 --- a/docs/docs/components/crates/axvm.md +++ b/docs/docs/components/crates/axvm.md @@ -19,7 +19,7 @@ 可以把 `axvm` 理解为“可被 Hypervisor 编排的 VM 对象层”,而不是顶层 Hypervisor 程序。 ### 模块结构 -- `src/lib.rs`:crate 入口,导出 `AxVM`、`AxVMRef`、`AxVCpuRef`、`VMMemoryRegion`、`VMStatus`、`config`、`AxVMHal` 与 `has_hardware_support()`。 +- `src/lib.rs`:crate 入口,导出 `AxVM`、`AxVMRef`、`VMMemoryRegion`、`VMStatus`、`config`、`AxVMHal` 与 `has_hardware_support()`。 - `src/vm.rs`:核心实现文件,定义 `AxVM`、内部可变/不可变状态、内存区管理、状态切换、`init()`、`boot()`、`shutdown()`、`run_vcpu()` 等。 - `src/vcpu.rs`:AxVM 自有 vCPU wrapper、状态机、current-vCPU 绑定和架构适配层,按 `x86_64`、`riscv64`、`aarch64`、`loongarch64` 选择具体后端。 - `src/hal.rs`:定义 `AxVMHal` trait,规定宿主必须提供的能力边界。 @@ -31,7 +31,7 @@ - `AxVMInnerMut`:可变状态,包含 `address_space`、`memory_regions`、`config` 和 `vm_status`。 - `VMMemoryRegion`:记录客户机物理地址、宿主虚拟地址、布局信息和是否需要回收。 - `VMStatus`:`Loading`、`Loaded`、`Running`、`Suspended`、`Stopping`、`Stopped`,描述 VM 生命周期。 -- `AxVCpuRef`:统一的 vCPU 引用类型,是上层调度与 VM exit 处理的基本单元。 +- `VcpuSnapshot`:对外暴露的架构无关 vCPU 状态快照。 - `AxVMConfig` / `AxVMCrateConfig`:前者用于运行时 VM 创建,后者更贴近 TOML 配置源。 ### 1.4 VM 生命周期与主线 @@ -59,7 +59,7 @@ flowchart TD 1. `AxVM::new(config)` 创建空的客户机地址空间,并把状态初始化为 `Loading`。 2. `init()` 负责真正完成 VM 组装:创建 vCPU、合并直通地址区间、建立设备、设置页表根与 vCPU 初始入口。 3. `boot()` 把状态切换到 `Running`,但不直接执行客户机代码。 -4. 真正执行路径在 `run_vcpu(vcpu_id)`,它循环处理 `VmExit` / 兼容名 `AxVCpuExitReason`。 +4. 真正执行路径在 `run_vcpu(vcpu_id)`,它循环处理 `VmExit`。 5. `shutdown()` 把 VM 推入停止状态;`Drop` 中会触发资源清理。 当前源码表明: @@ -111,7 +111,7 @@ vm.boot()?; let exit_reason = vm.run_vcpu(0)?; ``` -在实际仓库中,这套流程由 `os/axvisor/src/vmm/*` 完成,而不是由普通库使用者直接手写。 +在实际仓库中,这套流程由 `virtualization/axvm/src/runtime/*` 完成,而不是由普通库使用者直接手写。 ## 依赖关系 ```mermaid diff --git a/docs/docs/components/crates/axvmconfig.md b/docs/docs/components/crates/axvmconfig.md index d804a29c83..880a4bdd5a 100644 --- a/docs/docs/components/crates/axvmconfig.md +++ b/docs/docs/components/crates/axvmconfig.md @@ -4,7 +4,7 @@ > 类型:库 + 二进制混合 crate > 分层:组件层 / 虚拟机配置模型 > 版本:`0.2.2` -> 文档依据:当前仓库源码、`Cargo.toml`、`README.md`、`src/lib.rs`、`src/tool.rs`、`virtualization/axvm/src/config.rs` 与 `os/axvisor/src/vmm/config.rs` +> 文档依据:当前仓库源码、`Cargo.toml`、`README.md`、`src/lib.rs`、`src/tool.rs`、`virtualization/axvm/src/config.rs` 与 `os/axvisor/src/config.rs` `axvmconfig` 是 Axvisor 虚拟机配置链路的静态模型层。它的职责不是直接创建 VM,也不是直接操作页表或设备,而是把 TOML 中描述的 VM 元信息、镜像布局、内存区域、模拟设备、直通设备和中断模式转换成一组稳定的数据结构;这些结构随后被 `axvm` 转成运行时 `AxVMConfig`,再由 `os/axvisor` 继续执行内存分配、镜像加载、FDT 处理和 VM 实例化。 @@ -177,7 +177,7 @@ flowchart TD - 各类镜像 load addr 转为 `GuestPhysAddr` - 设备配置整体搬运进运行时配置 -而 `os/axvisor/src/vmm/config.rs` 则负责: +而 `os/axvisor/src/config.rs` 则负责: - 调 `AxVMCrateConfig::from_toml()` 解析原始配置 - 在 AArch64 上做 FDT 相关调整 diff --git a/docs/docs/components/crates/riscv-vcpu.md b/docs/docs/components/crates/riscv-vcpu.md index f99332af69..47f08e81bd 100644 --- a/docs/docs/components/crates/riscv-vcpu.md +++ b/docs/docs/components/crates/riscv-vcpu.md @@ -6,7 +6,7 @@ > 版本:`0.2.2` > 文档依据:当前仓库源码、`Cargo.toml`、`README.md`、`trap.S`、`guest_mem.rs` 及其在 `axvm` 中的接入方式 -`riscv_vcpu` 是 Axvisor 在 RISC-V 架构上的 vCPU 后端实现。它围绕 RISC-V Hypervisor Extension 组织,负责完成四类核心工作:每核 H 扩展环境准备、vCPU 寄存器与 CSR 上下文保存恢复、客户机进入/退出虚拟化执行、以及把 SBI 调用、外部中断、timer 事件和 guest page fault 转译成 `axvm-types::VmExit` / 兼容名 `AxVCpuExitReason`。 +`riscv_vcpu` 是 Axvisor 在 RISC-V 架构上的 vCPU 后端实现。它围绕 RISC-V Hypervisor Extension 组织,负责完成四类核心工作:每核 H 扩展环境准备、vCPU 寄存器与 CSR 上下文保存恢复、客户机进入/退出虚拟化执行、以及把 SBI 调用、外部中断、timer 事件和 guest page fault 转译成 `axvm-types::VmExit`。 ## 架构设计 @@ -24,7 +24,7 @@ - VS 级 CSR 需要在 vCPU 绑定与解绑时手工保存恢复。 - 虚拟中断依赖 `hvip` 的 `vseip` / `vstip` / `vssip` 位。 -因此,尽管泛型接口里保留了 `set_ept_root()` 这类跨架构命名,实际实现并非 EPT,而是写入 `hgatp`。 +因此,尽管泛型接口里保留了 `set_nested_page_table_root()` 这类跨架构命名,实际实现并非 EPT,而是写入 `hgatp`。 ### 模块结构 @@ -105,7 +105,7 @@ flowchart TD ### 1.5 每核初始化与 H 扩展探测 -`RISCVPerCpu` 实现 `AxArchPerCpu`,其核心工作是在 `new()` 时调用 `setup_csrs()`: +`RISCVPerCpu` 实现 `VmArchPerCpuOps`,其核心工作是在 `new()` 时调用 `setup_csrs()`: - `hedeleg` 委托一部分同步异常。 - `hideleg` 委托 VS 级 timer / external / software interrupt。 @@ -119,12 +119,12 @@ flowchart TD ### 1.6 vCPU 生命周期 -`RISCVVCpu` 实现 `AxArchVCpu` 的主要阶段如下: +`RISCVVCpu` 实现 `VmArchVcpuOps` 的主要阶段如下: 1. `new()`:初始化 guest `a0`/`a1`,建立最小寄存器态。 2. `setup()`:准备 guest `sstatus` 与 `hstatus`,打开 `SPV`、`VSXL=64`、`SPVP`。 3. `set_entry()`:写 guest `sepc`。 -4. `set_ept_root()`:把 stage-2 根页表地址编码到 `virtual_hs_csrs.hgatp`。 +4. `set_nested_page_table_root()`:把 stage-2 根页表地址编码到 `virtual_hs_csrs.hgatp`。 5. `bind()`:把 `vsatp`、`vstvec`、`vsepc`、`vscause`、`vsstatus`、`vsie`、`htimedelta` 等写入硬件,并安装 `hgatp`。 6. `run()`:临时调整 host `sie/sstatus`,调用 `_run_guest()` 进入客户机,返回后进入 `vmexit_handler()`。 7. `unbind()`:从硬件读回 VS 级 CSR 和 `hgatp`,清空硬件中的 `hgatp` 并执行 `hfence_gvma_all()`。 @@ -141,7 +141,7 @@ flowchart TD - Legacy SBI:支持 `SET_TIMER`、`CONSOLE_PUTCHAR`、`CONSOLE_GETCHAR`、`SHUTDOWN`。 - HSM:把 `HART_START`、`HART_STOP`、`HART_SUSPEND` 转为 `CpuUp`、`CpuDown`、`Halt`。 -- 自定义 `EID_HVC`:转为 `AxVCpuExitReason::Hypercall`。 +- 自定义 `EID_HVC`:转为 `VmExit::Hypercall`。 - Debug Console Extension (`EID_DBCN`):支持读写 guest 缓冲区和单字节输出。 - System Reset:对 shutdown 转为 `SystemDown`。 - 其余扩展:交给 `RISCVVCpuSbi` 的 `RustSBI` 前向器处理。 @@ -204,19 +204,19 @@ flowchart TD - `RISCVVCpu::new()` - `setup()` - `set_entry()` -- `set_ept_root()` +- `set_nested_page_table_root()` - `bind()` / `run()` / `unbind()` - `has_hardware_support()` - `RISCVPerCpu::new()` / `hardware_enable()` ### 2.3 典型使用场景 -在 `axvm` 中,该 crate 会被包装成架构相关的 `AxArchVCpuImpl`: +在 `axvm` 中,该 crate 会被包装成架构相关的 `ArchVCpu`: - 创建 VM 时根据配置构建 `RISCVVCpuCreateConfig` - 每核先建立 `RISCVPerCpu` - 每个 vCPU 安装 guest 入口和 `hgatp` -- `run_vcpu()` 循环中持续消费 `AxVCpuExitReason` +- `run_vcpu()` 循环中持续消费 `VmExit` 对调用者而言,`riscv_vcpu` 提供的是“RISC-V 版本的统一 vCPU 后端”,而不是一个直接面向应用的 API。 @@ -238,7 +238,7 @@ flowchart TD ### 主要消费者 -- `virtualization/axvm`:把它作为 RISC-V 架构后端重导出为 `AxArchVCpuImpl` 与 `AxVMArchPerCpuImpl`。 +- `virtualization/axvm`:把它作为 RISC-V 架构后端重导出为 `ArchVCpu` 与 `ArchPerCpu`。 - `os/axvisor`:通过 `axvm` 间接使用,是当前仓库中的实际落地对象。 ### 3.3 关系示意 @@ -260,9 +260,9 @@ graph TD 1. 为每个物理核创建 `RISCVPerCpu`,并执行 `hardware_enable()` 检查 H 扩展。 2. 构造 `RISCVVCpuCreateConfig`,设置 `hart_id` 和 `dtb_addr`。 3. 调用 `new()` 和 `setup()` 初始化 guest 初始态。 -4. 用 `set_entry()` 安装 guest 入口,用 `set_ept_root()` 安装 stage-2 根页表。 +4. 用 `set_entry()` 安装 guest 入口,用 `set_nested_page_table_root()` 安装 stage-2 根页表。 5. 在每次调度前 `bind()`,执行 `run()`,退出后 `unbind()`。 -6. 根据返回的 `AxVCpuExitReason` 在 `axvm` 中继续处理设备、中断和 hypercall。 +6. 根据返回的 `VmExit` 在 `axvm` 中继续处理设备、中断和 hypercall。 ### 4.2 调试重点 @@ -294,7 +294,7 @@ cargo build -p riscv_vcpu --target riscv64gc-unknown-none-elf ### 4.4 维护注意事项 - `trap.S` 与 `regs.rs` 的偏移关系非常紧,改动结构体字段顺序必须同步检查汇编偏移宏。 -- `set_ept_root()` 这个名字来自通用抽象,但实现写的是 `hgatp`,文档和代码审查中应避免用 x86 语义误读。 +- `set_nested_page_table_root()` 这个名字来自通用抽象,但实现写的是 `hgatp`,文档和代码审查中应避免用 x86 语义误读。 - `guest_mem.rs` 通过清空 `vsatp` 访问 GPA,修改时要同步考虑 `hfence_vvma_all()` 语义。 - `vmexit_handler()` 集中承载了 SBI、timer、external IRQ 和 MMIO trap 语义,属于最敏感路径。 diff --git a/docs/docs/components/crates/x86-vcpu.md b/docs/docs/components/crates/x86-vcpu.md index 9a238467be..3990aa3e74 100644 --- a/docs/docs/components/crates/x86-vcpu.md +++ b/docs/docs/components/crates/x86-vcpu.md @@ -16,7 +16,7 @@ - 实现 x86_64 架构下的 `VmArchVcpuOps` - 实现每物理 CPU 的 VMX 启停逻辑 -- 将 VMX/SVM exit 原因翻译成 `axvm-types::VmExit` / 兼容名 `AxVCpuExitReason` +- 将 VMX/SVM exit 原因翻译成 `axvm-types::VmExit` 它不承担: @@ -137,7 +137,7 @@ #### 协议翻译层 -如果 exit 不能在本地完全处理,就继续翻译成 `AxVCpuExitReason` 上抛给 `axvm`/VMM,例如: +如果 exit 不能在本地完全处理,就继续翻译成 `VmExit` 上抛给 `axvm`/VMM,例如: - `Hypercall` - `IoRead` / `IoWrite` @@ -178,11 +178,11 @@ ### 2.2 关键 API 语义 -作为 `AxArchVCpu` 实现,它最关键的接口包括: +作为 `VmArchVcpuOps` 实现,它最关键的接口包括: - `new()` - `set_entry()` -- `set_ept_root()` +- `set_nested_page_table_root()` - `setup()` - `run()` - `bind()` @@ -201,7 +201,7 @@ flowchart TD C --> D[bind 到当前物理 CPU] D --> E[vmlaunch/vmresume] E --> F[发生 VM-exit] - F --> G[内建处理或翻译为 AxVCpuExitReason] + F --> G[内建处理或翻译为 VmExit] G --> H[高层 VMM 决策] ``` diff --git a/docs/docs/development/axvisor.md b/docs/docs/development/axvisor.md index 9ad2863c3e..e349863a1c 100644 --- a/docs/docs/development/axvisor.md +++ b/docs/docs/development/axvisor.md @@ -100,7 +100,7 @@ os/axvisor/ | 组件 | 职责 | |------|------| | `axvm` | VM 抽象:`AxVM`, `AxVMRef`, `VMMemoryRegion`, `VMStatus` | -| `axvm-types` + `axvm/src/vcpu.rs` | vCPU 协议与 wrapper:`VmArchVcpuOps`, `VmExit` / `AxVCpuExitReason`,状态机管理 | +| `axvm-types` + `axvm/src/vcpu.rs` | vCPU 协议与 wrapper:`VmArchVcpuOps`, `VmExit` / `VmExit`,状态机管理 | | `axdevice` | 虚拟设备框架:passthrough / emulated / excluded | | `axvisor_api` | Hypervisor API 接口 | | `axaddrspace` | 地址空间管理 | diff --git a/os/axvisor/build.rs b/os/axvisor/build.rs index ffd6ab0d3a..792e6be2c9 100644 --- a/os/axvisor/build.rs +++ b/os/axvisor/build.rs @@ -284,7 +284,7 @@ fn generate_guest_img_loading_functions( }; memory_images.push(quote! { - MemoryImage { + axvm::boot::StaticVmImage { id: #id, kernel: include_bytes!(#kernel), dtb: #dtb, @@ -296,22 +296,8 @@ fn generate_guest_img_loading_functions( } let output = quote! { - /// One guest image data from memory. - pub struct MemoryImage{ - /// vm id in config file - pub id: usize, - /// kernel image - pub kernel: &'static [u8], - /// dtb image - pub dtb: Option<&'static [u8]>, - /// bios image - pub bios: Option<&'static [u8]>, - /// ramdisk image - pub ramdisk: Option<&'static [u8]>, - } - /// Get memory images from config file. - pub fn get_memory_images() -> &'static [MemoryImage] { + pub fn get_memory_images() -> &'static [axvm::boot::StaticVmImage] { &[ #(#memory_images),* ] @@ -337,25 +323,20 @@ fn generate_firmware_img_loading_functions( let bios = bios.display().to_string(); firmware_images.push(quote! { - FirmwareImage { + axvm::boot::StaticVmImage { id: #id, - bios: include_bytes!(#bios), + kernel: &[], + dtb: None, + bios: Some(include_bytes!(#bios)), + ramdisk: None, } }); } } let output = quote! { - /// One guest firmware image loaded from the build host. - pub struct FirmwareImage { - /// vm id in config file - pub id: usize, - /// boot firmware image - pub bios: &'static [u8], - } - /// Get firmware images from config file. - pub fn get_firmware_images() -> &'static [FirmwareImage] { + pub fn get_firmware_images() -> &'static [axvm::boot::StaticVmImage] { &[ #(#firmware_images),* ] diff --git a/os/axvisor/src/config.rs b/os/axvisor/src/config.rs index 90afd8c368..2dcc1d7bae 100644 --- a/os/axvisor/src/config.rs +++ b/os/axvisor/src/config.rs @@ -22,10 +22,11 @@ use core::sync::atomic::{AtomicBool, Ordering}; use ax_errno::{AxResult, ax_err_type}; #[cfg(all(feature = "fs", target_arch = "x86_64"))] use axvm::InterruptTriggerMode; -#[cfg(any(target_arch = "x86_64", target_arch = "loongarch64"))] +#[cfg(target_arch = "x86_64")] use axvm::config::VMBootProtocol; use axvm::{ AxVM, GuestPhysAddr, + boot::{BootImageProvider, ImageLoader, StaticVmImage, get_image_header}, config::{ AxVCpuConfig, AxVMConfig, AxVMConfigParams, GuestBootPolicy, PhysCpuList, RamdiskInfo, VMImageConfig, @@ -33,13 +34,12 @@ use axvm::{ }; use axvmconfig::{AxVMCrateConfig, VMType}; -#[cfg(any( - target_arch = "aarch64", - target_arch = "loongarch64", - target_arch = "riscv64" -))] -use crate::fdt::*; -use crate::images::ImageLoader; +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] +use axvm::boot::handle_fdt_operations; +#[cfg(target_arch = "x86_64")] +use axvm::boot::is_x86_linux_image_config; +#[cfg(target_arch = "loongarch64")] +use axvm::boot::{handle_fdt_operations, init_guest_boot_resources}; /// Default BIOS load GPA for x86_64 built-in BIOS. #[cfg(target_arch = "x86_64")] @@ -119,6 +119,7 @@ pub fn init_guest_vms() { } pub fn init_guest_vm(raw_cfg: &str) -> AxResult { + let image_provider = AxvisorBootImageProvider; #[allow(unused_mut)] let mut vm_create_config = AxVMCrateConfig::from_toml(raw_cfg) .map_err(|e| ax_err_type!(InvalidData, format!("Failed to resolve VM config: {e:?}")))?; @@ -129,7 +130,7 @@ pub fn init_guest_vm(raw_cfg: &str) -> AxResult { ))] let release_host_filesystem = vm_config_needs_host_filesystem_release(&vm_create_config); - if let Some(linux) = super::images::get_image_header(&vm_create_config) { + if let Some(linux) = get_image_header(&vm_create_config, &image_provider) { debug!( "VM[{}] Linux header: {:#x?}", vm_create_config.base.id, linux @@ -141,7 +142,7 @@ pub fn init_guest_vm(raw_cfg: &str) -> AxResult { // Handle FDT-related operations for architectures that boot guests with DTB. #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] - let guest_dtb = handle_fdt_operations(&mut vm_config, &mut vm_create_config)?; + let guest_dtb = handle_fdt_operations(&mut vm_config, &mut vm_create_config, &image_provider)?; #[cfg(target_arch = "loongarch64")] handle_fdt_operations(&mut vm_config, &mut vm_create_config)?; @@ -171,9 +172,15 @@ pub fn init_guest_vm(raw_cfg: &str) -> AxResult { info!("VM[{}] created success, loading images...", vm.id()); #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] - let mut loader = ImageLoader::new(main_mem, vm_create_config, vm.clone(), guest_dtb); + let mut loader = ImageLoader::new( + main_mem, + vm_create_config, + vm.clone(), + &image_provider, + guest_dtb, + ); #[cfg(not(any(target_arch = "aarch64", target_arch = "riscv64")))] - let mut loader = ImageLoader::new(main_mem, vm_create_config, vm.clone()); + let mut loader = ImageLoader::new(main_mem, vm_create_config, vm.clone(), &image_provider); loader.load()?; vm.prepare() @@ -214,12 +221,6 @@ pub(crate) fn build_axvm_config(cfg: &AxVMCrateConfig) -> AxVMConfig { cpu_config: AxVCpuConfig { bsp_entry: GuestPhysAddr::from(cfg.kernel.entry_point), ap_entry: GuestPhysAddr::from(cfg.kernel.entry_point), - #[cfg(target_arch = "loongarch64")] - boot_args: [0; 3], - #[cfg(target_arch = "loongarch64")] - boot_stack_top: 0, - #[cfg(target_arch = "loongarch64")] - firmware_boot: cfg.kernel.effective_boot_protocol() == VMBootProtocol::Uefi, }, image_config: VMImageConfig { kernel_load_gpa: GuestPhysAddr::from(cfg.kernel.kernel_load_addr), @@ -301,7 +302,7 @@ pub fn host_filesystem_release_required() -> bool { #[cfg(all(feature = "fs", target_arch = "x86_64"))] fn register_x86_host_fs_passthrough_irq_route() { - let (_, _, _, guest_gsi) = crate::images::x86_qemu_passthrough_block_intx(); + let (_, _, _, guest_gsi) = axvm::boot::x86_qemu_passthrough_block_intx(); let info = x86_host_fs_passthrough_pci_info(); let route = match ax_driver::pci::resolve_intx_binding(info) { @@ -372,7 +373,7 @@ fn unmask_x86_host_fs_passthrough_intx() { fn x86_host_fs_passthrough_pci_info() -> ax_driver::probe::pci::PciInfo { use ax_driver::probe::pci::{PciAddress, PciInfo, PciIntxRoute}; - let (device, function, pin, _) = crate::images::x86_qemu_passthrough_block_intx(); + let (device, function, pin, _) = axvm::boot::x86_qemu_passthrough_block_intx(); PciInfo { address: PciAddress::new(0, 0, device, function), interrupt_pin: pin, @@ -420,7 +421,35 @@ fn x86_intx_forwarding_trigger(binding: &ax_driver::BindingIrq) -> InterruptTrig #[cfg(target_arch = "x86_64")] fn x86_linux_direct_boot_config(config: &AxVMCrateConfig) -> bool { - crate::images::is_x86_linux_image_config(config) + is_x86_linux_image_config(config, &AxvisorBootImageProvider) +} + +struct AxvisorBootImageProvider; + +impl BootImageProvider for AxvisorBootImageProvider { + fn static_vm_images(&self) -> &'static [StaticVmImage] { + vmcfg::get_memory_images() + } + + #[cfg(target_arch = "loongarch64")] + fn static_firmware_images(&self) -> &'static [StaticVmImage] { + vmcfg::get_firmware_images() + } + + #[cfg(feature = "fs")] + fn read_file(&self, file_name: &str) -> AxResult> { + crate::manager::AxvmManager::read_file(file_name) + } + + #[cfg(feature = "fs")] + fn read_file_exact(&self, file_name: &str, read_size: usize) -> AxResult> { + crate::manager::AxvmManager::read_file_exact(file_name, read_size) + } + + #[cfg(feature = "fs")] + fn file_size(&self, file_name: &str) -> AxResult { + crate::manager::AxvmManager::file_size(file_name) + } } #[cfg(test)] diff --git a/os/axvisor/src/main.rs b/os/axvisor/src/main.rs index 72617b2208..a1679a2990 100644 --- a/os/axvisor/src/main.rs +++ b/os/axvisor/src/main.rs @@ -35,15 +35,6 @@ use ax_std as _; extern crate ax_std as std; mod config; -#[cfg(any( - target_arch = "aarch64", - target_arch = "loongarch64", - target_arch = "riscv64" -))] -mod fdt; -#[cfg(target_arch = "loongarch64")] -mod guest_platform; -mod images; mod manager; mod shell; diff --git a/os/axvisor/src/manager.rs b/os/axvisor/src/manager.rs index fb17ab8528..d2ba028472 100644 --- a/os/axvisor/src/manager.rs +++ b/os/axvisor/src/manager.rs @@ -228,7 +228,7 @@ impl AxvmManager { #[cfg(target_arch = "loongarch64")] pub(crate) fn register_loongarch_passthrough_irq_routes(vm_id: VMId) { - let routes = crate::guest_platform::loongarch64::get_guest_irq_routes(vm_id); + let routes = axvm::boot::guest_platform::loongarch64::get_guest_irq_routes(vm_id); if routes.is_empty() { if let Some(vm) = axvm::get_vm_by_id(vm_id) { let passthrough = vm.with_config(|cfg| !cfg.pass_through_devices().is_empty()); diff --git a/os/axvisor/src/shell/command/vm.rs b/os/axvisor/src/shell/command/vm.rs index 94e0b58734..041130b187 100644 --- a/os/axvisor/src/shell/command/vm.rs +++ b/os/axvisor/src/shell/command/vm.rs @@ -20,7 +20,7 @@ use std::{ vec::Vec, }; -use axvm::{StopReason, VCpuState, VmStatus}; +use axvm::{StopReason, VmStatus, VmVcpuState}; #[cfg(feature = "fs")] use std::fs::read_to_string; @@ -429,11 +429,11 @@ fn suspend_vm_by_id(vm_id: usize) { // Check if all VCpus are in blocked state if let Some(vm) = crate::manager::AxvmManager::vm_by_id(vm_id) { let vcpu_states: Vec<_> = - vm.vcpu_list().iter().map(|vcpu| vcpu.state()).collect(); + vm.vcpu_snapshots().iter().map(|vcpu| vcpu.state).collect(); let blocked_count = vcpu_states .iter() - .filter(|s| matches!(s, VCpuState::Blocked)) + .filter(|s| matches!(s, VmVcpuState::Blocked)) .count(); if blocked_count == vcpu_states.len() { @@ -709,22 +709,22 @@ fn vm_list(cmd: &ParsedCommand) { // Get VCpu ID list let vcpu_ids: Vec = vm - .vcpu_list() + .vcpu_snapshots() .iter() - .map(|vcpu| vcpu.id().to_string()) + .map(|vcpu| vcpu.id.to_string()) .collect(); let vcpu_id_list = vcpu_ids.join(","); // Get VCpu state summary let mut state_counts = std::collections::BTreeMap::new(); - for vcpu in vm.vcpu_list() { - let state = match vcpu.state() { - VCpuState::Free => "Free", - VCpuState::Running => "Run", - VCpuState::Blocked => "Blk", - VCpuState::Invalid => "Inv", - VCpuState::Created => "Cre", - VCpuState::Ready => "Rdy", + for vcpu in vm.vcpu_snapshots() { + let state = match vcpu.state { + VmVcpuState::Free => "Free", + VmVcpuState::Running => "Run", + VmVcpuState::Blocked => "Blk", + VmVcpuState::Invalid => "Inv", + VmVcpuState::Created => "Cre", + VmVcpuState::Ready => "Rdy", }; *state_counts.entry(state).or_insert(0) += 1; } @@ -820,14 +820,14 @@ fn show_vm_basic_details(vm_id: usize, show_config: bool, show_stats: bool) { println!(); println!("VCPU Summary:"); let mut state_counts = std::collections::BTreeMap::new(); - for vcpu in vm.vcpu_list() { - let state = match vcpu.state() { - VCpuState::Free => "Free", - VCpuState::Running => "Running", - VCpuState::Blocked => "Blocked", - VCpuState::Invalid => "Invalid", - VCpuState::Created => "Created", - VCpuState::Ready => "Ready", + for vcpu in vm.vcpu_snapshots() { + let state = match vcpu.state { + VmVcpuState::Free => "Free", + VmVcpuState::Running => "Running", + VmVcpuState::Blocked => "Blocked", + VmVcpuState::Invalid => "Invalid", + VmVcpuState::Created => "Created", + VmVcpuState::Ready => "Ready", }; *state_counts.entry(state).or_insert(0) += 1; } @@ -891,9 +891,9 @@ fn show_vm_full_details(vm_id: usize) { // Calculate total memory let total_memory: usize = vm.memory_regions().iter().map(|region| region.size()).sum(); println!(" Memory: {}", format_memory_size(total_memory)); - match vm.ept_root() { - Ok(root) => println!(" EPT Root: {:#x}", root.as_usize()), - Err(err) => println!(" EPT Root: unavailable ({:?})", err), + match vm.nested_page_table_root() { + Ok(root) => println!(" NPT Root: {:#x}", root.as_usize()), + Err(err) => println!(" NPT Root: unavailable ({:?})", err), } // Add state-specific information @@ -928,14 +928,14 @@ fn show_vm_full_details(vm_id: usize) { // Count VCpu states for summary let mut state_counts = std::collections::BTreeMap::new(); - for vcpu in vm.vcpu_list() { - let state = match vcpu.state() { - VCpuState::Free => "Free", - VCpuState::Running => "Running", - VCpuState::Blocked => "Blocked", - VCpuState::Invalid => "Invalid", - VCpuState::Created => "Created", - VCpuState::Ready => "Ready", + for vcpu in vm.vcpu_snapshots() { + let state = match vcpu.state { + VmVcpuState::Free => "Free", + VmVcpuState::Running => "Running", + VmVcpuState::Blocked => "Blocked", + VmVcpuState::Invalid => "Invalid", + VmVcpuState::Created => "Created", + VmVcpuState::Ready => "Ready", }; *state_counts.entry(state).or_insert(0) += 1; } @@ -948,25 +948,23 @@ fn show_vm_full_details(vm_id: usize) { println!(" Summary: {}", summary.join(", ")); println!(); - for vcpu in vm.vcpu_list() { - let vcpu_state = match vcpu.state() { - VCpuState::Free => "Free", - VCpuState::Running => "Running", - VCpuState::Blocked => "Blocked", - VCpuState::Invalid => "Invalid", - VCpuState::Created => "Created", - VCpuState::Ready => "Ready", + for vcpu in vm.vcpu_snapshots() { + let vcpu_state = match vcpu.state { + VmVcpuState::Free => "Free", + VmVcpuState::Running => "Running", + VmVcpuState::Blocked => "Blocked", + VmVcpuState::Invalid => "Invalid", + VmVcpuState::Created => "Created", + VmVcpuState::Ready => "Ready", }; - if let Some(phys_cpu_set) = vcpu.phys_cpu_set() { + if let Some(phys_cpu_set) = vcpu.phys_cpu_set { println!( " VCPU {}: {} (Affinity: {:#x})", - vcpu.id(), - vcpu_state, - phys_cpu_set + vcpu.id, vcpu_state, phys_cpu_set ); } else { - println!(" VCPU {}: {} (No affinity)", vcpu.id(), vcpu_state); + println!(" VCPU {}: {} (No affinity)", vcpu.id, vcpu_state); } } diff --git a/os/axvisor/src/vmm/mod.rs b/os/axvisor/src/vmm/mod.rs deleted file mode 100644 index d7006862d7..0000000000 --- a/os/axvisor/src/vmm/mod.rs +++ /dev/null @@ -1,173 +0,0 @@ -// Copyright 2025 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. - -mod hvc; -mod ivc; - -pub mod config; -pub mod devices; -pub mod images; -pub mod timer; -pub mod vcpus; -pub mod vm_list; - -#[cfg(any( - target_arch = "aarch64", - target_arch = "loongarch64", - target_arch = "riscv64" -))] -pub mod fdt; - -use core::sync::atomic::{AtomicUsize, Ordering}; -use std::os::arceos::{ - api::task::{self, AxWaitQueueHandle}, - modules::ax_task, -}; - -use ax_errno::{AxResult, ax_err_type}; - -use crate::task::AsVCpuTask; -pub use timer::init_percpu as init_timer_percpu; - -/// The instantiated VM type. -pub type VM = axvm::AxVM; -/// The instantiated VM ref type (by `Arc`). -pub type VMRef = axvm::AxVMRef; -/// The instantiated VCpu ref type (by `Arc`). -pub type VCpuRef = axvm::AxVCpuRef; - -static VMM: AxWaitQueueHandle = AxWaitQueueHandle::new(); - -/// The number of running VMs. This is used to determine when to exit the VMM. -static RUNNING_VM_COUNT: AtomicUsize = AtomicUsize::new(0); - -/// Initialize the VMM. -/// -/// This function creates the VM structures. Runtime vCPU tasks are now created -/// by the VM lifecycle state machine when a VM starts. -pub fn init() { - info!("Initializing VMM..."); - // Initialize guest VM according to config file. - config::init_guest_vms(); - - #[cfg(all(feature = "fs", target_arch = "x86_64"))] - release_host_filesystem_for_guest_passthrough().expect( - "Failed to release host filesystem before guest passthrough devices take ownership", - ); -} - -#[cfg(all(feature = "fs", target_arch = "x86_64"))] -fn release_host_filesystem_for_guest_passthrough() -> AxResult { - use std::os::arceos::modules::ax_fs_ng; - - let has_conflicting_guest_ownership = vm_list::get_vm_list() - .into_iter() - .any(|vm| vm.has_host_fs_passthrough_conflict()); - if !has_conflicting_guest_ownership { - return Ok(()); - } - - ax_fs_ng::shutdown_filesystems()?; - crate::config::prepare_x86_host_fs_passthrough_devices(); - info!("Host filesystem cleanly unmounted before guest passthrough devices start"); - Ok(()) -} - -/// Start the VMM. -pub fn start() { - info!("VMM starting, booting VMs..."); - for vm in vm_list::get_vm_list() { - match vm.start() { - Ok(_) => { - vcpus::notify_primary_vcpu(vm.id()); - RUNNING_VM_COUNT.fetch_add(1, Ordering::Release); - info!("VM[{}] boot success", vm.id()) - } - Err(err) => warn!("VM[{}] boot failed, error {:?}", vm.id(), err), - } - } - - // Do not exit until all VMs are stopped. - task::ax_wait_queue_wait_until( - &VMM, - || { - let vm_count = RUNNING_VM_COUNT.load(Ordering::Acquire); - debug!("a VM exited, current running VM count: {vm_count}"); - vm_count == 0 - }, - None, - ); -} - -#[allow(unused_imports)] -pub use vcpus::with_vcpu_task; - -/// Run a closure with the specified VM. -pub fn with_vm(vm_id: usize, f: impl FnOnce(VMRef) -> T) -> Option { - let vm = vm_list::get_vm_by_id(vm_id)?; - Some(f(vm)) -} - -/// Run a closure with the specified VM and vCPU. -pub fn with_vm_and_vcpu( - vm_id: usize, - vcpu_id: usize, - f: impl FnOnce(VMRef, VCpuRef) -> T, -) -> Option { - let vm = vm_list::get_vm_by_id(vm_id)?; - let vcpu = vm.vcpu(vcpu_id)?; - - Some(f(vm, vcpu)) -} - -/// Run a closure with the specified VM and vCPU, with the guarantee that the closure will be -/// executed on the physical CPU where the vCPU is running, waiting, or queueing. -/// -/// TODO: It seems necessary to disable scheduling when running the closure. -pub fn with_vm_and_vcpu_on_pcpu( - vm_id: usize, - vcpu_id: usize, - f: impl FnOnce(VMRef, VCpuRef) + 'static, -) -> AxResult { - // Disables preemption and IRQs to prevent the current task from being preempted or re-scheduled. - let guard = ax_kernel_guard::NoPreemptIrqSave::new(); - - let current_vm = ax_task::current().as_vcpu_task().vm().id(); - let current_vcpu = ax_task::current().as_vcpu_task().vcpu.id(); - - // The target vCPU is the current task, execute the closure directly. - if current_vm == vm_id && current_vcpu == vcpu_id { - with_vm_and_vcpu(vm_id, vcpu_id, f).ok_or_else(|| ax_err_type!(NotFound))?; - return Ok(()); - } - - // The target vCPU is not the current task, send an IPI to the target physical CPU. - drop(guard); - - let _pcpu_id = vcpus::with_vcpu_task(vm_id, vcpu_id, |task| task.cpu_id()) - .ok_or_else(|| ax_err_type!(NotFound))?; - - ax_errno::ax_err!( - Unsupported, - "cross-CPU vCPU closure dispatch is not implemented" - ) -} - -pub fn add_running_vm_count(count: usize) { - RUNNING_VM_COUNT.fetch_add(count, Ordering::Release); -} - -pub fn sub_running_vm_count(count: usize) { - RUNNING_VM_COUNT.fetch_sub(count, Ordering::Release); -} diff --git a/virtualization/arm_vcpu/src/exception.rs b/virtualization/arm_vcpu/src/exception.rs index 9b581d299f..e19b21a57f 100644 --- a/virtualization/arm_vcpu/src/exception.rs +++ b/virtualization/arm_vcpu/src/exception.rs @@ -14,7 +14,7 @@ use aarch64_cpu::registers::{ESR_EL2, HCR_EL2, Readable, SCTLR_EL1, VTCR_EL2, VTTBR_EL2}; use ax_errno::{AxError, AxResult}; -use axvm_types::{AccessWidth, AxVCpuExitReason, GuestPhysAddr, SysRegAddr}; +use axvm_types::{AccessWidth, GuestPhysAddr, SysRegAddr, VmExit}; use log::error; use crate::{ @@ -74,7 +74,7 @@ core::arch::global_asm!( /// /// # Returns /// -/// An `AxResult` containing an `AxVCpuExitReason` indicating the reason for the VM exit. +/// An `AxResult` containing an `VmExit` indicating the reason for the VM exit. /// This could be due to a hypervisor call (`Hypercall`) or other reasons such as data aborts. /// /// # Panics @@ -82,7 +82,7 @@ core::arch::global_asm!( /// If an unhandled exception class is encountered, the function will panic, outputting /// details about the exception including the instruction pointer, faulting address, exception /// syndrome register (ESR), and system control registers. -pub fn handle_exception_sync(ctx: &mut TrapFrame) -> AxResult { +pub fn handle_exception_sync(ctx: &mut TrapFrame) -> AxResult { match exception_class() { Some(ESR_EL2::EC::Value::DataAbortLowerEL) => { let elr = ctx.exception_pc(); @@ -106,7 +106,7 @@ pub fn handle_exception_sync(ctx: &mut TrapFrame) -> AxResult // And arm64 hcall implementation uses `x0` to specify the hcall number. // For more details on the hypervisor call (HVC) mechanism and the use of general-purpose registers, // refer to the [Linux Kernel documentation on KVM ARM hypervisor ABI](https://github.com/torvalds/linux/blob/master/Documentation/virt/kvm/arm/hyp-abi.rst). - Ok(AxVCpuExitReason::Hypercall { + Ok(VmExit::Hypercall { nr: ctx.gpr[0], args: [ ctx.gpr[1], ctx.gpr[2], ctx.gpr[3], ctx.gpr[4], ctx.gpr[5], ctx.gpr[6], @@ -138,7 +138,7 @@ pub fn handle_exception_sync(ctx: &mut TrapFrame) -> AxResult } } -fn handle_data_abort(context_frame: &mut TrapFrame) -> AxResult { +fn handle_data_abort(context_frame: &mut TrapFrame) -> AxResult { let addr = exception_fault_addr()?; let access_width = exception_data_abort_access_width(); let is_write = exception_data_abort_access_is_write(); @@ -180,13 +180,13 @@ fn handle_data_abort(context_frame: &mut TrapFrame) -> AxResult AxResult` - An `AxResult` containing an `AxVCpuExitReason` indicating +/// * `AxResult` - An `AxResult` containing an `VmExit` indicating /// whether the operation was a read or write and the relevant details. -fn handle_system_register(context_frame: &mut TrapFrame) -> AxResult { +fn handle_system_register(context_frame: &mut TrapFrame) -> AxResult { let iss = ESR_EL2.read(ESR_EL2::ISS); let addr = exception_sysreg_addr(iss.try_into().unwrap()); @@ -216,12 +216,12 @@ fn handle_system_register(context_frame: &mut TrapFrame) -> AxResult AxResult Option> { +fn handle_psci_call(ctx: &mut TrapFrame) -> Option> { const PSCI_FN_RANGE_32: core::ops::RangeInclusive = 0x8400_0000..=0x8400_001F; const PSCI_FN_RANGE_64: core::ops::RangeInclusive = 0xC400_0000..=0xC400_001F; @@ -257,13 +257,13 @@ fn handle_psci_call(ctx: &mut TrapFrame) -> Option> { }; match fn_offset { - Some(PSCI_FN_CPU_OFF) => Some(Ok(AxVCpuExitReason::CpuDown { _state: ctx.gpr[1] })), - Some(PSCI_FN_CPU_ON) => Some(Ok(AxVCpuExitReason::CpuUp { + Some(PSCI_FN_CPU_OFF) => Some(Ok(VmExit::CpuDown { _state: ctx.gpr[1] })), + Some(PSCI_FN_CPU_ON) => Some(Ok(VmExit::CpuUp { target_cpu: ctx.gpr[1], entry_point: GuestPhysAddr::from(ctx.gpr[2] as usize), arg: ctx.gpr[3], })), - Some(PSCI_FN_SYSTEM_OFF) => Some(Ok(AxVCpuExitReason::SystemDown)), + Some(PSCI_FN_SYSTEM_OFF) => Some(Ok(VmExit::SystemDown)), // We just forward these request to the ATF directly. Some(PSCI_FN_VERSION..PSCI_FN_END) => None, _ => None, @@ -274,7 +274,7 @@ fn handle_psci_call(ctx: &mut TrapFrame) -> Option> { /// /// This function will judge if the SMC call is a PSCI call, if so, it will handle it as a PSCI call. /// Otherwise, it will forward the SMC call to the ATF directly. -fn handle_smc64_exception(ctx: &mut TrapFrame) -> AxResult { +fn handle_smc64_exception(ctx: &mut TrapFrame) -> AxResult { // Is this a psci call? if let Some(result) = handle_psci_call(ctx) { result @@ -283,7 +283,7 @@ fn handle_smc64_exception(ctx: &mut TrapFrame) -> AxResult { // The args are from lower EL, so it is safe to call the ATF. (ctx.gpr[0], ctx.gpr[1], ctx.gpr[2], ctx.gpr[3]) = unsafe { crate::smc::smc_call(ctx.gpr[0], ctx.gpr[1], ctx.gpr[2], ctx.gpr[3]) }; - Ok(AxVCpuExitReason::Nothing) + Ok(VmExit::Nothing) } } @@ -292,7 +292,7 @@ fn handle_smc64_exception(ctx: &mut TrapFrame) -> AxResult { /// which is provided by the host callback. #[unsafe(no_mangle)] fn current_el_irq_handler(_tf: &mut TrapFrame) { - // TODO: consider if returning AxVCpuExitReason::ExternalInterrupt (or another enum variant) is + // TODO: consider if returning VmExit::ExternalInterrupt (or another enum variant) is // better than directly calling the handler here. crate::host::handle_irq() } diff --git a/virtualization/arm_vcpu/src/vcpu.rs b/virtualization/arm_vcpu/src/vcpu.rs index 64041642fc..d2e2a20a80 100644 --- a/virtualization/arm_vcpu/src/vcpu.rs +++ b/virtualization/arm_vcpu/src/vcpu.rs @@ -14,7 +14,7 @@ use aarch64_cpu::registers::*; use ax_errno::AxResult; -use axvm_types::{AxVCpuExitReason, GuestPhysAddr, HostPhysAddr, SysRegAddr, VmArchVcpuOps}; +use axvm_types::{GuestPhysAddr, HostPhysAddr, SysRegAddr, VmArchVcpuOps, VmExit}; use crate::{ TrapFrame, @@ -110,13 +110,13 @@ impl axvm_types::VmArchVcpuOps for Aarch64VCpu { Ok(()) } - fn set_ept_root(&mut self, ept_root: HostPhysAddr) -> AxResult { - debug!("set vcpu ept root:{ept_root:#x}"); - self.guest_system_regs.vttbr_el2 = ept_root.as_usize() as u64; + fn set_nested_page_table_root(&mut self, nested_page_table_root: HostPhysAddr) -> AxResult { + debug!("set vcpu stage-2 root:{nested_page_table_root:#x}"); + self.guest_system_regs.vttbr_el2 = nested_page_table_root.as_usize() as u64; Ok(()) } - fn run(&mut self) -> AxResult { + fn run(&mut self) -> AxResult { unsafe { core::arch::asm!("msr daifset, #2"); } @@ -302,10 +302,10 @@ impl Aarch64VCpu { /// - `exit_reason`: The reason why the VM-Exit happened in [`TrapKind`]. /// /// Returns: - /// - [`AxVCpuExitReason`]: a wrappered VM-Exit reason needed to be handled by the hypervisor. + /// - [`VmExit`]: a wrappered VM-Exit reason needed to be handled by the hypervisor. /// /// This function may panic for unhandled exceptions. - fn vmexit_handler(&mut self, exit_reason: TrapKind) -> AxResult { + fn vmexit_handler(&mut self, exit_reason: TrapKind) -> AxResult { trace!( "Aarch64VCpu vmexit_handler() esr:{:#x} ctx:{:#x?}", exception_class_value(), @@ -327,14 +327,14 @@ impl Aarch64VCpu { let result = match exit_reason { TrapKind::Synchronous => handle_exception_sync(&mut self.ctx), - TrapKind::Irq => Ok(AxVCpuExitReason::ExternalInterrupt { + TrapKind::Irq => Ok(VmExit::ExternalInterrupt { vector: crate::host::fetch_irq() as u64, }), _ => panic!("Unhandled exception {:?}", exit_reason), }; match result { - Ok(AxVCpuExitReason::SysRegRead { addr, reg }) => { + Ok(VmExit::SysRegRead { addr, reg }) => { if let Some(exit_reason) = self.builtin_sysreg_access_handler(addr, false, 0, reg)? { @@ -343,7 +343,7 @@ impl Aarch64VCpu { result } - Ok(AxVCpuExitReason::SysRegWrite { addr, value }) => { + Ok(VmExit::SysRegWrite { addr, value }) => { if let Some(exit_reason) = self.builtin_sysreg_access_handler(addr, true, value, 0)? { @@ -365,7 +365,7 @@ impl Aarch64VCpu { write: bool, value: u64, reg: usize, - ) -> AxResult> { + ) -> AxResult> { const SYSREG_ICC_SGI1R_EL1: SysRegAddr = SysRegAddr::new(0x3A_3016); // ICC_SGI1R_EL1 match (addr, write) { @@ -381,7 +381,7 @@ impl Aarch64VCpu { if irm { debug!("arm_vcpu ICC_SGI1R_EL1 write: irm == 1, send to all except self"); - return Ok(Some(AxVCpuExitReason::SendIPI { + return Ok(Some(VmExit::SendIPI { target_cpu: 0, target_cpu_aux: 0, send_to_all: true, @@ -400,7 +400,7 @@ impl Aarch64VCpu { intid:{intid:#x} target_list:{target_list:#x}" ); - Ok(Some(AxVCpuExitReason::SendIPI { + Ok(Some(VmExit::SendIPI { target_cpu: (aff3 << 24) | (aff2 << 16) | (aff1 << 8), target_cpu_aux: target_list, send_to_all: false, @@ -411,7 +411,7 @@ impl Aarch64VCpu { (SYSREG_ICC_SGI1R_EL1, false) => { // ICC_SGI1R_EL1 is WO, we take it as RAZ. self.set_gpr(reg, 0); - Ok(Some(AxVCpuExitReason::Nothing)) + Ok(Some(VmExit::Nothing)) } _ => { // If the system register access is not handled by the VCpu itself, diff --git a/virtualization/axvm-types/src/lib.rs b/virtualization/axvm-types/src/lib.rs index 89cde21203..9fb7807e42 100644 --- a/virtualization/axvm-types/src/lib.rs +++ b/virtualization/axvm-types/src/lib.rs @@ -354,9 +354,6 @@ pub enum VmExit { }, } -/// Backward-compatible name for the VM-level exit reason. -pub type AxVCpuExitReason = VmExit; - /// Architecture-specific vCPU operations consumed by AxVM. pub trait VmArchVcpuOps: Sized { /// Architecture-specific creation configuration. @@ -369,7 +366,7 @@ pub trait VmArchVcpuOps: Sized { /// Sets the guest entry point. fn set_entry(&mut self, entry: GuestPhysAddr) -> AxVmResult; /// Sets the nested page table root. - fn set_ept_root(&mut self, ept_root: HostPhysAddr) -> AxVmResult; + fn set_nested_page_table_root(&mut self, nested_page_table_root: HostPhysAddr) -> AxVmResult; /// Completes architecture-specific setup. fn setup(&mut self, config: Self::SetupConfig) -> AxVmResult; /// Runs the vCPU until a VM exit. @@ -410,11 +407,6 @@ pub trait VmArchVcpuOps: Sized { fn set_return_value(&mut self, val: usize); } -/// Backward-compatible name for the architecture vCPU trait. -pub trait AxArchVCpu: VmArchVcpuOps {} - -impl AxArchVCpu for T {} - /// Architecture-specific per-CPU virtualization state consumed by AxVM. pub trait VmArchPerCpuOps: Sized { /// Creates a new per-CPU state. @@ -431,11 +423,6 @@ pub trait VmArchPerCpuOps: Sized { } } -/// Backward-compatible name for the architecture per-CPU trait. -pub trait AxArchPerCpu: VmArchPerCpuOps {} - -impl AxArchPerCpu for T {} - /// Execution state of an AxVM-owned vCPU wrapper. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum VmVcpuState { @@ -453,9 +440,6 @@ pub enum VmVcpuState { Blocked = 5, } -/// Backward-compatible name for vCPU state. -pub type VCpuState = VmVcpuState; - /// A part of `AxVMConfig`, which represents guest VM type. #[derive(Default, Clone, Copy, PartialEq, Eq, Debug)] pub enum VMType { @@ -722,7 +706,7 @@ mod tests { Ok(()) } - fn set_ept_root(&mut self, _ept_root: HostPhysAddr) -> AxVmResult { + fn set_nested_page_table_root(&mut self, _root: HostPhysAddr) -> AxVmResult { Ok(()) } @@ -763,7 +747,8 @@ mod tests { let mut vcpu = MockVcpu::new(1, 0, ()).unwrap(); vcpu.set_entry(GuestPhysAddr::from(0x8020_0000)).unwrap(); - vcpu.set_ept_root(HostPhysAddr::from(0x1000)).unwrap(); + vcpu.set_nested_page_table_root(HostPhysAddr::from(0x1000)) + .unwrap(); vcpu.setup(()).unwrap(); assert!(matches!( vcpu.run().unwrap(), diff --git a/virtualization/axvm/Cargo.toml b/virtualization/axvm/Cargo.toml index 2b65fd7113..9f7934e785 100644 --- a/virtualization/axvm/Cargo.toml +++ b/virtualization/axvm/Cargo.toml @@ -55,6 +55,9 @@ axvm-types = { workspace = true } axhvc = { workspace = true } axdevice = { workspace = true } axdevice_base = { workspace = true } +axvmconfig = { workspace = true, default-features = false } +fdt-parser = "0.4" +hashbrown = "0.14" [target.'cfg(target_arch = "x86_64")'.dependencies] x86_vcpu = { workspace = true } x86_vlapic = { workspace = true } @@ -63,6 +66,7 @@ axplat-dyn = { workspace = true, optional = true, default-features = false } riscv_vcpu = { workspace = true } riscv_vplic = { workspace = true } [target.'cfg(target_arch = "loongarch64")'.dependencies] +ax-driver = { workspace = true } ax-plat = { workspace = true } loongarch_vcpu = { workspace = true } [target.'cfg(target_arch = "aarch64")'.dependencies] diff --git a/virtualization/axvm/src/arch.rs b/virtualization/axvm/src/arch.rs deleted file mode 100644 index 58350aba9a..0000000000 --- a/virtualization/axvm/src/arch.rs +++ /dev/null @@ -1,350 +0,0 @@ -//! Architecture component glue owned by AxVM. - -#[cfg(target_arch = "x86_64")] -mod x86_64 { - use alloc::boxed::Box; - use core::time::Duration; - - use ax_crate_interface::impl_interface; - use ax_errno::AxResult; - use ax_memory_addr::{PhysAddr, VirtAddr}; - use axvm_types::{InterruptVector, VCpuId, VMId}; - use x86_vcpu::host::X86VcpuHostIf; - use x86_vlapic::host::X86VlapicHostIf; - - use crate::{ - host::{HostConsole, HostMemory, HostTime, default_host}, - manager, - vcpu::{AxArchVCpuImpl, get_current_vcpu}, - }; - - struct X86VcpuHostIfImpl; - - #[impl_interface] - impl X86VcpuHostIf for X86VcpuHostIfImpl { - fn alloc_frame() -> Option { - default_host().alloc_frame() - } - - fn dealloc_frame(paddr: PhysAddr) { - default_host().dealloc_frame(paddr); - } - - fn alloc_contiguous_frames(frame_count: usize, frame_align: usize) -> Option { - default_host().alloc_contiguous_frames(frame_count, frame_align) - } - - fn dealloc_contiguous_frames(start_paddr: PhysAddr, frame_count: usize) { - default_host().dealloc_contiguous_frames(start_paddr, frame_count); - } - - fn phys_to_virt(paddr: PhysAddr) -> VirtAddr { - default_host().phys_to_virt(paddr) - } - - fn nanos_to_ticks(nanos: u64) -> u64 { - default_host().nanos_to_ticks(nanos) - } - } - - struct X86VlapicHostIfImpl; - - #[impl_interface] - impl X86VlapicHostIf for X86VlapicHostIfImpl { - fn alloc_frame() -> Option { - default_host().alloc_frame() - } - - fn dealloc_frame(paddr: PhysAddr) { - default_host().dealloc_frame(paddr); - } - - fn phys_to_virt(paddr: PhysAddr) -> VirtAddr { - default_host().phys_to_virt(paddr) - } - - fn virt_to_phys(vaddr: VirtAddr) -> PhysAddr { - default_host().virt_to_phys(vaddr) - } - - fn current_time() -> Duration { - default_host().monotonic_time() - } - - fn current_time_nanos() -> u64 { - default_host().monotonic_time().as_nanos() as u64 - } - - fn register_timer( - deadline: Duration, - callback: Box, - ) -> usize { - default_host().register_timer(deadline.as_nanos() as u64, callback) - } - - fn cancel_timer(token: usize) { - default_host().cancel_timer(token); - } - - fn write_bytes(bytes: &[u8]) { - default_host().write_bytes(bytes); - } - - fn read_bytes(bytes: &mut [u8]) -> usize { - default_host().read_bytes(bytes) - } - - fn current_vm_id() -> VMId { - get_current_vcpu::() - .expect("current x86 vCPU is not set") - .vm_id() - } - - fn current_vm_vcpu_num() -> usize { - let vm_id = Self::current_vm_id(); - manager::with_vm(vm_id, |vm| vm.vcpu_num()).unwrap_or(0) - } - - fn current_vm_active_vcpus() -> usize { - manager::active_vcpu_mask(Self::current_vm_id()).unwrap_or(0) - } - - fn active_vcpus(vm_id: VMId) -> Option { - manager::active_vcpu_mask(vm_id) - } - - fn inject_interrupt(vm_id: VMId, vcpu_id: VCpuId, vector: InterruptVector) -> AxResult { - manager::inject_interrupt(vm_id, vcpu_id, vector as usize) - } - } -} - -#[cfg(target_arch = "riscv64")] -mod riscv64 { - use ax_crate_interface::impl_interface; - use ax_memory_addr::{PhysAddr, VirtAddr}; - use riscv_vcpu::host::RiscvVcpuHostIf; - use riscv_vplic::host::RiscvVplicHostIf; - - use crate::host::{HostMemory, default_host}; - - struct RiscvVcpuHostIfImpl; - - #[impl_interface] - impl RiscvVcpuHostIf for RiscvVcpuHostIfImpl { - fn virt_to_phys(vaddr: VirtAddr) -> PhysAddr { - default_host().virt_to_phys(vaddr) - } - } - - struct RiscvVplicHostIfImpl; - - #[impl_interface] - impl RiscvVplicHostIf for RiscvVplicHostIfImpl { - fn phys_to_virt(paddr: PhysAddr) -> VirtAddr { - default_host().phys_to_virt(paddr) - } - } - - #[cfg(feature = "plat-dyn")] - pub(crate) fn register_platform_irq_injector() { - axplat_dyn::register_virtual_irq_injector(inject_virtual_irq); - } - - #[cfg(not(feature = "plat-dyn"))] - compile_error!("riscv64 Axvisor requires the plat-dyn feature"); - - #[cfg(feature = "plat-dyn")] - fn inject_virtual_irq(irq_id: usize) -> bool { - debug!("injecting RISC-V virtual IRQ id: {irq_id}"); - - let Some(vm_id) = crate::current_vm_id() else { - warn!("cannot inject RISC-V virtual IRQ without current VM context"); - return false; - }; - - let Some(injected) = crate::manager::with_vm(vm_id, |vm| { - if let Err(err) = vm.pulse_interrupt(irq_id) { - warn!("failed to inject RISC-V virtual IRQ {irq_id}: {err:?}"); - return false; - } - true - }) else { - warn!("cannot inject RISC-V virtual IRQ {irq_id}: VM[{vm_id}] not found"); - return false; - }; - - injected - } -} - -#[cfg(target_arch = "loongarch64")] -mod loongarch64 { - use alloc::boxed::Box; - use core::time::Duration; - - use ax_crate_interface::impl_interface; - use ax_memory_addr::{PhysAddr, VirtAddr}; - use loongarch_vcpu::host::LoongArchVcpuHostIf; - - use crate::host::{HostMemory, HostTime, default_host}; - - struct LoongArchVcpuHostIfImpl; - - #[impl_interface] - impl LoongArchVcpuHostIf for LoongArchVcpuHostIfImpl { - fn virt_to_phys(vaddr: VirtAddr) -> PhysAddr { - default_host().virt_to_phys(vaddr) - } - - fn current_time_nanos() -> u64 { - default_host().monotonic_time().as_nanos() as u64 - } - - fn ticks_to_nanos(ticks: u64) -> u64 { - ax_std::os::arceos::modules::ax_hal::time::ticks_to_nanos(ticks) - } - - fn register_timer( - deadline: Duration, - callback: Box, - ) -> usize { - default_host().register_timer(deadline.as_nanos() as u64, callback) - } - - fn cancel_timer(token: usize) { - default_host().cancel_timer(token); - } - - fn inject_interrupt(vm_id: usize, vcpu_id: usize, vector: usize) { - if let Err(err) = crate::runtime::vcpus::queue_interrupt(vm_id, vcpu_id, vector) { - warn!( - "failed to queue LoongArch interrupt {vector:#x} for VM[{vm_id}] \ - VCpu[{vcpu_id}]: {err:?}" - ); - } - } - - fn inject_external_interrupt( - vm_id: usize, - vcpu_id: usize, - vector: usize, - physical_irq: usize, - ) { - if let Err(err) = crate::runtime::vcpus::queue_external_interrupt( - vm_id, - vcpu_id, - vector, - physical_irq, - ) { - warn!( - "failed to queue LoongArch external interrupt vector={vector:#x}, \ - physical_irq={physical_irq:#x} for VM[{vm_id}] VCpu[{vcpu_id}]: {err:?}" - ); - } - } - } - - pub(crate) fn register_platform_irq_injector() { - crate::runtime::loongarch_irq::register_platform_irq_injector(); - } -} - -#[cfg(target_arch = "riscv64")] -pub(crate) fn register_platform_irq_injector() { - riscv64::register_platform_irq_injector(); -} - -#[cfg(target_arch = "loongarch64")] -pub(crate) fn register_platform_irq_injector() { - loongarch64::register_platform_irq_injector(); -} - -#[cfg(not(any(target_arch = "riscv64", target_arch = "loongarch64")))] -pub(crate) fn register_platform_irq_injector() {} - -#[cfg(target_arch = "aarch64")] -mod aarch64 { - use alloc::boxed::Box; - use core::time::Duration; - - use arm_vcpu::host::ArmVcpuHostIf; - use arm_vgic::host::ArmVgicHostIf; - use ax_crate_interface::impl_interface; - use ax_memory_addr::{PhysAddr, VirtAddr}; - - use crate::host::{HostCpu, HostMemory, HostTime, default_host, gic}; - - struct ArmVcpuHostIfImpl; - - #[impl_interface] - impl ArmVcpuHostIf for ArmVcpuHostIfImpl { - fn hardware_inject_virtual_interrupt(vector: u8) { - gic::inject_interrupt(vector as usize); - } - - fn fetch_irq() -> usize { - gic::fetch_irq() - } - - fn handle_irq() { - gic::handle_current_irq(); - } - } - - struct ArmVgicHostIfImpl; - - #[impl_interface] - impl ArmVgicHostIf for ArmVgicHostIfImpl { - fn alloc_contiguous_frames(frame_count: usize, frame_align: usize) -> Option { - default_host().alloc_contiguous_frames(frame_count, frame_align) - } - - fn dealloc_contiguous_frames(start_paddr: PhysAddr, frame_count: usize) { - default_host().dealloc_contiguous_frames(start_paddr, frame_count); - } - - fn phys_to_virt(paddr: PhysAddr) -> VirtAddr { - default_host().phys_to_virt(paddr) - } - - fn host_cpu_num() -> usize { - default_host().cpu_count() - } - - fn current_vcpu_id() -> usize { - crate::current_vcpu_id().expect("current AArch64 vCPU is not set") - } - - fn current_time_nanos() -> u64 { - default_host().monotonic_time().as_nanos() as u64 - } - - fn register_timer( - deadline: Duration, - callback: Box, - ) { - let _ = default_host().register_timer(deadline.as_nanos() as u64, callback); - } - - fn read_vgicd_iidr() -> u32 { - gic::read_gicd_iidr() - } - - fn read_vgicd_typer() -> u32 { - gic::read_gicd_typer() - } - - fn get_host_gicd_base() -> PhysAddr { - gic::host_gicd_base() - } - - fn get_host_gicr_base() -> PhysAddr { - gic::host_gicr_base() - } - - fn hardware_inject_virtual_interrupt(vector: u8) { - gic::inject_interrupt(vector as usize); - } - } -} diff --git a/virtualization/axvm/src/arch/aarch64.rs b/virtualization/axvm/src/arch/aarch64.rs new file mode 100644 index 0000000000..f36382c887 --- /dev/null +++ b/virtualization/axvm/src/arch/aarch64.rs @@ -0,0 +1,161 @@ +use alloc::boxed::Box; +use core::time::Duration; + +use arm_vcpu::host::ArmVcpuHostIf; +use arm_vgic::host::ArmVgicHostIf; +use ax_crate_interface::impl_interface; +use ax_errno::AxResult; +use ax_memory_addr::{PhysAddr, VirtAddr}; + +use super::{ArchOps, VcpuCreateContext, VcpuSetupContext}; +use crate::host::{HostCpu, HostMemory, HostTime, default_host, gic}; + +pub(crate) struct Aarch64Arch; + +impl ArchOps for Aarch64Arch { + type VCpu = arm_vcpu::Aarch64VCpu; + type PerCpu = arm_vcpu::Aarch64PerCpu; + type VcpuCreateState = (); + + fn has_hardware_support() -> bool { + arm_vcpu::has_hardware_support() + } + + fn max_guest_page_table_levels() -> usize { + arm_vcpu::max_guest_page_table_levels() + } + + fn clean_dcache_range(addr: VirtAddr, size: usize) { + aarch64_cpu_ext::cache::dcache_range( + aarch64_cpu_ext::cache::CacheOp::Clean, + addr.as_usize(), + size, + ); + } + + fn new_vcpu_create_state( + _vcpu_mappings: &[(usize, Option, usize)], + ) -> AxResult { + Ok(()) + } + + fn build_vcpu_create_config( + _state: &Self::VcpuCreateState, + ctx: VcpuCreateContext, + ) -> AxResult<::CreateConfig> { + Ok(arm_vcpu::Aarch64VCpuCreateConfig { + mpidr_el1: ctx.phys_cpu_id as _, + dtb_addr: ctx.dtb_addr.unwrap_or_default().as_usize(), + }) + } + + fn build_vcpu_setup_config( + ctx: VcpuSetupContext<'_>, + ) -> AxResult<::SetupConfig> { + let passthrough = ctx.interrupt_mode == axvm_types::VMInterruptMode::Passthrough; + Ok(arm_vcpu::Aarch64VCpuSetupConfig { + passthrough_interrupt: passthrough, + passthrough_timer: passthrough, + }) + } + + fn ipi_targets( + vm: &crate::AxVMRef, + current_vcpu_id: usize, + target_cpu: u64, + target_cpu_aux: u64, + send_to_all: bool, + send_to_self: bool, + ) -> crate::CpuMask<64> { + let mut targets = crate::CpuMask::new(); + if send_to_all { + for vcpu in vm.vcpu_list() { + if vcpu.id() != current_vcpu_id { + targets.set(vcpu.id(), true); + } + } + } else if send_to_self { + targets.set(current_vcpu_id, true); + } else { + for (vcpu_id, _, phys_id) in vm.get_vcpu_affinities_pcpu_ids() { + let affinity = phys_id as u64; + let aff0 = affinity & 0xff; + let aff123 = affinity & !0xff; + if aff123 == target_cpu && aff0 < 16 && (target_cpu_aux & (1u64 << aff0)) != 0 { + targets.set(vcpu_id, true); + } + } + } + targets + } +} + +struct ArmVcpuHostIfImpl; + +#[impl_interface] +impl ArmVcpuHostIf for ArmVcpuHostIfImpl { + fn hardware_inject_virtual_interrupt(vector: u8) { + gic::inject_interrupt(vector as usize); + } + + fn fetch_irq() -> usize { + gic::fetch_irq() + } + + fn handle_irq() { + gic::handle_current_irq(); + } +} + +struct ArmVgicHostIfImpl; + +#[impl_interface] +impl ArmVgicHostIf for ArmVgicHostIfImpl { + fn alloc_contiguous_frames(frame_count: usize, frame_align: usize) -> Option { + default_host().alloc_contiguous_frames(frame_count, frame_align) + } + + fn dealloc_contiguous_frames(start_paddr: PhysAddr, frame_count: usize) { + default_host().dealloc_contiguous_frames(start_paddr, frame_count); + } + + fn phys_to_virt(paddr: PhysAddr) -> VirtAddr { + default_host().phys_to_virt(paddr) + } + + fn host_cpu_num() -> usize { + default_host().cpu_count() + } + + fn current_vcpu_id() -> usize { + crate::current_vcpu_id().expect("current AArch64 vCPU is not set") + } + + fn current_time_nanos() -> u64 { + default_host().monotonic_time().as_nanos() as u64 + } + + fn register_timer(deadline: Duration, callback: Box) { + let _ = default_host().register_timer(deadline.as_nanos() as u64, callback); + } + + fn read_vgicd_iidr() -> u32 { + gic::read_gicd_iidr() + } + + fn read_vgicd_typer() -> u32 { + gic::read_gicd_typer() + } + + fn get_host_gicd_base() -> PhysAddr { + gic::host_gicd_base() + } + + fn get_host_gicr_base() -> PhysAddr { + gic::host_gicr_base() + } + + fn hardware_inject_virtual_interrupt(vector: u8) { + gic::inject_interrupt(vector as usize); + } +} diff --git a/virtualization/axvm/src/arch/loongarch64.rs b/virtualization/axvm/src/arch/loongarch64.rs new file mode 100644 index 0000000000..027694ee23 --- /dev/null +++ b/virtualization/axvm/src/arch/loongarch64.rs @@ -0,0 +1,220 @@ +use alloc::boxed::Box; +use core::time::Duration; + +use ax_crate_interface::impl_interface; +use ax_errno::AxResult; +use ax_memory_addr::{PhysAddr, VirtAddr}; +use loongarch_vcpu::host::LoongArchVcpuHostIf; + +use super::{ArchOps, VcpuCreateContext, VcpuSetupContext}; +use crate::host::{HostMemory, HostTime, default_host}; + +pub(crate) struct LoongArch64Arch; + +impl ArchOps for LoongArch64Arch { + type VCpu = loongarch_vcpu::LoongArchVCpu; + type PerCpu = loongarch_vcpu::LoongArchPerCpu; + type VcpuCreateState = loongarch_vcpu::LoongArchIocsrStateRef; + + fn has_hardware_support() -> bool { + loongarch_vcpu::has_hardware_support() + } + + fn new_vcpu_create_state( + vcpu_mappings: &[(usize, Option, usize)], + ) -> AxResult { + let vcpu_state_count = vcpu_mappings + .iter() + .map(|(vcpu_id, ..)| *vcpu_id) + .max() + .map_or(0, |vcpu_id| vcpu_id + 1); + loongarch_vcpu::LoongArchIocsrState::new(vcpu_state_count) + } + + fn build_vcpu_create_config( + state: &Self::VcpuCreateState, + ctx: VcpuCreateContext, + ) -> AxResult<::CreateConfig> { + Ok(loongarch_vcpu::LoongArchVCpuCreateConfig { + cpu_id: ctx.vcpu_id, + dtb_addr: ctx.dtb_addr.unwrap_or_default().as_usize(), + boot_args: [0; 3], + boot_stack_top: 0, + firmware_boot: ctx.firmware_boot, + iocsr_state: state.clone(), + }) + } + + fn build_vcpu_setup_config( + ctx: VcpuSetupContext<'_>, + ) -> AxResult<::SetupConfig> { + let passthrough = ctx.interrupt_mode == axvm_types::VMInterruptMode::Passthrough; + Ok(loongarch_vcpu::LoongArchVCpuSetupConfig { + passthrough_interrupt: passthrough, + passthrough_timer: passthrough, + boot_args: [0; 3], + boot_stack_top: 0, + firmware_boot: ctx.firmware_boot, + }) + } + + fn register_platform_irq_injector() { + crate::runtime::loongarch_irq::register_platform_irq_injector(); + } + + fn inject_pending_interrupt( + vm: &crate::AxVMRef, + vcpu: &crate::vm::AxVCpuRef, + interrupt: crate::vm::PendingInterrupt, + ) { + match interrupt { + crate::vm::PendingInterrupt::Normal(vector) => { + trace!( + "Injecting queued interrupt {vector:#x} into VM[{}] VCpu[{}]", + vcpu.vm_id(), + vcpu.id() + ); + if let Err(err) = vcpu.inject_interrupt(vector) { + warn!( + "Failed to inject queued interrupt {vector:#x} into VM[{}] VCpu[{}]: \ + {err:?}", + vcpu.vm_id(), + vcpu.id() + ); + } + } + crate::vm::PendingInterrupt::External { + vector, + physical_irq, + } => { + let Some(vector) = vm.loongarch_external_irq_vector(vector, physical_irq) else { + trace!( + "Queued LoongArch external interrupt physical_irq={physical_irq:#x} is \ + masked in VM[{}]", + vm.id() + ); + return; + }; + trace!( + "Injecting queued LoongArch external interrupt vector={vector:#x}, \ + physical_irq={physical_irq:#x} into VM[{}] VCpu[{}]", + vm.id(), + vcpu.id() + ); + if let Err(err) = vcpu + .get_arch_vcpu() + .inject_external_interrupt(vector, physical_irq) + { + warn!( + "Failed to inject queued LoongArch external interrupt vector={vector:#x}, \ + physical_irq={physical_irq:#x} into VM[{}] VCpu[{}]: {err:?}", + vm.id(), + vcpu.id() + ); + } + } + } + } + + fn after_mmio_write(vm: &crate::AxVM) { + vm.drain_loongarch_pch_pic_events(); + } + + fn handle_idle(_vm: &crate::AxVMRef, vcpu: &crate::vm::AxVCpuRef) { + crate::check_timer_events(); + if vcpu.get_arch_vcpu().has_enabled_pending_interrupt() { + trace!( + "VM[{}] VCpu[{}] skips idle wait because guest has enabled pending interrupt", + vcpu.vm_id(), + vcpu.id() + ); + return; + } + let idle_timeout = vcpu.get_arch_vcpu().idle_wait_timeout(); + trace!( + "VM[{}] VCpu[{}] host idle wait for {idle_timeout:?}", + vcpu.vm_id(), + vcpu.id() + ); + ax_std::os::arceos::modules::ax_hal::asm::set_timer_irq_enabled(true); + ax_std::os::arceos::modules::ax_hal::asm::enable_irqs(); + ax_std::os::arceos::modules::ax_hal::time::busy_wait(idle_timeout); + ax_std::os::arceos::modules::ax_hal::asm::disable_irqs(); + ax_std::os::arceos::modules::ax_hal::asm::set_timer_irq_enabled(false); + } + + fn clean_dcache_range(addr: VirtAddr, size: usize) { + unsafe { + cache_range::(addr, size); + core::arch::asm!("dbar 0"); + } + } +} + +struct LoongArchVcpuHostIfImpl; + +#[impl_interface] +impl LoongArchVcpuHostIf for LoongArchVcpuHostIfImpl { + fn virt_to_phys(vaddr: VirtAddr) -> PhysAddr { + default_host().virt_to_phys(vaddr) + } + + fn current_time_nanos() -> u64 { + default_host().monotonic_time().as_nanos() as u64 + } + + fn ticks_to_nanos(ticks: u64) -> u64 { + ax_std::os::arceos::modules::ax_hal::time::ticks_to_nanos(ticks) + } + + fn register_timer( + deadline: Duration, + callback: Box, + ) -> usize { + default_host().register_timer(deadline.as_nanos() as u64, callback) + } + + fn cancel_timer(token: usize) { + default_host().cancel_timer(token); + } + + fn inject_interrupt(vm_id: usize, vcpu_id: usize, vector: usize) { + if let Err(err) = crate::runtime::vcpus::queue_interrupt(vm_id, vcpu_id, vector) { + warn!( + "failed to queue LoongArch interrupt {vector:#x} for VM[{vm_id}] VCpu[{vcpu_id}]: \ + {err:?}" + ); + } + } + + fn inject_external_interrupt(vm_id: usize, vcpu_id: usize, vector: usize, physical_irq: usize) { + if let Err(err) = + crate::runtime::vcpus::queue_external_interrupt(vm_id, vcpu_id, vector, physical_irq) + { + warn!( + "failed to queue LoongArch external interrupt vector={vector:#x}, \ + physical_irq={physical_irq:#x} for VM[{vm_id}] VCpu[{vcpu_id}]: {err:?}" + ); + } + } +} + +const CACHE_LINE_SIZE: usize = 64; +const DCACHE_WB: u8 = 0x19; + +unsafe fn cache_range(addr: VirtAddr, size: usize) { + if size == 0 { + return; + } + + let start = addr.as_usize() & !(CACHE_LINE_SIZE - 1); + let end = addr.as_usize() + size; + let mut current = start; + + while current < end { + unsafe { + core::arch::asm!("cacop {0}, {1}, 0", const OP, in(reg) current); + } + current += CACHE_LINE_SIZE; + } +} diff --git a/virtualization/axvm/src/arch/mod.rs b/virtualization/axvm/src/arch/mod.rs new file mode 100644 index 0000000000..7fe314f0bc --- /dev/null +++ b/virtualization/axvm/src/arch/mod.rs @@ -0,0 +1,220 @@ +//! Architecture component glue owned by AxVM. + +use alloc::vec::Vec; + +use ax_errno::AxResult; +use ax_memory_addr::VirtAddr; +use axvm_types::{ + GuestPhysAddr, PassThroughPortConfig, VMInterruptMode, VmArchPerCpuOps, VmArchVcpuOps, +}; + +use crate::CpuMask; + +#[cfg(target_arch = "aarch64")] +mod aarch64; +#[cfg(target_arch = "loongarch64")] +mod loongarch64; +#[cfg(target_arch = "riscv64")] +mod riscv64; +#[cfg(target_arch = "x86_64")] +mod x86_64; + +#[cfg(target_arch = "aarch64")] +pub(crate) type CurrentArch = aarch64::Aarch64Arch; +#[cfg(target_arch = "loongarch64")] +pub(crate) type CurrentArch = loongarch64::LoongArch64Arch; +#[cfg(target_arch = "riscv64")] +pub(crate) type CurrentArch = riscv64::Riscv64Arch; +#[cfg(target_arch = "x86_64")] +pub(crate) type CurrentArch = x86_64::X86_64Arch; + +pub(crate) type ArchVCpu = ::VCpu; +pub(crate) type ArchPerCpu = ::PerCpu; + +#[allow(dead_code)] +pub(crate) struct VcpuCreateContext { + pub(crate) vcpu_id: usize, + pub(crate) phys_cpu_id: usize, + pub(crate) dtb_addr: Option, + pub(crate) firmware_boot: bool, +} + +#[allow(dead_code)] +pub(crate) struct VcpuSetupContext<'a> { + pub(crate) interrupt_mode: VMInterruptMode, + pub(crate) emulates_console: bool, + pub(crate) passthrough_ports: &'a [PassThroughPortConfig], + pub(crate) firmware_boot: bool, +} + +pub(crate) trait ArchOps { + type VCpu: VmArchVcpuOps; + type PerCpu: VmArchPerCpuOps; + type VcpuCreateState; + + fn has_hardware_support() -> bool; + + fn max_guest_page_table_levels() -> usize { + 4 + } + + fn clean_dcache_range(_addr: VirtAddr, _size: usize) {} + + fn new_vcpu_create_state( + vcpu_mappings: &[(usize, Option, usize)], + ) -> AxResult; + + fn build_vcpu_create_config( + state: &Self::VcpuCreateState, + ctx: VcpuCreateContext, + ) -> AxResult<::CreateConfig>; + + fn build_vcpu_setup_config( + ctx: VcpuSetupContext<'_>, + ) -> AxResult<::SetupConfig>; + + fn register_platform_irq_injector() {} + + fn vcpu_affinities( + cpu_num: usize, + phys_cpu_ids: Option<&[usize]>, + phys_cpu_sets: Option<&[usize]>, + ) -> Vec<(usize, Option, usize)> { + default_vcpu_affinities(cpu_num, phys_cpu_ids, phys_cpu_sets) + } + + fn ipi_targets( + vm: &crate::AxVMRef, + current_vcpu_id: usize, + target_cpu: u64, + target_cpu_aux: u64, + send_to_all: bool, + send_to_self: bool, + ) -> CpuMask<64> { + let mut targets = CpuMask::new(); + + if send_to_all { + for vcpu in vm.vcpu_list() { + if vcpu.id() != current_vcpu_id { + targets.set(vcpu.id(), true); + } + } + } else if send_to_self { + targets.set(current_vcpu_id, true); + } else { + let _ = target_cpu_aux; + targets.set(target_cpu as usize, true); + } + + targets + } + + fn set_vcpu_on_args(vcpu: &crate::vm::AxVCpuRef, _vcpu_id: usize, arg: usize) { + vcpu.set_gpr(0, arg); + } + + fn set_cpu_up_success(vcpu: &crate::vm::AxVCpuRef) { + vcpu.set_gpr(0, 0); + } + + fn set_io_read_result(vcpu: &crate::vm::AxVCpuRef, val: usize) { + vcpu.set_gpr(0, val); + } + + fn before_first_run(_vm: &crate::AxVMRef, _vcpu: &crate::vm::AxVCpuRef) {} + + fn before_vcpu_run(_vm: &crate::AxVMRef, _vcpu: &crate::vm::AxVCpuRef) {} + + fn inject_pending_interrupt( + _vm: &crate::AxVMRef, + vcpu: &crate::vm::AxVCpuRef, + interrupt: crate::vm::PendingInterrupt, + ) { + match interrupt { + crate::vm::PendingInterrupt::Normal(vector) => { + trace!( + "Injecting queued interrupt {vector:#x} into VM[{}] VCpu[{}]", + vcpu.vm_id(), + vcpu.id() + ); + if let Err(err) = vcpu.inject_interrupt(vector) { + warn!( + "Failed to inject queued interrupt {vector:#x} into VM[{}] VCpu[{}]: \ + {err:?}", + vcpu.vm_id(), + vcpu.id() + ); + } + } + crate::vm::PendingInterrupt::External { + vector, + physical_irq, + } => { + warn!( + "VM[{}] VCpu[{}] dropped unsupported external interrupt vector={vector:#x}, \ + physical_irq={physical_irq:#x}", + vcpu.vm_id(), + vcpu.id() + ); + } + } + } + + fn after_external_interrupt(_vm: &crate::AxVMRef, _vcpu: &crate::vm::AxVCpuRef, vector: usize) { + crate::host::arceos::dispatch_host_irq(vector); + crate::check_timer_events(); + } + + fn after_preemption_timer(_vm: &crate::AxVMRef, _vcpu: &crate::vm::AxVCpuRef) { + crate::check_timer_events(); + } + + fn after_interrupt_end( + _vm: &crate::AxVMRef, + _vcpu: &crate::vm::AxVCpuRef, + _vector: Option, + ) { + } + + fn handle_halt(runtime: &crate::vm::VmRuntimeHandle) -> bool { + runtime.wait(); + false + } + + fn handle_idle(_vm: &crate::AxVMRef, _vcpu: &crate::vm::AxVCpuRef) { + crate::check_timer_events(); + } + + fn on_last_vcpu_exit(_vm_id: usize) {} + + fn after_mmio_write(_vm: &crate::AxVM) {} +} + +pub(crate) fn default_vcpu_affinities( + cpu_num: usize, + phys_cpu_ids: Option<&[usize]>, + phys_cpu_sets: Option<&[usize]>, +) -> Vec<(usize, Option, usize)> { + let mut vcpus = Vec::with_capacity(cpu_num); + for vcpu_id in 0..cpu_num { + vcpus.push((vcpu_id, None, vcpu_id)); + } + + if let Some(phys_cpu_sets) = phys_cpu_sets { + for (vcpu_id, pcpu_mask_bitmap) in phys_cpu_sets.iter().enumerate() { + if let Some(vcpu) = vcpus.get_mut(vcpu_id) { + vcpu.1 = Some(*pcpu_mask_bitmap); + } + } + } + + if let Some(phys_cpu_ids) = phys_cpu_ids { + for (vcpu_id, phys_id) in phys_cpu_ids.iter().enumerate() { + if let Some(vcpu) = vcpus.get_mut(vcpu_id) { + vcpu.2 = *phys_id; + } + } + } + + vcpus +} diff --git a/virtualization/axvm/src/arch/riscv64.rs b/virtualization/axvm/src/arch/riscv64.rs new file mode 100644 index 0000000000..af4c0afd7b --- /dev/null +++ b/virtualization/axvm/src/arch/riscv64.rs @@ -0,0 +1,132 @@ +use alloc::vec::Vec; + +use ax_crate_interface::impl_interface; +use ax_errno::AxResult; +use ax_memory_addr::{PhysAddr, VirtAddr}; +use riscv_vcpu::{GprIndex as RiscvGprIndex, host::RiscvVcpuHostIf}; +use riscv_vplic::host::RiscvVplicHostIf; + +use super::{ArchOps, VcpuCreateContext, VcpuSetupContext, default_vcpu_affinities}; +use crate::host::{HostMemory, default_host}; + +pub(crate) struct Riscv64Arch; + +impl ArchOps for Riscv64Arch { + type VCpu = riscv_vcpu::RISCVVCpu; + type PerCpu = riscv_vcpu::RISCVPerCpu; + type VcpuCreateState = (); + + fn has_hardware_support() -> bool { + riscv_vcpu::has_hardware_support() + } + + fn new_vcpu_create_state( + _vcpu_mappings: &[(usize, Option, usize)], + ) -> AxResult { + Ok(()) + } + + fn build_vcpu_create_config( + _state: &Self::VcpuCreateState, + ctx: VcpuCreateContext, + ) -> AxResult<::CreateConfig> { + Ok(riscv_vcpu::RISCVVCpuCreateConfig { + hart_id: ctx.vcpu_id, + dtb_addr: ctx.dtb_addr.unwrap_or_default().as_usize(), + }) + } + + fn build_vcpu_setup_config( + _ctx: VcpuSetupContext<'_>, + ) -> AxResult<::SetupConfig> { + Ok(Default::default()) + } + + fn register_platform_irq_injector() { + register_platform_irq_injector(); + } + + fn vcpu_affinities( + cpu_num: usize, + phys_cpu_ids: Option<&[usize]>, + phys_cpu_sets: Option<&[usize]>, + ) -> Vec<(usize, Option, usize)> { + let mut vcpus = default_vcpu_affinities(cpu_num, phys_cpu_ids, phys_cpu_sets); + if phys_cpu_sets.is_none() { + for (_, mask, phys_id) in &mut vcpus { + *mask = Some(1 << *phys_id); + } + } + vcpus + } + + fn set_vcpu_on_args(vcpu: &crate::vm::AxVCpuRef, vcpu_id: usize, arg: usize) { + vcpu.set_gpr(RiscvGprIndex::A0 as usize, vcpu_id); + vcpu.set_gpr(RiscvGprIndex::A1 as usize, arg); + } + + fn set_cpu_up_success(vcpu: &crate::vm::AxVCpuRef) { + vcpu.set_gpr(RiscvGprIndex::A0 as usize, 0); + } + + fn set_io_read_result(vcpu: &crate::vm::AxVCpuRef, val: usize) { + vcpu.set_gpr(RiscvGprIndex::A0 as usize, val); + } + + fn after_external_interrupt(_vm: &crate::AxVMRef, vcpu: &crate::vm::AxVCpuRef, vector: usize) { + vcpu.with_current_cpu_set(|| { + crate::host::arceos::dispatch_host_irq(vector); + vcpu.get_arch_vcpu().latch_hvip_from_hw(); + }); + crate::check_timer_events(); + } +} + +struct RiscvVcpuHostIfImpl; + +#[impl_interface] +impl RiscvVcpuHostIf for RiscvVcpuHostIfImpl { + fn virt_to_phys(vaddr: VirtAddr) -> PhysAddr { + default_host().virt_to_phys(vaddr) + } +} + +struct RiscvVplicHostIfImpl; + +#[impl_interface] +impl RiscvVplicHostIf for RiscvVplicHostIfImpl { + fn phys_to_virt(paddr: PhysAddr) -> VirtAddr { + default_host().phys_to_virt(paddr) + } +} + +#[cfg(feature = "plat-dyn")] +fn register_platform_irq_injector() { + axplat_dyn::register_virtual_irq_injector(inject_virtual_irq); +} + +#[cfg(not(feature = "plat-dyn"))] +compile_error!("riscv64 Axvisor requires the plat-dyn feature"); + +#[cfg(feature = "plat-dyn")] +fn inject_virtual_irq(irq_id: usize) -> bool { + debug!("injecting RISC-V virtual IRQ id: {irq_id}"); + + let Some(vm_id) = crate::current_vm_id() else { + warn!("cannot inject RISC-V virtual IRQ without current VM context"); + return false; + }; + + let Some(injected) = crate::manager::with_vm(vm_id, |vm| { + if let Err(err) = vm.pulse_interrupt(irq_id) { + warn!("failed to inject RISC-V virtual IRQ {irq_id}: {err:?}"); + return false; + } + true + }) else { + warn!("cannot inject RISC-V virtual IRQ {irq_id}: VM[{vm_id}] not found"); + return false; + }; + + injected +} diff --git a/virtualization/axvm/src/arch/x86_64.rs b/virtualization/axvm/src/arch/x86_64.rs new file mode 100644 index 0000000000..e664d2781e --- /dev/null +++ b/virtualization/axvm/src/arch/x86_64.rs @@ -0,0 +1,191 @@ +use alloc::boxed::Box; +use core::time::Duration; + +use ax_crate_interface::impl_interface; +use ax_errno::AxResult; +use ax_memory_addr::{PhysAddr, VirtAddr}; +use axvm_types::{InterruptVector, VCpuId, VMId}; +use x86_vcpu::host::X86VcpuHostIf; +use x86_vlapic::host::X86VlapicHostIf; + +use super::{ArchOps, VcpuCreateContext, VcpuSetupContext}; +use crate::{ + host::{HostConsole, HostMemory, HostTime, default_host}, + manager, + vcpu::get_current_vcpu, +}; + +pub(crate) struct X86_64Arch; + +pub(crate) struct X86VcpuCreateState; + +impl ArchOps for X86_64Arch { + type VCpu = x86_vcpu::X86ArchVCpu; + type PerCpu = x86_vcpu::X86ArchPerCpuState; + type VcpuCreateState = X86VcpuCreateState; + + fn has_hardware_support() -> bool { + x86_vcpu::has_hardware_support() + } + + fn new_vcpu_create_state( + _vcpu_mappings: &[(usize, Option, usize)], + ) -> AxResult { + Ok(X86VcpuCreateState) + } + + fn build_vcpu_create_config( + _state: &Self::VcpuCreateState, + _ctx: VcpuCreateContext, + ) -> AxResult<::CreateConfig> { + Ok(x86_vcpu::X86VCpuCreateConfig) + } + + fn build_vcpu_setup_config( + ctx: VcpuSetupContext<'_>, + ) -> AxResult<::SetupConfig> { + let mut config = x86_vcpu::X86VCpuSetupConfig { + emulate_com1: ctx.emulates_console, + ..Default::default() + }; + for port in ctx.passthrough_ports { + config.add_passthrough_port_range(port.base, port.length)?; + } + Ok(config) + } + + fn before_first_run(vm: &crate::AxVMRef, vcpu: &crate::vm::AxVCpuRef) { + crate::runtime::x86_irq::enable_ioapic_irq_forwarding(vm, vcpu); + } + + fn before_vcpu_run(vm: &crate::AxVMRef, vcpu: &crate::vm::AxVCpuRef) { + crate::runtime::x86_irq::drain_pending_ioapic_irqs(vm, vcpu); + crate::runtime::x86_irq::activate_ready_ioapic_forwarding_routes(vm); + } + + fn after_external_interrupt(vm: &crate::AxVMRef, vcpu: &crate::vm::AxVCpuRef, vector: usize) { + crate::host::arceos::dispatch_host_irq(vector); + crate::check_timer_events(); + crate::runtime::x86_irq::inject_pending_serial_irq(vm, vcpu); + } + + fn after_preemption_timer(vm: &crate::AxVMRef, vcpu: &crate::vm::AxVCpuRef) { + crate::timer::check_events(); + crate::runtime::x86_irq::inject_due_pit_irq0(vm, vcpu); + crate::runtime::x86_irq::inject_pending_serial_irq(vm, vcpu); + } + + fn after_interrupt_end(vm: &crate::AxVMRef, vcpu: &crate::vm::AxVCpuRef, vector: Option) { + if let Some(vector) = vector { + crate::runtime::x86_irq::inject_pending_ioapic_irq_after_eoi(vm, vcpu, vector); + } + } + + fn handle_halt(_runtime: &crate::vm::VmRuntimeHandle) -> bool { + true + } + + fn on_last_vcpu_exit(vm_id: usize) { + crate::runtime::x86_irq::disable_ioapic_irq_forwarding_for_vm(vm_id); + } +} + +struct X86VcpuHostIfImpl; + +#[impl_interface] +impl X86VcpuHostIf for X86VcpuHostIfImpl { + fn alloc_frame() -> Option { + default_host().alloc_frame() + } + + fn dealloc_frame(paddr: PhysAddr) { + default_host().dealloc_frame(paddr); + } + + fn alloc_contiguous_frames(frame_count: usize, frame_align: usize) -> Option { + default_host().alloc_contiguous_frames(frame_count, frame_align) + } + + fn dealloc_contiguous_frames(start_paddr: PhysAddr, frame_count: usize) { + default_host().dealloc_contiguous_frames(start_paddr, frame_count); + } + + fn phys_to_virt(paddr: PhysAddr) -> VirtAddr { + default_host().phys_to_virt(paddr) + } + + fn nanos_to_ticks(nanos: u64) -> u64 { + default_host().nanos_to_ticks(nanos) + } +} + +struct X86VlapicHostIfImpl; + +#[impl_interface] +impl X86VlapicHostIf for X86VlapicHostIfImpl { + fn alloc_frame() -> Option { + default_host().alloc_frame() + } + + fn dealloc_frame(paddr: PhysAddr) { + default_host().dealloc_frame(paddr); + } + + fn phys_to_virt(paddr: PhysAddr) -> VirtAddr { + default_host().phys_to_virt(paddr) + } + + fn virt_to_phys(vaddr: VirtAddr) -> PhysAddr { + default_host().virt_to_phys(vaddr) + } + + fn current_time() -> Duration { + default_host().monotonic_time() + } + + fn current_time_nanos() -> u64 { + default_host().monotonic_time().as_nanos() as u64 + } + + fn register_timer( + deadline: Duration, + callback: Box, + ) -> usize { + default_host().register_timer(deadline.as_nanos() as u64, callback) + } + + fn cancel_timer(token: usize) { + default_host().cancel_timer(token); + } + + fn write_bytes(bytes: &[u8]) { + default_host().write_bytes(bytes); + } + + fn read_bytes(bytes: &mut [u8]) -> usize { + default_host().read_bytes(bytes) + } + + fn current_vm_id() -> VMId { + get_current_vcpu::() + .expect("current x86 vCPU is not set") + .vm_id() + } + + fn current_vm_vcpu_num() -> usize { + let vm_id = Self::current_vm_id(); + manager::with_vm(vm_id, |vm| vm.vcpu_num()).unwrap_or(0) + } + + fn current_vm_active_vcpus() -> usize { + manager::active_vcpu_mask(Self::current_vm_id()).unwrap_or(0) + } + + fn active_vcpus(vm_id: VMId) -> Option { + manager::active_vcpu_mask(vm_id) + } + + fn inject_interrupt(vm_id: VMId, vcpu_id: VCpuId, vector: InterruptVector) -> AxResult { + manager::inject_interrupt(vm_id, vcpu_id, vector as usize) + } +} diff --git a/os/axvisor/src/fdt/create.rs b/virtualization/axvm/src/boot/fdt/create.rs similarity index 98% rename from os/axvisor/src/fdt/create.rs rename to virtualization/axvm/src/boot/fdt/create.rs index 935238ad86..52a7a386f8 100644 --- a/os/axvisor/src/fdt/create.rs +++ b/virtualization/axvm/src/boot/fdt/create.rs @@ -23,20 +23,20 @@ use core::ptr::NonNull; use ax_errno::{AxError, AxResult, ax_err_type}; #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] use ax_memory_addr::MemoryAddr; +use axvmconfig::AxVMCrateConfig; use fdt_parser::{Fdt, Node}; use super::vm_fdt::{FdtWriter, FdtWriterNode}; #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] -use crate::images::load_vm_image_from_memory; -#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] -use axvm::AxVMRef; +use crate::AxVMRef; #[cfg(any(target_arch = "aarch64", target_arch = "riscv64", test))] -use axvm::GuestPhysAddr; +use crate::GuestPhysAddr; #[cfg(any(target_arch = "aarch64", target_arch = "riscv64", test))] -use axvm::VMMemoryRegion; -use axvmconfig::AxVMCrateConfig; +use crate::VMMemoryRegion; +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] +use crate::boot::images::load_vm_image_from_memory; -// use crate::fdt::print::{print_fdt, print_guest_fdt}; +// use crate::boot::fdt::print::{print_fdt, print_guest_fdt}; fn fdt_write_err(err: impl core::fmt::Display) -> AxError { ax_err_type!(InvalidData, format!("Failed to write guest FDT: {err}")) @@ -342,7 +342,7 @@ fn add_memory_node( #[cfg(any(target_arch = "aarch64", test))] fn initrd_range_from_image_config( - ramdisk: Option<&axvm::config::RamdiskInfo>, + ramdisk: Option<&crate::config::RamdiskInfo>, ) -> Option<(u64, u64)> { let rd = ramdisk?; let start = rd.load_gpa.as_usize() as u64; @@ -516,7 +516,7 @@ pub fn update_fdt( let new_fdt_bytes = new_fdt.finish().map_err(fdt_write_err)?; - // crate::fdt::print::print_guest_fdt(new_fdt_bytes.as_slice()); + // crate::boot::fdt::print::print_guest_fdt(new_fdt_bytes.as_slice()); let vm_clone = vm.clone(); let dest_addr = calculate_dtb_load_addr(vm, new_fdt_bytes.len())?; debug!( @@ -567,7 +567,7 @@ mod tests { use fdt_parser::Fdt; use super::{cpu_node_id, initrd_range_from_image_config, need_cpu_node, sanitize_bootargs}; - use axvm::{GuestPhysAddr, config::RamdiskInfo}; + use crate::{GuestPhysAddr, config::RamdiskInfo}; fn test_fdt(dts: &str) -> Fdt<'static> { let mut writer = super::FdtWriter::new().unwrap(); @@ -595,7 +595,7 @@ mod tests { fn cpu_node_selection_uses_node_id_when_reg_differs() { let fdt = test_fdt("cpu@0=200\ncpu@100=0\ncpu@101=100"); let nodes: Vec<_> = fdt.all_nodes().collect(); - let paths = crate::fdt::build_all_node_paths(&nodes); + let paths = crate::boot::fdt::build_all_node_paths(&nodes); let selected: Vec<_> = nodes .iter() .zip(paths.iter()) diff --git a/os/axvisor/src/fdt/device.rs b/virtualization/axvm/src/boot/fdt/device.rs similarity index 99% rename from os/axvisor/src/fdt/device.rs rename to virtualization/axvm/src/boot/fdt/device.rs index 0b8ad2182a..8c8e5e9ed9 100644 --- a/os/axvisor/src/fdt/device.rs +++ b/virtualization/axvm/src/boot/fdt/device.rs @@ -23,7 +23,7 @@ use alloc::{ use fdt_parser::{Fdt, Node}; -use axvm::config::AxVMConfig; +use crate::config::AxVMConfig; /// Return the collection of all passthrough devices in the configuration file and newly added devices found pub fn find_all_passthrough_devices(vm_cfg: &mut AxVMConfig, fdt: &Fdt) -> Vec { diff --git a/os/axvisor/src/fdt/loongarch64/guest_firmware_dtb.rs b/virtualization/axvm/src/boot/fdt/loongarch64/guest_firmware_dtb.rs similarity index 98% rename from os/axvisor/src/fdt/loongarch64/guest_firmware_dtb.rs rename to virtualization/axvm/src/boot/fdt/loongarch64/guest_firmware_dtb.rs index 0fb2c5c72a..177736a204 100644 --- a/os/axvisor/src/fdt/loongarch64/guest_firmware_dtb.rs +++ b/virtualization/axvm/src/boot/fdt/loongarch64/guest_firmware_dtb.rs @@ -1,9 +1,8 @@ -use alloc::{format, vec}; +use alloc::{format, vec, vec::Vec}; use ax_errno::{AxError, AxResult, ax_err_type}; -use crate::fdt::vm_fdt::FdtWriter; -use crate::guest_platform::loongarch64::GuestPlatform; +use crate::boot::{fdt::vm_fdt::FdtWriter, guest_platform::loongarch64::GuestPlatform}; const PHANDLE_CPU0: u32 = 0x8000; const PHANDLE_CPUIC: u32 = 0x8001; diff --git a/os/axvisor/src/fdt/loongarch64/mod.rs b/virtualization/axvm/src/boot/fdt/loongarch64/mod.rs similarity index 100% rename from os/axvisor/src/fdt/loongarch64/mod.rs rename to virtualization/axvm/src/boot/fdt/loongarch64/mod.rs diff --git a/os/axvisor/src/fdt/mod.rs b/virtualization/axvm/src/boot/fdt/mod.rs similarity index 88% rename from os/axvisor/src/fdt/mod.rs rename to virtualization/axvm/src/boot/fdt/mod.rs index 8123e38dc4..ec36d996c7 100644 --- a/os/axvisor/src/fdt/mod.rs +++ b/virtualization/axvm/src/boot/fdt/mod.rs @@ -36,44 +36,43 @@ use alloc::vec::Vec; use ax_errno::AxResult; #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] use ax_errno::ax_err_type; -#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] -use fdt_parser::Fdt; +use axvmconfig::{AxVMCrateConfig, VMBootProtocol}; // pub use print::print_fdt; #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] pub use create::update_fdt; #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] pub use device::build_all_node_paths; #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] +use fdt_parser::Fdt; +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] pub use parser::*; -use axvm::config::AxVMConfig; -use axvmconfig::{AxVMCrateConfig, VMBootProtocol}; - #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] -use crate::config::vmcfg; +use crate::boot::BootImageProvider; +use crate::config::AxVMConfig; /// Guest DTB artifact produced or patched by the monitor before AxVM owns it. #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] #[derive(Debug, Clone)] -pub(crate) struct GuestDtbImage { +pub struct GuestDtbImage { bytes: Vec, } #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] impl GuestDtbImage { - pub(crate) fn new(bytes: Vec) -> Self { + pub fn new(bytes: Vec) -> Self { Self { bytes } } - pub(crate) fn as_bytes(&self) -> &[u8] { + pub fn as_bytes(&self) -> &[u8] { &self.bytes } } /// Initialize LoongArch guest firmware resource handling. #[cfg(target_arch = "loongarch64")] -pub(crate) fn init_guest_boot_resources() { - crate::guest_platform::loongarch64::init(); +pub fn init_guest_boot_resources() { + crate::boot::guest_platform::loongarch64::init(); } #[cfg(target_arch = "loongarch64")] @@ -81,7 +80,7 @@ fn handle_uefi_fdt_operations( vm_config: &mut AxVMConfig, vm_create_config: &mut AxVMCrateConfig, ) -> AxResult { - crate::guest_platform::loongarch64::prepare_uefi_fdt_config(vm_config, vm_create_config) + crate::boot::guest_platform::loongarch64::prepare_uefi_fdt_config(vm_config, vm_create_config) } #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] @@ -100,7 +99,7 @@ fn handle_uefi_fdt_operations( /// Prepare LoongArch guest firmware FDT facts for UEFI boot. #[cfg(target_arch = "loongarch64")] -pub(crate) fn handle_fdt_operations( +pub fn handle_fdt_operations( vm_config: &mut AxVMConfig, vm_create_config: &mut AxVMCrateConfig, ) -> AxResult { @@ -117,9 +116,10 @@ pub(crate) fn handle_fdt_operations( /// Handle all FDT-related operations for guest architectures that boot with DTB. #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] -pub(crate) fn handle_fdt_operations( +pub fn handle_fdt_operations( vm_config: &mut AxVMConfig, vm_create_config: &mut AxVMCrateConfig, + provider: &dyn BootImageProvider, ) -> AxResult> { if vm_create_config.kernel.effective_boot_protocol() == VMBootProtocol::Uefi { handle_uefi_fdt_operations(vm_config, vm_create_config)?; @@ -134,7 +134,9 @@ pub(crate) fn handle_fdt_operations( .map_err(|e| ax_err_type!(InvalidData, format!("Failed to parse host FDT: {e:#?}")))?; set_phys_cpu_sets(vm_config, &host_fdt, vm_create_config)?; - if let Some(provided_dtb) = get_developer_provided_dtb(vm_config, vm_create_config)? { + if let Some(provided_dtb) = + get_developer_provided_dtb(vm_config, vm_create_config, provider)? + { info!("VM[{}] found DTB , parsing...", vm_config.id()); reserve_excluded_device_ranges(vm_config, vm_create_config, &provided_dtb)?; guest_dtb = Some(GuestDtbImage::new(update_provided_fdt( @@ -153,7 +155,9 @@ pub(crate) fn handle_fdt_operations( vm_create_config, )?)); } - } else if let Some(provided_dtb) = get_developer_provided_dtb(vm_config, vm_create_config)? { + } else if let Some(provided_dtb) = + get_developer_provided_dtb(vm_config, vm_create_config, provider)? + { info!("VM[{}] found DTB , parsing...", vm_config.id()); reserve_excluded_device_ranges(vm_config, vm_create_config, &provided_dtb)?; guest_dtb = Some(GuestDtbImage::new(update_provided_fdt( @@ -205,10 +209,12 @@ pub(crate) fn handle_fdt_operations( fn get_developer_provided_dtb( vm_cfg: &AxVMConfig, crate_config: &AxVMCrateConfig, + provider: &dyn BootImageProvider, ) -> AxResult>> { match crate_config.kernel.image_location.as_deref() { Some("memory") => { - let vm_images = vmcfg::get_memory_images() + let vm_images = provider + .static_vm_images() .iter() .find(|&v| v.id == vm_cfg.id()); @@ -217,10 +223,10 @@ fn get_developer_provided_dtb( return Ok(Some(dtb.to_vec())); } } - #[cfg(feature = "fs")] + #[cfg(any(feature = "fs", feature = "host-fs"))] Some("fs") => { if let Some(dtb_path) = &crate_config.kernel.dtb_path { - let dtb_buffer = crate::images::fs::read_full_image(dtb_path)?; + let dtb_buffer = crate::boot::images::fs::read_full_image(dtb_path, provider)?; info!("DTB file in fs, size: 0x{:x}", dtb_buffer.len()); return Ok(Some(dtb_buffer)); } diff --git a/os/axvisor/src/fdt/parser.rs b/virtualization/axvm/src/boot/fdt/parser.rs similarity index 98% rename from os/axvisor/src/fdt/parser.rs rename to virtualization/axvm/src/boot/fdt/parser.rs index 0760f69f83..244bb47df6 100644 --- a/os/axvisor/src/fdt/parser.rs +++ b/virtualization/axvm/src/boot/fdt/parser.rs @@ -14,29 +14,34 @@ //! FDT parsing and processing functionality. -use alloc::{format, string::ToString, vec, vec::Vec}; +use alloc::{ + format, + string::{String, ToString}, + vec, + vec::Vec, +}; use ax_errno::{AxResult, ax_err_type}; -use fdt_parser::{Fdt, FdtHeader, PciRange, PciSpace}; - -#[cfg(target_arch = "aarch64")] -use crate::fdt::create::update_cpu_node; -use axvm::{MappingFlags, config::AxVMConfig}; use axvmconfig::{ AxVMCrateConfig, PassThroughDeviceConfig, ReservedAddressConfig, VmMemConfig, VmMemMappingType, }; +use fdt_parser::{Fdt, FdtHeader, PciRange, PciSpace}; + +#[cfg(target_arch = "aarch64")] +use crate::boot::fdt::create::update_cpu_node; +use crate::{MappingFlags, config::AxVMConfig}; const PAGE_SIZE_4K: usize = 0x1000; pub fn try_get_host_fdt() -> Option<&'static [u8]> { const FDT_VALID_MAGIC: u32 = 0xd00d_feed; - let bootarg: usize = axvm::host_fdt_bootarg(); + let bootarg: usize = crate::host_fdt_bootarg(); if bootarg == 0 { warn!("Boot argument does not contain a host FDT pointer"); return None; } - let fdt_vaddr = axvm::host_phys_to_virt(bootarg.into()); + let fdt_vaddr = crate::host_phys_to_virt(bootarg.into()); let header = unsafe { core::slice::from_raw_parts(fdt_vaddr.as_ptr(), core::mem::size_of::()) }; @@ -426,12 +431,15 @@ pub fn parse_reserved_memory_regions(crate_cfg: &mut AxVMCrateConfig, dtb: &[u8] #[cfg(test)] mod tests { - use super::{align_reserved_region_4k, reserve_excluded_device_ranges}; - use crate::fdt::vm_fdt::FdtWriter; - use axvm::config::{AxVMConfig, AxVMConfigParams, PhysCpuList}; use axvm_types::AddressSpacePolicy; use axvmconfig::{AxVMCrateConfig, VMDevicesConfig}; + use super::{align_reserved_region_4k, reserve_excluded_device_ranges}; + use crate::{ + boot::fdt::vm_fdt::FdtWriter, + config::{AxVMConfig, AxVMConfigParams, PhysCpuList}, + }; + fn fdt_with_excluded_devices() -> Vec { let mut writer = FdtWriter::new().unwrap(); let root = writer.begin_node("").unwrap(); diff --git a/os/axvisor/src/fdt/print.rs b/virtualization/axvm/src/boot/fdt/print.rs similarity index 100% rename from os/axvisor/src/fdt/print.rs rename to virtualization/axvm/src/boot/fdt/print.rs diff --git a/os/axvisor/src/fdt/vm_fdt/mod.rs b/virtualization/axvm/src/boot/fdt/vm_fdt/mod.rs similarity index 100% rename from os/axvisor/src/fdt/vm_fdt/mod.rs rename to virtualization/axvm/src/boot/fdt/vm_fdt/mod.rs diff --git a/os/axvisor/src/fdt/vm_fdt/writer.rs b/virtualization/axvm/src/boot/fdt/vm_fdt/writer.rs similarity index 100% rename from os/axvisor/src/fdt/vm_fdt/writer.rs rename to virtualization/axvm/src/boot/fdt/vm_fdt/writer.rs diff --git a/os/axvisor/src/guest_platform/loongarch64/mod.rs b/virtualization/axvm/src/boot/guest_platform/loongarch64/mod.rs similarity index 97% rename from os/axvisor/src/guest_platform/loongarch64/mod.rs rename to virtualization/axvm/src/boot/guest_platform/loongarch64/mod.rs index 756710d3c7..4b17fba3e8 100644 --- a/os/axvisor/src/guest_platform/loongarch64/mod.rs +++ b/virtualization/axvm/src/boot/guest_platform/loongarch64/mod.rs @@ -7,15 +7,13 @@ use ax_errno::{AxResult, ax_err_type}; use axdevice::{ FwCfgInterruptConfig, FwCfgPciConfig, FwCfgPlatformConfig, FwCfgRamRegion, FwCfgSerialConfig, }; -use axvm::{AxVMRef, GuestPhysAddr}; use axvmconfig::{AxVMCrateConfig, EmulatedDeviceType}; - pub use resources::{ LoongArchGuestIrqRoute, get_guest_irq_routes, prepare_uefi_fdt_config, prepare_uefi_runtime_config, }; -use crate::images::load_vm_image_from_memory; +use crate::{AxVMRef, GuestPhysAddr, boot::images::load_vm_image_from_memory}; pub const UEFI_FIRMWARE_FDT_BASE: usize = 0x0010_0000; @@ -147,7 +145,7 @@ impl GuestPlatform { pub fn load_firmware_fdt(vm: &AxVMRef, config: &AxVMCrateConfig) -> AxResult { let platform = GuestPlatform::discover(vm, config); - let fdt = crate::fdt::loongarch64::guest_firmware_dtb::build(&platform)?; + let fdt = crate::boot::fdt::loongarch64::guest_firmware_dtb::build(&platform)?; debug!( "VM[{}] loading LoongArch UEFI firmware FDT: {} bytes at {:#x}", config.base.id, diff --git a/os/axvisor/src/guest_platform/loongarch64/probe.rs b/virtualization/axvm/src/boot/guest_platform/loongarch64/probe.rs similarity index 100% rename from os/axvisor/src/guest_platform/loongarch64/probe.rs rename to virtualization/axvm/src/boot/guest_platform/loongarch64/probe.rs diff --git a/os/axvisor/src/guest_platform/loongarch64/resources.rs b/virtualization/axvm/src/boot/guest_platform/loongarch64/resources.rs similarity index 98% rename from os/axvisor/src/guest_platform/loongarch64/resources.rs rename to virtualization/axvm/src/boot/guest_platform/loongarch64/resources.rs index 2555661c6e..796480c99d 100644 --- a/os/axvisor/src/guest_platform/loongarch64/resources.rs +++ b/virtualization/axvm/src/boot/guest_platform/loongarch64/resources.rs @@ -3,10 +3,13 @@ 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 axvm::{AxVMRef, config::AxVMConfig, config::PassThroughDeviceConfig}; use axvmconfig::AxVMCrateConfig; use super::UEFI_FIRMWARE_FDT_BASE; +use crate::{ + AxVMRef, + config::{AxVMConfig, PassThroughDeviceConfig}, +}; static LOONGARCH_GUEST_IRQ_ROUTES: LazyInit>>> = LazyInit::new(); diff --git a/os/axvisor/src/guest_platform/mod.rs b/virtualization/axvm/src/boot/guest_platform/mod.rs similarity index 100% rename from os/axvisor/src/guest_platform/mod.rs rename to virtualization/axvm/src/boot/guest_platform/mod.rs diff --git a/os/axvisor/src/images/linux.rs b/virtualization/axvm/src/boot/images/linux.rs similarity index 100% rename from os/axvisor/src/images/linux.rs rename to virtualization/axvm/src/boot/images/linux.rs diff --git a/os/axvisor/src/images/mod.rs b/virtualization/axvm/src/boot/images/mod.rs similarity index 87% rename from os/axvisor/src/images/mod.rs rename to virtualization/axvm/src/boot/images/mod.rs index ffb785a065..5bcc1cac7b 100644 --- a/os/axvisor/src/images/mod.rs +++ b/virtualization/axvm/src/boot/images/mod.rs @@ -24,11 +24,12 @@ use axvmconfig::VMBootProtocol; use axvmconfig::VmMemMappingType; use byte_unit::Byte; -use axvm::{AxVMRef, GuestPhysAddr, VMMemoryRegion}; - -use crate::config::vmcfg; #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] -use crate::fdt::GuestDtbImage; +use crate::boot::fdt::GuestDtbImage; +use crate::{ + AxVMRef, GuestPhysAddr, VMMemoryRegion, + boot::{BootImageProvider, StaticVmImage}, +}; mod linux; #[cfg(target_arch = "x86_64")] @@ -45,15 +46,18 @@ use x86::mptable as x86_mptable; use x86::multiboot as x86_boot; #[cfg(target_arch = "x86_64")] -pub fn is_x86_linux_image_config(config: &AxVMCrateConfig) -> bool { +pub fn is_x86_linux_image_config( + config: &AxVMCrateConfig, + provider: &dyn BootImageProvider, +) -> bool { if !should_direct_boot_x86_linux(config) { return false; } match config.kernel.image_location.as_deref() { - Some("memory") => with_memory_image(config, detect_x86_linux_image).is_some(), - #[cfg(feature = "fs")] - Some("fs") => fs::kernel_read(config, x86_linux::HEADER_READ_SIZE) + Some("memory") => with_memory_image(config, provider, detect_x86_linux_image).is_some(), + #[cfg(any(feature = "fs", feature = "host-fs"))] + Some("fs") => fs::kernel_read(config, provider, x86_linux::HEADER_READ_SIZE) .ok() .and_then(|data| detect_x86_linux_image(&data)) .is_some(), @@ -71,33 +75,46 @@ pub const fn x86_qemu_passthrough_block_intx() -> (u8, u8, u8, usize) { ) } -pub fn get_image_header(config: &AxVMCrateConfig) -> Option { +pub fn get_image_header( + config: &AxVMCrateConfig, + provider: &dyn BootImageProvider, +) -> Option { match config.kernel.image_location.as_deref() { - Some("memory") => with_memory_image(config, linux::Header::parse).flatten(), - #[cfg(feature = "fs")] + Some("memory") => with_memory_image(config, provider, linux::Header::parse).flatten(), + #[cfg(any(feature = "fs", feature = "host-fs"))] Some("fs") => { let read_size = linux::Header::hdr_size(); - let data = fs::kernel_read(config, read_size).ok()?; + let data = fs::kernel_read(config, provider, read_size).ok()?; linux::Header::parse(&data) } _ => None, } } -fn with_memory_image(config: &AxVMCrateConfig, func: F) -> Option +fn with_memory_image( + config: &AxVMCrateConfig, + provider: &dyn BootImageProvider, + func: F, +) -> Option where F: FnOnce(&[u8]) -> R, { - let vm_images = vmcfg::get_memory_images() + let vm_images = provider + .static_vm_images() .iter() .find(|&v| v.id == config.base.id)?; Some(func(vm_images.kernel)) } -fn memory_images_for_vm(config: &AxVMCrateConfig) -> AxResult<&'static vmcfg::MemoryImage> { - vmcfg::get_memory_images() +fn memory_images_for_vm( + config: &AxVMCrateConfig, + provider: &dyn BootImageProvider, +) -> AxResult { + provider + .static_vm_images() .iter() + .copied() .find(|&v| v.id == config.base.id) .ok_or_else(|| { ax_err_type!( @@ -108,14 +125,20 @@ fn memory_images_for_vm(config: &AxVMCrateConfig) -> AxResult<&'static vmcfg::Me } #[cfg(target_arch = "loongarch64")] -fn firmware_image_for_vm(config: &AxVMCrateConfig) -> Option<&'static [u8]> { - vmcfg::get_firmware_images() +fn provider_firmware_image_for_vm( + config: &AxVMCrateConfig, + provider: &dyn BootImageProvider, +) -> Option<&'static [u8]> { + provider + .static_firmware_images() .iter() .find(|&v| v.id == config.base.id) .map(|v| v.bios) + .flatten() } -pub struct ImageLoader { +pub struct ImageLoader<'a> { + provider: &'a dyn BootImageProvider, main_memory: VMMemoryRegion, vm: AxVMRef, config: AxVMCrateConfig, @@ -126,16 +149,18 @@ pub struct ImageLoader { ramdisk_load_gpa: Option, } -impl ImageLoader { +impl<'a> ImageLoader<'a> { pub fn new( main_memory: VMMemoryRegion, config: AxVMCrateConfig, vm: AxVMRef, + provider: &'a dyn BootImageProvider, #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] guest_dtb: Option< GuestDtbImage, >, ) -> Self { Self { + provider, main_memory, vm, config, @@ -152,7 +177,7 @@ impl ImageLoader { let bytes = dtb.as_bytes(); let dtb_src = core::ptr::NonNull::new(bytes.as_ptr() as *mut u8) .ok_or_else(|| ax_err_type!(InvalidData, "Guest DTB pointer is null"))?; - crate::fdt::update_fdt(dtb_src, bytes.len(), self.vm.clone(), &self.config) + crate::boot::fdt::update_fdt(dtb_src, bytes.len(), self.vm.clone(), &self.config) } pub fn load(&mut self) -> AxResult { @@ -173,7 +198,7 @@ impl ImageLoader { match self.config.kernel.image_location.as_deref() { Some("memory") => self.load_vm_images_from_memory(), - #[cfg(feature = "fs")] + #[cfg(any(feature = "fs", feature = "host-fs"))] Some("fs") => fs::load_vm_images_from_filesystem(self), _ => ax_err!( InvalidInput, @@ -187,7 +212,7 @@ impl ImageLoader { fn load_vm_images_from_memory(&mut self) -> AxResult { debug!("Loading VM[{}] images from memory", self.config.base.id); - let vm_images = memory_images_for_vm(&self.config)?; + let vm_images = memory_images_for_vm(&self.config, self.provider)?; #[cfg(target_arch = "x86_64")] if should_direct_boot_x86_linux(&self.config) @@ -289,22 +314,25 @@ impl ImageLoader { let load_gpa = self.bios_load_gpa.ok_or_else(|| { ax_errno::ax_err_type!(NotFound, "UEFI firmware load addr is missed") })?; + #[cfg(not(any(feature = "fs", feature = "host-fs")))] + let _ = (firmware_path, load_gpa); - #[cfg(feature = "fs")] + #[cfg(any(feature = "fs", feature = "host-fs"))] { info!( "Loading UEFI firmware image {} at GPA {:#x}", firmware_path, load_gpa.as_usize() ); - return fs::load_vm_image(firmware_path, load_gpa, self.vm.clone()); + return fs::load_vm_image(firmware_path, load_gpa, self.vm.clone(), self.provider); } - #[cfg(not(feature = "fs"))] + #[cfg(not(any(feature = "fs", feature = "host-fs")))] { return Err(ax_errno::ax_err_type!( Unsupported, - "UEFI firmware path requires the fs feature when no firmware image buffer is available" + "UEFI firmware path requires the fs feature when no firmware image buffer is \ + available" )); } } @@ -339,16 +367,16 @@ impl ImageLoader { kernel: &'static [u8], ramdisk: Option<&'static [u8]>, ) -> AxResult { - let fw_cfg = crate::guest_platform::loongarch64::emulated_fw_cfg(&self.config)?; + let fw_cfg = crate::boot::guest_platform::loongarch64::emulated_fw_cfg(&self.config)?; - self.vm.add_fw_cfg_device(axvm::FwCfgDeviceConfig { + self.vm.add_fw_cfg_device(crate::FwCfgDeviceConfig { base: GuestPhysAddr::from(fw_cfg.base_gpa), size: fw_cfg.length, kernel, initrd: ramdisk, cmdline: self.config.kernel.cmdline.clone(), cpu_num: self.config.base.cpu_num as u16, - platform: crate::guest_platform::loongarch64::fw_cfg_platform_config( + platform: crate::boot::guest_platform::loongarch64::fw_cfg_platform_config( &self.vm, &self.config, ), @@ -374,7 +402,7 @@ impl ImageLoader { #[cfg(target_arch = "loongarch64")] fn load_loongarch_uefi_firmware_from_memory(&self, bios: Option<&[u8]>) -> AxResult { let firmware = bios - .or_else(|| firmware_image_for_vm(&self.config)) + .or_else(|| provider_firmware_image_for_vm(&self.config, self.provider)) .ok_or_else(|| { ax_err_type!( NotFound, @@ -402,8 +430,11 @@ impl ImageLoader { #[cfg(target_arch = "loongarch64")] fn load_loongarch_uefi_firmware_dtb(&self) -> AxResult { - crate::guest_platform::loongarch64::prepare_uefi_runtime_config(&self.vm, &self.config); - crate::guest_platform::loongarch64::load_firmware_fdt(&self.vm, &self.config) + crate::boot::guest_platform::loongarch64::prepare_uefi_runtime_config( + &self.vm, + &self.config, + ); + crate::boot::guest_platform::loongarch64::load_firmware_fdt(&self.vm, &self.config) } #[cfg(target_arch = "x86_64")] @@ -635,13 +666,13 @@ impl ImageLoader { }) } - #[cfg(feature = "fs")] + #[cfg(any(feature = "fs", feature = "host-fs"))] fn load_ramdisk_from_filesystem(&self, ramdisk_path: &str) -> AxResult { let load_gpa = self .vm .with_config(|config| config.image_config.ramdisk.as_ref().map(|r| r.load_gpa)) .ok_or_else(|| ax_errno::ax_err_type!(NotFound, "Ramdisk load addr is missed"))?; - let ramdisk_size = fs::image_size(ramdisk_path)?; + let ramdisk_size = fs::image_size(ramdisk_path, self.provider)?; self.vm.with_config(|config| { if let Some(ref mut rd) = config.image_config.ramdisk { rd.size = Some(ramdisk_size); @@ -653,7 +684,7 @@ impl ImageLoader { ramdisk_size, load_gpa.as_usize() ); - fs::load_vm_image(ramdisk_path, load_gpa, self.vm.clone()) + fs::load_vm_image(ramdisk_path, load_gpa, self.vm.clone(), self.provider) } } @@ -691,7 +722,7 @@ pub fn load_vm_image_from_memory( ); } - axvm::clean_dcache_range((region.as_ptr() as usize).into(), bytes_to_write); + crate::clean_dcache_range((region.as_ptr() as usize).into(), bytes_to_write); // Update the position of the buffer. buffer_pos += bytes_to_write; @@ -722,7 +753,7 @@ fn fill_vm_region(load_addr: GuestPhysAddr, size: usize, byte: u8, vm: AxVMRef) unsafe { core::ptr::write_bytes(region.as_mut_ptr(), byte, region.len()); } - axvm::clean_dcache_range((region.as_ptr() as usize).into(), region.len()); + crate::clean_dcache_range((region.as_ptr() as usize).into(), region.len()); filled_size += region.len(); } @@ -736,17 +767,23 @@ fn fill_vm_region(load_addr: GuestPhysAddr, size: usize, byte: u8, vm: AxVMRef) } } -#[cfg(feature = "fs")] +#[cfg(any(feature = "fs", feature = "host-fs"))] pub mod fs { + #[cfg(target_arch = "loongarch64")] + use alloc::boxed::Box; use alloc::vec::Vec; use ax_errno::{AxResult, ax_err, ax_err_type}; use super::*; - pub fn kernel_read(config: &AxVMCrateConfig, read_size: usize) -> AxResult> { + pub fn kernel_read( + config: &AxVMCrateConfig, + provider: &dyn BootImageProvider, + read_size: usize, + ) -> AxResult> { let file_name = &config.kernel.kernel_path; - crate::manager::AxvmManager::read_file_exact(file_name, read_size) + provider.read_file_exact(file_name, read_size) } /// Loads the VM image files from the filesystem @@ -756,11 +793,15 @@ pub mod fs { #[cfg(target_arch = "x86_64")] { if should_direct_boot_x86_linux(&loader.config) { - let kernel_probe = kernel_read(&loader.config, x86_linux::HEADER_READ_SIZE); + let kernel_probe = + kernel_read(&loader.config, loader.provider, x86_linux::HEADER_READ_SIZE); match kernel_probe { Ok(data) => { if let Some(header) = detect_x86_linux_image(&data) { - let kernel = read_image_file(&loader.config.kernel.kernel_path)?; + let kernel = read_image_file( + &loader.config.kernel.kernel_path, + loader.provider, + )?; return loader.load_x86_linux_images_from_filesystem(header, &kernel); } } @@ -780,6 +821,7 @@ pub mod fs { &loader.config.kernel.kernel_path, loader.kernel_load_gpa, loader.vm.clone(), + loader.provider, )?; } #[cfg(not(target_arch = "loongarch64"))] @@ -787,6 +829,7 @@ pub mod fs { &loader.config.kernel.kernel_path, loader.kernel_load_gpa, loader.vm.clone(), + loader.provider, )?; // Load boot firmware image if needed. if loader.config.kernel.enable_bios @@ -796,16 +839,26 @@ pub mod fs { #[cfg(target_arch = "x86_64")] { if should_patch_x86_multiboot_info(&loader.config) { - let bios_image = read_image_file(bios_path)?; + let bios_image = read_image_file(bios_path, loader.provider)?; validate_x86_bios_patch_region(&bios_image)?; load_vm_image_from_memory(&bios_image, bios_load_addr, loader.vm.clone())?; loader.load_x86_multiboot_info(&bios_image, bios_load_addr)?; } else { - load_vm_image(bios_path, bios_load_addr, loader.vm.clone())?; + load_vm_image( + bios_path, + bios_load_addr, + loader.vm.clone(), + loader.provider, + )?; } } #[cfg(not(target_arch = "x86_64"))] - load_vm_image(bios_path, bios_load_addr, loader.vm.clone())?; + load_vm_image( + bios_path, + bios_load_addr, + loader.vm.clone(), + loader.provider, + )?; } else { return ax_err!(NotFound, "boot firmware load addr is missed"); } @@ -842,12 +895,12 @@ pub mod fs { } #[cfg(target_arch = "loongarch64")] - impl ImageLoader { + impl ImageLoader<'_> { fn add_loongarch_uefi_fw_cfg_from_filesystem(&self) -> AxResult { - let kernel = read_full_image(&self.config.kernel.kernel_path)?; + let kernel = read_full_image(&self.config.kernel.kernel_path, self.provider)?; let kernel: &'static [u8] = Box::leak(kernel.into_boxed_slice()); let ramdisk = if let Some(path) = &self.config.kernel.ramdisk_path { - let ramdisk = read_full_image(path)?; + let ramdisk = read_full_image(path, self.provider)?; Some(Box::leak(ramdisk.into_boxed_slice()) as &'static [u8]) } else { None @@ -864,18 +917,19 @@ pub mod fs { } fn load_loongarch_uefi_firmware_from_filesystem(&self) -> AxResult { - let firmware = firmware_image_for_vm(&self.config).ok_or_else(|| { - ax_err_type!( - NotFound, - "LoongArch UEFI boot requires a build-time firmware image" - ) - })?; + let firmware = + provider_firmware_image_for_vm(&self.config, self.provider).ok_or_else(|| { + ax_err_type!( + NotFound, + "LoongArch UEFI boot requires a build-time firmware image" + ) + })?; self.load_loongarch_uefi_firmware_image(firmware) } } #[cfg(target_arch = "x86_64")] - impl ImageLoader { + impl ImageLoader<'_> { fn load_x86_linux_images_from_filesystem( &mut self, header: x86_linux::X86LinuxHeader, @@ -884,7 +938,7 @@ pub mod fs { self.adjust_x86_linux_dma_identity_layout()?; let payload = x86_linux_payload(&header, kernel)?; let initrd = if let Some(ramdisk_path) = &self.config.kernel.ramdisk_path { - let ramdisk_size = image_size(ramdisk_path)?; + let ramdisk_size = image_size(ramdisk_path, self.provider)?; Some(x86_linux::X86LinuxRange::new( self.ramdisk_load_gpa()?.as_usize(), ramdisk_size, @@ -915,8 +969,9 @@ pub mod fs { image_path: &str, image_load_gpa: GuestPhysAddr, vm: AxVMRef, + provider: &dyn BootImageProvider, ) -> AxResult { - let image = crate::manager::AxvmManager::read_file(image_path)?; + let image = provider.read_file(image_path)?; let image_size = image.len(); let image_load_regions = vm.get_image_load_region(image_load_gpa, image_size)?; @@ -933,19 +988,19 @@ pub mod fs { buffer.copy_from_slice(data); offset = end; - axvm::clean_dcache_range((buffer.as_ptr() as usize).into(), buffer.len()); + crate::clean_dcache_range((buffer.as_ptr() as usize).into(), buffer.len()); } Ok(()) } #[cfg(target_arch = "x86_64")] - fn read_image_file(image_path: &str) -> AxResult> { - crate::manager::AxvmManager::read_file(image_path) + fn read_image_file(image_path: &str, provider: &dyn BootImageProvider) -> AxResult> { + provider.read_file(image_path) } - pub fn image_size(file_name: &str) -> AxResult { - crate::manager::AxvmManager::file_size(file_name) + pub fn image_size(file_name: &str, provider: &dyn BootImageProvider) -> AxResult { + provider.file_size(file_name) } #[cfg(any( @@ -953,8 +1008,8 @@ pub mod fs { target_arch = "loongarch64", target_arch = "riscv64" ))] - pub fn read_full_image(file_name: &str) -> AxResult> { - crate::manager::AxvmManager::read_file(file_name) + pub fn read_full_image(file_name: &str, provider: &dyn BootImageProvider) -> AxResult> { + provider.read_file(file_name) } } diff --git a/os/axvisor/src/images/x86/boot_params.rs b/virtualization/axvm/src/boot/images/x86/boot_params.rs similarity index 98% rename from os/axvisor/src/images/x86/boot_params.rs rename to virtualization/axvm/src/boot/images/x86/boot_params.rs index 7a1f6b6ba5..1906cdba4a 100644 --- a/os/axvisor/src/images/x86/boot_params.rs +++ b/virtualization/axvm/src/boot/images/x86/boot_params.rs @@ -354,10 +354,8 @@ fn write_u64(buffer: &mut [u8], offset: usize, value: u64) { #[cfg(test)] mod tests { - use super::{ - linux::{BOOT_PARAMS_GPA, BOOT_STUB_GPA, BOOT_STUB_SIZE}, - *, - }; + use super::*; + use crate::boot::images::x86::linux::{BOOT_PARAMS_GPA, BOOT_STUB_GPA, BOOT_STUB_SIZE}; const SETUP_SECTS_OFFSET: usize = 0x1f1; const BOOT_FLAG_OFFSET: usize = 0x1fe; @@ -509,10 +507,10 @@ mod tests { let image = valid_image(); let header = X86LinuxHeader::parse(&image).unwrap(); let layout = valid_layout(&header); - let params = - BootParamsBuilder::new(&image, header, layout, X86LinuxRange::new(0, 0x20_0000)) - .build() - .unwrap(); + let mut builder = + BootParamsBuilder::new(&image, header, layout, X86LinuxRange::new(0, 0x20_0000)); + builder.set_command_line("console=ttyS0").unwrap(); + let params = builder.build().unwrap(); let entries = read_u8(¶ms, E820_ENTRIES_OFFSET) as usize; assert!(entries >= 5); @@ -542,6 +540,7 @@ mod tests { X86LinuxRange::new(0x0960_0000, 0x0800_0000), ); builder.add_ram_range(X86LinuxRange::new(0, 0x10_0000)); + builder.set_command_line("console=ttyS0").unwrap(); let params = builder.build().unwrap(); let entries = read_u8(¶ms, E820_ENTRIES_OFFSET) as usize; @@ -569,6 +568,7 @@ mod tests { let mut builder = BootParamsBuilder::new(&image, header, layout, X86LinuxRange::new(0, 0x20_0000)); builder.add_reserved_range(X86LinuxRange::new(0xfec0_0000, 0x1000)); + builder.set_command_line("console=ttyS0").unwrap(); let params = builder.build().unwrap(); let entries = read_u8(¶ms, E820_ENTRIES_OFFSET) as usize; diff --git a/os/axvisor/src/images/x86/linux.rs b/virtualization/axvm/src/boot/images/x86/linux.rs similarity index 99% rename from os/axvisor/src/images/x86/linux.rs rename to virtualization/axvm/src/boot/images/x86/linux.rs index 5f2d8ccd64..7fcaa7f37c 100644 --- a/os/axvisor/src/images/x86/linux.rs +++ b/virtualization/axvm/src/boot/images/x86/linux.rs @@ -42,7 +42,7 @@ const BOOT_FLAG_MAGIC: u16 = 0xaa55; const HEADER_MAGIC: u32 = u32::from_le_bytes(*b"HdrS"); /// Number of bytes needed to parse every field used by [`X86LinuxHeader`]. -#[cfg(any(feature = "fs", test))] +#[cfg(any(feature = "fs", feature = "host-fs", test))] pub const HEADER_READ_SIZE: usize = CMDLINE_SIZE_OFFSET + core::mem::size_of::(); /// Parsed subset of Linux x86 setup header fields. diff --git a/os/axvisor/src/images/x86/linux_boot.rs b/virtualization/axvm/src/boot/images/x86/linux_boot.rs similarity index 98% rename from os/axvisor/src/images/x86/linux_boot.rs rename to virtualization/axvm/src/boot/images/x86/linux_boot.rs index 8b2a88e4e6..18fb82ab43 100644 --- a/os/axvisor/src/images/x86/linux_boot.rs +++ b/virtualization/axvm/src/boot/images/x86/linux_boot.rs @@ -104,7 +104,7 @@ fn write_u32(buffer: &mut [u8], offset: usize, value: u32) { #[cfg(test)] mod tests { use super::*; - use crate::images::x86::linux::{BOOT_PARAMS_GPA, X86LinuxHeader, X86LinuxRange}; + use crate::boot::images::x86::linux::{BOOT_PARAMS_GPA, X86LinuxHeader, X86LinuxRange}; const SETUP_SECTS_OFFSET: usize = 0x1f1; const BOOT_FLAG_OFFSET: usize = 0x1fe; diff --git a/os/axvisor/src/images/x86/mod.rs b/virtualization/axvm/src/boot/images/x86/mod.rs similarity index 100% rename from os/axvisor/src/images/x86/mod.rs rename to virtualization/axvm/src/boot/images/x86/mod.rs diff --git a/os/axvisor/src/images/x86/mptable.rs b/virtualization/axvm/src/boot/images/x86/mptable.rs similarity index 100% rename from os/axvisor/src/images/x86/mptable.rs rename to virtualization/axvm/src/boot/images/x86/mptable.rs diff --git a/os/axvisor/src/images/x86/multiboot.rs b/virtualization/axvm/src/boot/images/x86/multiboot.rs similarity index 100% rename from os/axvisor/src/images/x86/multiboot.rs rename to virtualization/axvm/src/boot/images/x86/multiboot.rs diff --git a/virtualization/axvm/src/boot/mod.rs b/virtualization/axvm/src/boot/mod.rs new file mode 100644 index 0000000000..8a86e3ff64 --- /dev/null +++ b/virtualization/axvm/src/boot/mod.rs @@ -0,0 +1,74 @@ +//! Guest boot image and platform planning. + +#[cfg(any(feature = "fs", feature = "host-fs"))] +use ax_errno::ax_err_type; + +pub mod images; +mod policy; + +#[cfg(any( + target_arch = "aarch64", + target_arch = "loongarch64", + target_arch = "riscv64" +))] +pub mod fdt; +#[cfg(target_arch = "loongarch64")] +pub mod guest_platform; + +#[cfg(target_arch = "loongarch64")] +pub use fdt::handle_fdt_operations; +#[cfg(target_arch = "loongarch64")] +pub use fdt::init_guest_boot_resources; +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] +pub use fdt::{GuestDtbImage, handle_fdt_operations}; +pub use images::{ImageLoader, get_image_header}; +#[cfg(target_arch = "x86_64")] +pub use images::{is_x86_linux_image_config, x86_qemu_passthrough_block_intx}; +pub use policy::{GuestAcpiTables, GuestBootDescription, GuestDeviceTree, GuestFdtBuilder}; + +/// Build-time image bytes supplied by the hypervisor application. +#[derive(Clone, Copy, Debug)] +pub struct StaticVmImage { + pub id: usize, + pub kernel: &'static [u8], + pub bios: Option<&'static [u8]>, + pub ramdisk: Option<&'static [u8]>, + pub dtb: Option<&'static [u8]>, +} + +/// Application-owned source for guest image bytes and host files. +/// +/// AxVM owns architecture boot planning, while Axvisor or another monitor owns +/// where bytes come from. +pub trait BootImageProvider { + fn static_vm_images(&self) -> &'static [StaticVmImage]; + + #[cfg(target_arch = "loongarch64")] + fn static_firmware_images(&self) -> &'static [StaticVmImage] { + &[] + } + + #[cfg(any(feature = "fs", feature = "host-fs"))] + fn read_file(&self, file_name: &str) -> ax_errno::AxResult>; + + #[cfg(any(feature = "fs", feature = "host-fs"))] + fn read_file_exact( + &self, + file_name: &str, + read_size: usize, + ) -> ax_errno::AxResult> { + let buffer = self.read_file(file_name)?; + if buffer.len() < read_size { + return Err(ax_err_type!( + InvalidData, + "file is shorter than the requested read size" + )); + } + Ok(buffer[..read_size].to_vec()) + } + + #[cfg(any(feature = "fs", feature = "host-fs"))] + fn file_size(&self, file_name: &str) -> ax_errno::AxResult { + self.read_file(file_name).map(|buffer| buffer.len()) + } +} diff --git a/virtualization/axvm/src/boot.rs b/virtualization/axvm/src/boot/policy.rs similarity index 100% rename from virtualization/axvm/src/boot.rs rename to virtualization/axvm/src/boot/policy.rs diff --git a/virtualization/axvm/src/cache.rs b/virtualization/axvm/src/cache.rs deleted file mode 100644 index 050afcc8f0..0000000000 --- a/virtualization/axvm/src/cache.rs +++ /dev/null @@ -1,49 +0,0 @@ -//! Host cache maintenance helpers used by VM image loading. - -use ax_memory_addr::VirtAddr; - -/// Clean data cache lines covering a host virtual address range. -pub fn clean_dcache_range(addr: VirtAddr, size: usize) { - #[cfg(target_arch = "aarch64")] - { - aarch64_cpu_ext::cache::dcache_range( - aarch64_cpu_ext::cache::CacheOp::Clean, - addr.as_usize(), - size, - ); - } - - #[cfg(target_arch = "loongarch64")] - unsafe { - cache_range::(addr, size); - core::arch::asm!("dbar 0"); - } - - #[cfg(any(target_arch = "x86_64", target_arch = "riscv64"))] - { - let _ = (addr, size); - } -} - -#[cfg(target_arch = "loongarch64")] -const CACHE_LINE_SIZE: usize = 64; -#[cfg(target_arch = "loongarch64")] -const DCACHE_WB: u8 = 0x19; - -#[cfg(target_arch = "loongarch64")] -unsafe fn cache_range(addr: VirtAddr, size: usize) { - if size == 0 { - return; - } - - let start = addr.as_usize() & !(CACHE_LINE_SIZE - 1); - let end = addr.as_usize() + size; - let mut current = start; - - while current < end { - unsafe { - core::arch::asm!("cacop {0}, {1}, 0", const OP, in(reg) current); - } - current += CACHE_LINE_SIZE; - } -} diff --git a/virtualization/axvm/src/config.rs b/virtualization/axvm/src/config.rs index 8eaede53c7..120f2d2b91 100644 --- a/virtualization/axvm/src/config.rs +++ b/virtualization/axvm/src/config.rs @@ -22,6 +22,8 @@ pub use axvm_types::{ VMInterruptMode, VMType, VmMemConfig, VmMemMappingType, }; +use crate::arch::{ArchOps, CurrentArch}; + /// Policy used by AxVM when deriving runtime guest boot image addresses. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub enum GuestBootPolicy { @@ -40,15 +42,6 @@ pub struct AxVCpuConfig { pub bsp_entry: GuestPhysAddr, /// The entry address in GPA for the Application Processor (AP). pub ap_entry: GuestPhysAddr, - /// LoongArch Linux EFI-style boot arguments (a0, a1, a2). - #[cfg(target_arch = "loongarch64")] - pub boot_args: [usize; 3], - /// LoongArch Linux boot stack top. - #[cfg(target_arch = "loongarch64")] - pub boot_stack_top: usize, - /// Whether the LoongArch guest should be entered like firmware after CPU reset. - #[cfg(target_arch = "loongarch64")] - pub firmware_boot: bool, } /// Ramdisk image information. @@ -348,10 +341,6 @@ impl PhysCpuList { /// - The pCpu affinity mask, `None` if not set. /// - The physical id of the vCpu, equal to vCpu id if not provided. pub fn get_vcpu_affinities_pcpu_ids(&self) -> Vec<(usize, Option, usize)> { - let mut vcpu_pcpu_tuples = Vec::new(); - #[cfg(target_arch = "riscv64")] - let mut pcpu_mask_flag = false; - if let Some(phys_cpu_ids) = &self.phys_cpu_ids && self.cpu_num != phys_cpu_ids.len() { @@ -360,39 +349,11 @@ impl PhysCpuList { self.cpu_num, self.phys_cpu_ids ); } - - for vcpu_id in 0..self.cpu_num { - vcpu_pcpu_tuples.push((vcpu_id, None, vcpu_id)); - } - - #[cfg(target_arch = "riscv64")] - if let Some(phys_cpu_sets) = &self.phys_cpu_sets { - pcpu_mask_flag = true; - for (vcpu_id, pcpu_mask_bitmap) in phys_cpu_sets.iter().enumerate() { - vcpu_pcpu_tuples[vcpu_id].1 = Some(*pcpu_mask_bitmap); - } - } - - #[cfg(not(target_arch = "riscv64"))] - if let Some(phys_cpu_sets) = &self.phys_cpu_sets { - for (vcpu_id, pcpu_mask_bitmap) in phys_cpu_sets.iter().enumerate() { - vcpu_pcpu_tuples[vcpu_id].1 = Some(*pcpu_mask_bitmap); - } - } - - if let Some(phys_cpu_ids) = &self.phys_cpu_ids { - for (vcpu_id, phys_id) in phys_cpu_ids.iter().enumerate() { - vcpu_pcpu_tuples[vcpu_id].2 = *phys_id; - #[cfg(target_arch = "riscv64")] - { - if !pcpu_mask_flag { - // if don't assign pcpu mask yet, assign it manually - vcpu_pcpu_tuples[vcpu_id].1 = Some(1 << (*phys_id)); - } - } - } - } - vcpu_pcpu_tuples + CurrentArch::vcpu_affinities( + self.cpu_num, + self.phys_cpu_ids.as_deref(), + self.phys_cpu_sets.as_deref(), + ) } /// Returns the number of CPUs. diff --git a/virtualization/axvm/src/host/arceos.rs b/virtualization/axvm/src/host/arceos.rs index 57d1a2127d..98888a8a71 100644 --- a/virtualization/axvm/src/host/arceos.rs +++ b/virtualization/axvm/src/host/arceos.rs @@ -24,7 +24,10 @@ use axvm_types::{HostPhysAddr, HostVirtAddr}; #[cfg(target_arch = "x86_64")] use crate::host::HostConsole; -use crate::host::{HostCpu, HostMemory, HostPlatform, HostTime}; +use crate::{ + arch::{ArchOps, CurrentArch}, + host::{HostCpu, HostMemory, HostPlatform, HostTime}, +}; /// Private default host adapter used by [`crate::AxvmRuntime`]. pub(crate) struct ArceOsHost; @@ -124,7 +127,6 @@ pub(crate) fn handle_host_irq(vector: usize) -> Option { modules::ax_hal::irq::handle_irq(vector).then_some(vector) } -#[cfg(not(target_arch = "aarch64"))] pub(crate) fn dispatch_host_irq(vector: usize) { modules::ax_hal::irq::handle_irq(vector); } @@ -301,19 +303,7 @@ impl HostConsole for ArceOsHost { impl HostPlatform for ArceOsHost { fn has_hardware_support(&self) -> bool { - cfg_if::cfg_if! { - if #[cfg(target_arch = "x86_64")] { - x86_vcpu::has_hardware_support() - } else if #[cfg(target_arch = "riscv64")] { - riscv_vcpu::has_hardware_support() - } else if #[cfg(target_arch = "loongarch64")] { - loongarch_vcpu::has_hardware_support() - } else if #[cfg(target_arch = "aarch64")] { - arm_vcpu::has_hardware_support() - } else { - false - } - } + CurrentArch::has_hardware_support() } fn enable_virtualization_on_current_cpu(&self) -> AxResult { @@ -374,7 +364,7 @@ impl HostPlatform for ArceOsHost { break; } } - crate::arch::register_platform_irq_injector(); + CurrentArch::register_platform_irq_injector(); let enabled_count = CORES.load(Ordering::Acquire); if enabled_count == cpu_count { info!("All cores have enabled hardware virtualization support."); diff --git a/virtualization/axvm/src/lib.rs b/virtualization/axvm/src/lib.rs index 0394aa42cd..1a448c05df 100644 --- a/virtualization/axvm/src/lib.rs +++ b/virtualization/axvm/src/lib.rs @@ -25,7 +25,6 @@ extern crate log; mod arch; pub mod boot; -mod cache; mod host; pub mod irq; pub mod layout; @@ -38,13 +37,15 @@ mod timer; mod vcpu; mod vm; +use crate::arch::ArchOps; + pub mod config; pub use ax_cpumask::CpuMask; pub use ax_page_table_entry::MappingFlags; pub use axvm_types::{ - AccessWidth, AxVCpuExitReason, GuestPhysAddr, HostPhysAddr, InterruptTriggerMode, Port, - SysRegAddr, VMId, + AccessWidth, GuestPhysAddr, HostPhysAddr, InterruptTriggerMode, Port, SysRegAddr, VMId, VmExit, + VmVcpuState, }; pub(crate) use host::{ paging::HostPagingHandler, @@ -61,12 +62,13 @@ pub use runtime::loongarch_irq::{ register_guest_irq_route as register_loongarch_guest_irq_route, unregister_guest_irq_routes as unregister_loongarch_guest_irq_routes, }; -pub use task::{AsVCpuTask, VCpuTask}; -pub use vcpu::VCpuState; -pub use vm::{AxVCpuRef, AxVM, AxVMRef, FwCfgDeviceConfig, PreparedMemoryLayout, VMMemoryRegion}; +pub(crate) use task::{AsVCpuTask, VCpuTask}; +pub use vm::{ + AxVM, AxVMRef, FwCfgDeviceConfig, PreparedMemoryLayout, VMMemoryRegion, VcpuSnapshot, +}; /// The architecture-independent per-CPU type. -pub type AxVMPerCpu = vcpu::AxPerCpu; +pub(crate) type AxVMPerCpu = vcpu::AxPerCpu; /// Check and dispatch pending AxVM timer events on the current CPU. pub fn check_timer_events() { @@ -75,7 +77,7 @@ pub fn check_timer_events() { /// Clean data cache lines covering a host virtual address range. pub fn clean_dcache_range(addr: ax_memory_addr::VirtAddr, size: usize) { - cache::clean_dcache_range(addr, size); + arch::CurrentArch::clean_dcache_range(addr, size); } /// Return the host FDT boot argument physical address. diff --git a/virtualization/axvm/src/manager.rs b/virtualization/axvm/src/manager.rs index c4b27e9dd2..d0aa97b20f 100644 --- a/virtualization/axvm/src/manager.rs +++ b/virtualization/axvm/src/manager.rs @@ -9,8 +9,9 @@ use ax_kspin::SpinNoIrq as Mutex; use axvm_types::VMId; use crate::{ + arch::ArchVCpu, host::{HostPlatform, default_host}, - vcpu::{AxArchVCpuImpl, get_current_vcpu}, + vcpu::get_current_vcpu, vm::AxVMRef, }; @@ -99,17 +100,17 @@ pub(crate) fn inject_vm_vcpu_interrupt(vm_id: VMId, vcpu_id: usize, vector: usiz /// Return the current VM ID from the vCPU currently executing on this CPU. pub fn current_vm_id() -> Option { - get_current_vcpu::().map(|vcpu| vcpu.vm_id()) + get_current_vcpu::().map(|vcpu| vcpu.vm_id()) } /// Return the current vCPU ID from the vCPU currently executing on this CPU. pub fn current_vcpu_id() -> Option { - get_current_vcpu::().map(|vcpu| vcpu.id()) + get_current_vcpu::().map(|vcpu| vcpu.id()) } /// 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::() + let vcpu = get_current_vcpu::() .ok_or_else(|| ax_err_type!(BadState, "current vCPU is not set"))?; vcpu.inject_interrupt(vector) } diff --git a/virtualization/axvm/src/runtime/mod.rs b/virtualization/axvm/src/runtime/mod.rs index abec185476..e5542929de 100644 --- a/virtualization/axvm/src/runtime/mod.rs +++ b/virtualization/axvm/src/runtime/mod.rs @@ -19,7 +19,7 @@ mod ivc; pub mod loongarch_irq; pub(crate) mod vcpus; #[cfg(target_arch = "x86_64")] -mod x86_irq; +pub(crate) mod x86_irq; use core::sync::atomic::{AtomicUsize, Ordering}; @@ -32,7 +32,7 @@ use crate::{StopReason, VmStatus}; /// The instantiated VM ref type (by `Arc`). pub type VMRef = crate::AxVMRef; /// The instantiated VCpu ref type (by `Arc`). -pub type VCpuRef = crate::AxVCpuRef; +pub type VCpuRef = crate::vm::AxVCpuRef; static VMM: crate::HostWaitQueueHandle = crate::HostWaitQueueHandle::new(); diff --git a/virtualization/axvm/src/runtime/vcpus.rs b/virtualization/axvm/src/runtime/vcpus.rs index b77feaa012..b65f35b7d0 100644 --- a/virtualization/axvm/src/runtime/vcpus.rs +++ b/virtualization/axvm/src/runtime/vcpus.rs @@ -15,14 +15,12 @@ use alloc::format; use ax_errno::{AxResult, ax_err_type}; -#[cfg(target_arch = "riscv64")] -use riscv_vcpu::GprIndex as RiscvGprIndex; use crate::{ - AsVCpuTask, AxVCpuExitReason, CpuMask, GuestPhysAddr, StopReason, VCpuState, VCpuTask, - VmStatus, + AsVCpuTask, GuestPhysAddr, StopReason, VCpuTask, VmExit, VmStatus, VmVcpuState, + arch::{ArchOps, CurrentArch}, runtime::{VCpuRef, VMRef, sub_running_vm_count}, - vm::{PendingInterrupt, VmRuntimeHandle}, + vm::VmRuntimeHandle, }; const KERNEL_STACK_SIZE: usize = 0x40000; // 256 KiB @@ -143,89 +141,8 @@ pub(crate) fn inject_pending_interrupts(vm_id: usize, vcpu_id: usize, vcpu: &VCp }; for interrupt in interrupts { - match interrupt { - PendingInterrupt::Normal(vector) => { - trace!("Injecting queued interrupt {vector:#x} into VM[{vm_id}] VCpu[{vcpu_id}]"); - if let Err(err) = vcpu.inject_interrupt(vector) { - warn!( - "Failed to inject queued interrupt {vector:#x} into VM[{vm_id}] \ - VCpu[{vcpu_id}]: {err:?}" - ); - } - } - #[cfg(target_arch = "loongarch64")] - PendingInterrupt::LoongArchExternal { - vector, - physical_irq, - } => { - let Some(vm) = crate::get_vm_by_id(vm_id) else { - warn!("VM[{vm_id}] not found while injecting queued LoongArch external IRQ"); - continue; - }; - let Some(vector) = vm.loongarch_external_irq_vector(vector, physical_irq) else { - trace!( - "Queued LoongArch external interrupt physical_irq={physical_irq:#x} is \ - masked in VM[{vm_id}]" - ); - continue; - }; - trace!( - "Injecting queued LoongArch external interrupt vector={vector:#x}, \ - physical_irq={physical_irq:#x} into VM[{vm_id}] VCpu[{vcpu_id}]" - ); - if let Err(err) = vcpu - .get_arch_vcpu() - .inject_external_interrupt(vector, physical_irq) - { - warn!( - "Failed to inject queued LoongArch external interrupt vector={vector:#x}, \ - physical_irq={physical_irq:#x} into VM[{vm_id}] VCpu[{vcpu_id}]: {err:?}" - ); - } - } - } - } -} - -fn ipi_targets( - vm: &VMRef, - current_vcpu_id: usize, - target_cpu: u64, - target_cpu_aux: u64, - send_to_all: bool, - send_to_self: bool, -) -> CpuMask<64> { - let mut targets = CpuMask::new(); - - if send_to_all { - for vcpu in vm.vcpu_list() { - if vcpu.id() != current_vcpu_id { - targets.set(vcpu.id(), true); - } - } - } else if send_to_self { - targets.set(current_vcpu_id, true); - } else { - #[cfg(target_arch = "aarch64")] - { - for (vcpu_id, _, phys_id) in vm.get_vcpu_affinities_pcpu_ids() { - let affinity = phys_id as u64; - let aff0 = affinity & 0xff; - let aff123 = affinity & !0xff; - if aff123 == target_cpu && aff0 < 16 && (target_cpu_aux & (1u64 << aff0)) != 0 { - targets.set(vcpu_id, true); - } - } - } - - #[cfg(not(target_arch = "aarch64"))] - { - let _ = target_cpu_aux; - targets.set(target_cpu as usize, true); - } + CurrentArch::inject_pending_interrupt(&vm, vcpu, interrupt); } - - targets } /// Cleans up VCpu resources for a VM that is being deleted. @@ -273,7 +190,7 @@ fn vcpu_on(vm: VMRef, vcpu_id: usize, entry_point: GuestPhysAddr, arg: usize) -> .get(vcpu_id) .cloned() .ok_or_else(|| ax_err_type!(NotFound, format!("vCPU {vcpu_id} not found")))?; - if vcpu.state() != VCpuState::Free { + if vcpu.state() != VmVcpuState::Free { return Err(ax_err_type!( BadState, format!("vCPU {} invalid state {:?}", vcpu.id(), vcpu.state()) @@ -281,18 +198,7 @@ fn vcpu_on(vm: VMRef, vcpu_id: usize, entry_point: GuestPhysAddr, arg: usize) -> } vcpu.set_entry(entry_point)?; - #[cfg(not(target_arch = "riscv64"))] - vcpu.set_gpr(0, arg); - - #[cfg(target_arch = "riscv64")] - { - info!( - "vcpu_on: vcpu[{}] entry={:x} opaque={:x}", - vcpu_id, entry_point, arg - ); - vcpu.set_gpr(RiscvGprIndex::A0 as usize, vcpu_id); - vcpu.set_gpr(RiscvGprIndex::A1 as usize, arg); - } + CurrentArch::set_vcpu_on_args(&vcpu, vcpu_id, arg); let vcpu_task = alloc_vcpu_task(&vm, vcpu); vm.with_runtime(|runtime| { @@ -382,21 +288,16 @@ fn vcpu_run() { wait_for(&runtime, || vm.running()); info!("VM[{}] VCpu[{}] running...", vm.id(), vcpu.id()); - #[cfg(target_arch = "x86_64")] - super::x86_irq::enable_ioapic_irq_forwarding(&vm, &vcpu); + CurrentArch::before_first_run(&vm, &vcpu); mark_vcpu_running(&vm); loop { inject_pending_interrupts(vm_id, vcpu_id, &vcpu); - - #[cfg(target_arch = "x86_64")] - super::x86_irq::drain_pending_ioapic_irqs(&vm, &vcpu); - #[cfg(target_arch = "x86_64")] - super::x86_irq::activate_ready_ioapic_forwarding_routes(&vm); + CurrentArch::before_vcpu_run(&vm, &vcpu); match vm.run_vcpu(vcpu_id) { Ok(exit_reason) => match exit_reason { - AxVCpuExitReason::Hypercall { nr, args } => { + VmExit::Hypercall { nr, args } => { debug!("Hypercall [{nr}] args {args:x?}"); use crate::runtime::hvc::HyperCall; @@ -416,7 +317,7 @@ fn vcpu_run() { } } } - AxVCpuExitReason::FailEntry { + VmExit::FailEntry { hardware_entry_failure_reason, } => { warn!( @@ -424,76 +325,32 @@ fn vcpu_run() { {hardware_entry_failure_reason}" ); } - AxVCpuExitReason::ExternalInterrupt { vector } => { + VmExit::ExternalInterrupt { vector } => { debug!("VM[{vm_id}] run VCpu[{vcpu_id}] get irq {vector}"); - - // TODO: maybe move this irq dispatcher to lower layer to accelerate the interrupt handling - #[cfg(not(any(target_arch = "aarch64", target_arch = "riscv64")))] - crate::host::arceos::dispatch_host_irq(vector as usize); - #[cfg(target_arch = "riscv64")] - vcpu.with_current_cpu_set(|| { - crate::host::arceos::dispatch_host_irq(vector as usize); - vcpu.get_arch_vcpu().latch_hvip_from_hw(); - }); - crate::check_timer_events(); - #[cfg(target_arch = "x86_64")] - super::x86_irq::inject_pending_serial_irq(&vm, &vcpu); + CurrentArch::after_external_interrupt(&vm, &vcpu, vector as usize); } - AxVCpuExitReason::PreemptionTimer => { - crate::timer::check_events(); - #[cfg(target_arch = "x86_64")] - super::x86_irq::inject_due_pit_irq0(&vm, &vcpu); - #[cfg(target_arch = "x86_64")] - super::x86_irq::inject_pending_serial_irq(&vm, &vcpu); + VmExit::PreemptionTimer => { + CurrentArch::after_preemption_timer(&vm, &vcpu); } - AxVCpuExitReason::InterruptEnd { vector: _vector } => { - #[cfg(target_arch = "x86_64")] - if let Some(vector) = _vector { - super::x86_irq::inject_pending_ioapic_irq_after_eoi(&vm, &vcpu, vector); - } + VmExit::InterruptEnd { vector } => { + CurrentArch::after_interrupt_end(&vm, &vcpu, vector); } - AxVCpuExitReason::Halt => { + VmExit::Halt => { debug!("VM[{vm_id}] run VCpu[{vcpu_id}] Halt"); - #[cfg(target_arch = "x86_64")] - super::x86_irq::inject_due_pit_irq0(&vm, &vcpu); - #[cfg(target_arch = "x86_64")] - super::x86_irq::inject_pending_serial_irq(&vm, &vcpu); - #[cfg(target_arch = "x86_64")] - continue; - #[cfg(not(target_arch = "x86_64"))] - wait(&runtime) + if CurrentArch::handle_halt(&runtime) { + continue; + } } - AxVCpuExitReason::Idle => { + VmExit::Idle => { trace!("VM[{vm_id}] run VCpu[{vcpu_id}] Idle"); - #[cfg(target_arch = "loongarch64")] - { - crate::check_timer_events(); - if vcpu.get_arch_vcpu().has_enabled_pending_interrupt() { - trace!( - "VM[{vm_id}] VCpu[{vcpu_id}] skips idle wait because guest has \ - enabled pending interrupt" - ); - continue; - } - let idle_timeout = vcpu.get_arch_vcpu().idle_wait_timeout(); - trace!("VM[{vm_id}] VCpu[{vcpu_id}] host idle wait for {idle_timeout:?}"); - ax_std::os::arceos::modules::ax_hal::asm::set_timer_irq_enabled(true); - ax_std::os::arceos::modules::ax_hal::asm::enable_irqs(); - ax_std::os::arceos::modules::ax_hal::time::busy_wait(idle_timeout); - ax_std::os::arceos::modules::ax_hal::asm::disable_irqs(); - ax_std::os::arceos::modules::ax_hal::asm::set_timer_irq_enabled(false); - } - #[cfg(not(target_arch = "loongarch64"))] - { - crate::check_timer_events(); - } + CurrentArch::handle_idle(&vm, &vcpu); } - AxVCpuExitReason::Nothing => {} - AxVCpuExitReason::CpuDown { _state } => { + VmExit::Nothing => {} + VmExit::CpuDown { _state } => { warn!("VM[{vm_id}] run VCpu[{vcpu_id}] CpuDown state {_state:#x}"); wait(&runtime) } - AxVCpuExitReason::CpuUp { + VmExit::CpuUp { target_cpu, entry_point, arg, @@ -519,10 +376,7 @@ fn vcpu_run() { match vcpu_on(vm.clone(), target_vcpu_id, entry_point, arg as _) { Ok(()) => { - #[cfg(not(target_arch = "riscv64"))] - vcpu.set_gpr(0, 0); - #[cfg(target_arch = "riscv64")] - vcpu.set_gpr(RiscvGprIndex::A0 as usize, 0); + CurrentArch::set_cpu_up_success(&vcpu); } Err(err) => { warn!("Failed to boot VM[{vm_id}] VCpu[{target_vcpu_id}]: {err:?}"); @@ -530,7 +384,7 @@ fn vcpu_run() { } } } - AxVCpuExitReason::SystemDown => { + VmExit::SystemDown => { warn!("VM[{vm_id}] run VCpu[{vcpu_id}] SystemDown"); if let Err(err) = vm.stop(StopReason::SystemDown) { warn!("VM[{vm_id}] shutdown failed: {err:?}"); @@ -538,7 +392,7 @@ fn vcpu_run() { // Notify all vCPUs to wake up to check the shutdown flag notify_all_vcpus(vm_id); } - AxVCpuExitReason::SendIPI { + VmExit::SendIPI { target_cpu, target_cpu_aux, send_to_all, @@ -549,7 +403,7 @@ fn vcpu_run() { "VM[{vm_id}] run VCpu[{vcpu_id}] SendIPI, target_cpu={target_cpu:#x}, \ target_cpu_aux={target_cpu_aux:#x}, vector={vector}", ); - let targets = ipi_targets( + let targets = CurrentArch::ipi_targets( &vm, vcpu_id, target_cpu, @@ -620,8 +474,7 @@ fn vcpu_run() { } info!("VM[{}] state changed to Stopped", vm_id); - #[cfg(target_arch = "x86_64")] - super::x86_irq::disable_ioapic_irq_forwarding_for_vm(vm_id); + CurrentArch::on_last_vcpu_exit(vm_id); sub_running_vm_count(1); crate::host::task::wait_queue_wake(&super::VMM, 1); diff --git a/virtualization/axvm/src/task.rs b/virtualization/axvm/src/task.rs index ef73fd1464..ba4b8c92bb 100644 --- a/virtualization/axvm/src/task.rs +++ b/virtualization/axvm/src/task.rs @@ -5,9 +5,8 @@ extern crate alloc; use alloc::sync::{Arc, Weak}; use crate::{ - AxVCpuRef, host::task::{TaskExt, TaskInner}, - vm::AxVMRef, + vm::{AxVCpuRef, AxVMRef}, }; /// Task extended data for a vCPU host task. diff --git a/virtualization/axvm/src/vcpu.rs b/virtualization/axvm/src/vcpu.rs index f291c3b407..93654fe4db 100644 --- a/virtualization/axvm/src/vcpu.rs +++ b/virtualization/axvm/src/vcpu.rs @@ -12,55 +12,19 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! AxVM-owned vCPU wrapper and architecture selection. +//! AxVM-owned architecture-independent vCPU wrapper. use alloc::format; use core::{cell::UnsafeCell, mem::MaybeUninit}; use ax_errno::{AxResult, ax_err}; use ax_kspin::SpinNoIrq as Mutex; +#[cfg(target_arch = "x86_64")] +use axvm_types::InterruptTriggerMode; use axvm_types::{ - GuestPhysAddr, HostPhysAddr, InterruptTriggerMode, VCpuId, VMId, VmArchPerCpuOps, - VmArchVcpuOps, VmExit, VmVcpuState, + GuestPhysAddr, HostPhysAddr, VCpuId, VMId, VmArchPerCpuOps, VmArchVcpuOps, VmExit, VmVcpuState, }; -cfg_if::cfg_if! { - if #[cfg(target_arch = "x86_64")] { - pub use x86_vcpu::X86ArchVCpu as AxArchVCpuImpl; - pub use x86_vcpu::X86ArchPerCpuState as AxVMArchPerCpuImpl; - pub use x86_vcpu::X86VCpuSetupConfig as AxVCpuSetupConfig; - #[allow(dead_code)] - pub type AxVCpuCreateConfig = (); - - /// x86_64 EPT always uses 4-level page tables. - pub fn max_guest_page_table_levels() -> usize { 4 } - } else if #[cfg(target_arch = "riscv64")] { - pub use riscv_vcpu::RISCVPerCpu as AxVMArchPerCpuImpl; - pub use riscv_vcpu::RISCVVCpu as AxArchVCpuImpl; - pub use riscv_vcpu::RISCVVCpuCreateConfig as AxVCpuCreateConfig; - - /// RISC-V uses Sv39 or Sv48 for guest page tables. Default to Sv48. - pub fn max_guest_page_table_levels() -> usize { 4 } - } else if #[cfg(target_arch = "loongarch64")] { - pub use loongarch_vcpu::LoongArchPerCpu as AxVMArchPerCpuImpl; - pub use loongarch_vcpu::LoongArchVCpu as AxArchVCpuImpl; - pub use loongarch_vcpu::LoongArchVCpuCreateConfig as AxVCpuCreateConfig; - pub use loongarch_vcpu::LoongArchVCpuSetupConfig as AxVCpuSetupConfig; - - /// LoongArch guests currently use 4-level page tables. - pub fn max_guest_page_table_levels() -> usize { 4 } - } else if #[cfg(target_arch = "aarch64")] { - pub use arm_vcpu::Aarch64PerCpu as AxVMArchPerCpuImpl; - pub use arm_vcpu::Aarch64VCpu as AxArchVCpuImpl; - pub use arm_vcpu::Aarch64VCpuCreateConfig as AxVCpuCreateConfig; - pub use arm_vcpu::Aarch64VCpuSetupConfig as AxVCpuSetupConfig; - pub use arm_vcpu::max_guest_page_table_levels; - } -} - -/// Backward-compatible AxVM vCPU state name. -pub type VCpuState = VmVcpuState; - /// Mutable runtime state of a virtual CPU. pub struct AxVCpuInnerMut { state: VmVcpuState, @@ -69,7 +33,6 @@ pub struct AxVCpuInnerMut { struct AxVCpuInnerConst { vm_id: VMId, vcpu_id: VCpuId, - favor_phys_cpu: usize, phys_cpu_set: Option, } @@ -85,7 +48,6 @@ impl AxVCpu { pub fn new( vm_id: VMId, vcpu_id: VCpuId, - favor_phys_cpu: usize, phys_cpu_set: Option, arch_config: A::CreateConfig, ) -> AxResult { @@ -93,7 +55,6 @@ impl AxVCpu { inner_const: AxVCpuInnerConst { vm_id, vcpu_id, - favor_phys_cpu, phys_cpu_set, }, inner_mut: Mutex::new(AxVCpuInnerMut { @@ -107,12 +68,12 @@ impl AxVCpu { pub fn setup( &self, entry: GuestPhysAddr, - ept_root: HostPhysAddr, + nested_page_table_root: HostPhysAddr, arch_config: A::SetupConfig, ) -> AxResult { self.manipulate_arch_vcpu(VmVcpuState::Created, VmVcpuState::Free, |arch_vcpu| { arch_vcpu.set_entry(entry)?; - arch_vcpu.set_ept_root(ept_root)?; + arch_vcpu.set_nested_page_table_root(nested_page_table_root)?; arch_vcpu.setup(arch_config)?; Ok(()) }) @@ -128,36 +89,16 @@ impl AxVCpu { self.inner_const.vm_id } - /// Returns the preferred physical CPU. - pub const fn favor_phys_cpu(&self) -> usize { - self.inner_const.favor_phys_cpu - } - /// Returns the allowed physical CPU mask. pub const fn phys_cpu_set(&self) -> Option { self.inner_const.phys_cpu_set } - /// Returns whether this vCPU is the bootstrap processor. - pub const fn is_bsp(&self) -> bool { - self.inner_const.vcpu_id == 0 - } - /// Returns the current vCPU state. pub fn state(&self) -> VmVcpuState { self.inner_mut.lock().state } - /// Sets the vCPU state without checking transitions. - /// - /// # Safety - /// - /// This may break the vCPU state machine and must only be used to recover - /// from externally synchronized lifecycle transitions. - pub unsafe fn set_state(&self, state: VmVcpuState) { - self.inner_mut.lock().state = state; - } - /// Runs `f` if the current state equals `from`, then stores `to`. pub fn with_state_transition( &self, @@ -275,6 +216,7 @@ impl AxVCpu { } /// Injects an interrupt with trigger-mode metadata. + #[cfg(target_arch = "x86_64")] pub fn inject_interrupt_with_trigger( &self, vector: usize, @@ -284,11 +226,6 @@ impl AxVCpu { .inject_interrupt_with_trigger(vector, trigger) } - /// Handles guest EOI and returns an external EOI vector when needed. - pub fn handle_eoi(&self) -> Option { - self.get_arch_vcpu().handle_eoi() - } - /// Sets the guest return value. pub fn set_return_value(&self, val: usize) { self.get_arch_vcpu().set_return_value(val); diff --git a/virtualization/axvm/src/vm.rs b/virtualization/axvm/src/vm.rs index b7dd473844..30b475ef08 100644 --- a/virtualization/axvm/src/vm.rs +++ b/virtualization/axvm/src/vm.rs @@ -25,16 +25,17 @@ use ax_memory_addr::align_up_4k; use axaddrspace::{AddrSpace, MappingFlags}; use axdevice::{AxVmDevices, FwCfg, FwCfgPlatformConfig}; use axdevice_base::AccessWidth; -use axvm_types::{AxVCpuExitReason, GuestPhysAddr, HostPhysAddr, HostVirtAddr, VmArchVcpuOps}; +use axvm_types::{GuestPhysAddr, HostPhysAddr, HostVirtAddr, VmArchVcpuOps, VmExit, VmVcpuState}; use crate::{ + arch::{ArchOps, CurrentArch}, boot::{GuestBootDescription, GuestFdtBuilder}, config::{AxVMConfig, PhysCpuList, VMInterruptMode}, host::paging::{HostPagingHandler, virt_to_phys}, irq::InterruptFabric, layout::VmAddressLayout, lifecycle::{Machine, StopReason, VmLifecycleError, VmStatus}, - vcpu::{AxArchVCpuImpl, AxVCpu, VCpuState}, + vcpu::AxVCpu, }; pub(crate) mod boot; @@ -46,12 +47,23 @@ const VM_ASPACE_BASE: usize = 0x0; const VM_ASPACE_SIZE: usize = 0x7fff_ffff_f000; /// A vCPU with architecture-independent interface. -type VCpu = AxVCpu; +type VCpu = AxVCpu; /// A reference to a vCPU. -pub type AxVCpuRef = Arc; +pub(crate) type AxVCpuRef = Arc; /// A reference to a VM. pub type AxVMRef = Arc; +/// Architecture-independent vCPU runtime metadata. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct VcpuSnapshot { + /// vCPU identifier inside its VM. + pub id: usize, + /// Current AxVM wrapper state. + pub state: VmVcpuState, + /// Optional physical CPU affinity mask. + pub phys_cpu_set: Option, +} + fn width_mask(width: AccessWidth) -> usize { match width { AccessWidth::Byte => 0xff, @@ -111,14 +123,11 @@ pub(crate) struct AxVMResources { unsafe impl Send for AxVMResources {} unsafe impl Sync for AxVMResources {} +#[allow(dead_code)] #[derive(Clone, Copy, Debug)] pub(crate) enum PendingInterrupt { Normal(usize), - #[cfg(target_arch = "loongarch64")] - LoongArchExternal { - vector: usize, - physical_irq: usize, - }, + External { vector: usize, physical_irq: usize }, } /// Runtime-only resources owned by Running/Paused/Stopping lifecycle states. @@ -176,7 +185,7 @@ impl VmRuntimeHandle { .lock() .entry(vcpu_id) .or_default() - .push(PendingInterrupt::LoongArchExternal { + .push(PendingInterrupt::External { vector, physical_irq, }); @@ -249,7 +258,7 @@ impl AxVMResources { fn new(config: AxVMConfig) -> AxResult { Ok(Self { address_space: AddrSpace::new_empty( - crate::vcpu::max_guest_page_table_levels(), + CurrentArch::max_guest_page_table_levels(), GuestPhysAddr::from(VM_ASPACE_BASE), VM_ASPACE_SIZE, )?, @@ -465,7 +474,7 @@ impl AxVM { /// Retrieves the vCPU corresponding to the given vcpu_id for the VM. /// Returns None if the vCPU does not exist. #[inline] - pub fn vcpu(&self, vcpu_id: usize) -> Option { + pub(crate) fn vcpu(&self, vcpu_id: usize) -> Option { self.vcpu_list().get(vcpu_id).cloned() } @@ -478,13 +487,25 @@ impl AxVM { /// Returns a snapshot of the VM's vCPU references. #[inline] - pub fn vcpu_list(&self) -> Vec { + pub(crate) fn vcpu_list(&self) -> Vec { self.with_resources(|resources| Ok(resources.vcpu_list()?.to_vec())) .unwrap_or_default() } - /// Returns the base address of the two-stage address translation page table for the VM. - pub fn ept_root(&self) -> AxResult { + /// Returns architecture-independent vCPU metadata snapshots. + pub fn vcpu_snapshots(&self) -> Vec { + self.vcpu_list() + .iter() + .map(|vcpu| VcpuSnapshot { + id: vcpu.id(), + state: vcpu.state(), + phys_cpu_set: vcpu.phys_cpu_set(), + }) + .collect() + } + + /// Returns the root address of the nested page table for the VM. + pub fn nested_page_table_root(&self) -> AxResult { self.with_resources(|resources| Ok(resources.address_space.page_table_root())) } @@ -806,16 +827,16 @@ impl AxVM { /// * `vcpu_id` - the id of the vCPU to run. /// /// ## Returns - /// * `AxVCpuExitReason` - the exit reason of the vCPU, wrapped in an `AxResult`. - pub fn run_vcpu(&self, vcpu_id: usize) -> AxResult { + /// * `VmExit` - the exit reason of the vCPU, wrapped in an `AxResult`. + pub fn run_vcpu(&self, vcpu_id: usize) -> AxResult { let vm_id = self.id(); let vcpu = self .vcpu(vcpu_id) .ok_or_else(|| ax_err_type!(InvalidInput, "Invalid vcpu_id"))?; match vcpu.state() { - VCpuState::Free => vcpu.bind()?, - VCpuState::Ready => {} + VmVcpuState::Free => vcpu.bind()?, + VmVcpuState::Ready => {} state => { return ax_err!( BadState, @@ -825,14 +846,14 @@ impl AxVM { } let devices = self.get_devices()?; - let run_result = vcpu.with_current_cpu_set(|| -> AxResult { + let run_result = vcpu.with_current_cpu_set(|| -> AxResult { loop { crate::runtime::vcpus::inject_pending_interrupts(self.id(), vcpu_id, &vcpu); let exit_reason = vcpu.run()?; trace!("{exit_reason:#x?}"); match exit_reason { - AxVCpuExitReason::MmioRead { + VmExit::MmioRead { addr, width, reg, @@ -848,21 +869,17 @@ impl AxVM { }; vcpu.set_gpr(reg, val); } - AxVCpuExitReason::MmioWrite { addr, width, data } => { + VmExit::MmioWrite { addr, width, data } => { self.handle_mmio_write(addr, width, data as usize)?; } - AxVCpuExitReason::IoRead { port, width } => { + VmExit::IoRead { port, width } => { let val = devices.handle_port_read(port, width)?; - #[cfg(not(target_arch = "riscv64"))] - vcpu.set_gpr(0, val); // The target is always eax/ax/al, todo: handle access_width correctly - - #[cfg(target_arch = "riscv64")] - vcpu.set_gpr(riscv_vcpu::GprIndex::A0 as usize, val); + CurrentArch::set_io_read_result(&vcpu, val); } - AxVCpuExitReason::IoWrite { port, width, data } => { + VmExit::IoWrite { port, width, data } => { devices.handle_port_write(port, width, data as usize)?; } - AxVCpuExitReason::SysRegRead { addr, reg } => { + VmExit::SysRegRead { addr, reg } => { let val = devices.handle_sys_reg_read( addr, // Generally speaking, the width of system register is fixed and needless to be specified. @@ -871,16 +888,16 @@ impl AxVM { )?; vcpu.set_gpr(reg, val); } - AxVCpuExitReason::SysRegWrite { addr, value } => { + VmExit::SysRegWrite { addr, value } => { devices.handle_sys_reg_write(addr, AccessWidth::Qword, value as usize)?; } - AxVCpuExitReason::NestedPageFault { addr, access_flags } => { + VmExit::NestedPageFault { addr, access_flags } => { if devices.find_mmio_dev(addr).is_some() { if let Some(mmio_exit) = vcpu.get_arch_vcpu().decode_mmio_fault(addr, access_flags) { match mmio_exit { - AxVCpuExitReason::MmioRead { + VmExit::MmioRead { addr, width, reg, @@ -896,16 +913,16 @@ impl AxVM { }; vcpu.set_gpr(reg, val); } - AxVCpuExitReason::MmioWrite { addr, width, data } => { + VmExit::MmioWrite { addr, width, data } => { self.handle_mmio_write(addr, width, data as usize)?; } exit_reason => break Ok(exit_reason), } } else { - break Ok(AxVCpuExitReason::NestedPageFault { addr, access_flags }); + break Ok(VmExit::NestedPageFault { addr, access_flags }); } } else if !self.handle_nested_page_fault(addr, access_flags) { - break Ok(AxVCpuExitReason::NestedPageFault { addr, access_flags }); + break Ok(VmExit::NestedPageFault { addr, access_flags }); } } exit_reason => break Ok(exit_reason), @@ -944,13 +961,12 @@ impl AxVM { } devices.handle_mmio_write(addr, width, data)?; - #[cfg(target_arch = "loongarch64")] - self.drain_loongarch_pch_pic_events(); + CurrentArch::after_mmio_write(self); Ok(()) } #[cfg(target_arch = "loongarch64")] - fn drain_loongarch_pch_pic_events(&self) { + pub(crate) fn drain_loongarch_pch_pic_events(&self) { let Ok(devices) = self.get_devices() else { return; }; diff --git a/virtualization/axvm/src/vm/prepare/devices.rs b/virtualization/axvm/src/vm/prepare/devices.rs index e41b577621..45b23df146 100644 --- a/virtualization/axvm/src/vm/prepare/devices.rs +++ b/virtualization/axvm/src/vm/prepare/devices.rs @@ -1,8 +1,11 @@ //! Device construction for VM preparation. +#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] use alloc::{format, sync::Arc}; -use ax_errno::{AxResult, ax_err_type}; +use ax_errno::AxResult; +#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] +use ax_errno::ax_err_type; use axdevice::{AxVmDeviceConfig, AxVmDevices, DeviceBuildContext, DeviceFactoryRegistry}; #[cfg(target_arch = "aarch64")] use axdevice_base::DeviceRegistry as _; diff --git a/virtualization/axvm/src/vm/prepare/vcpus.rs b/virtualization/axvm/src/vm/prepare/vcpus.rs index d06b210f2e..ccd40a411c 100644 --- a/virtualization/axvm/src/vm/prepare/vcpus.rs +++ b/virtualization/axvm/src/vm/prepare/vcpus.rs @@ -3,27 +3,13 @@ use alloc::{boxed::Box, sync::Arc, vec::Vec}; use ax_errno::AxResult; -#[cfg(target_arch = "x86_64")] -use axvm_types::EmulatedDeviceType; -use axvm_types::GuestPhysAddr; -#[cfg(not(any( - target_arch = "aarch64", - target_arch = "loongarch64", - target_arch = "x86_64" -)))] -use axvm_types::VmArchVcpuOps; +use axvm_types::{EmulatedDeviceType, GuestPhysAddr}; use super::super::{AxVCpuRef, AxVMResources, VCpu}; -#[cfg(any(target_arch = "aarch64", target_arch = "loongarch64"))] -use crate::config::VMInterruptMode; -#[cfg(not(any( - target_arch = "aarch64", - target_arch = "loongarch64", - target_arch = "x86_64" -)))] -use crate::vcpu::AxArchVCpuImpl; -#[cfg(not(target_arch = "x86_64"))] -use crate::vcpu::AxVCpuCreateConfig; +use crate::{ + arch::{ArchOps, CurrentArch, VcpuCreateContext, VcpuSetupContext}, + config::{GuestBootPolicy, VMBootProtocol}, +}; pub(super) struct PreparedVcpus { vcpus: Vec, @@ -36,40 +22,23 @@ impl PreparedVcpus { dtb_addr: Option, ) -> AxResult { let vcpu_id_pcpu_sets = resources.config.phys_cpu_ls.get_vcpu_affinities_pcpu_ids(); - #[cfg(target_arch = "loongarch64")] - let loongarch_iocsr_state = { - let vcpu_state_count = vcpu_id_pcpu_sets - .iter() - .map(|(vcpu_id, ..)| *vcpu_id) - .max() - .map_or(0, |vcpu_id| vcpu_id + 1); - loongarch_vcpu::LoongArchIocsrState::new(vcpu_state_count)? - }; + let create_state = CurrentArch::new_vcpu_create_state(&vcpu_id_pcpu_sets)?; + let firmware_boot = guest_uses_firmware_boot(resources); debug!("dtb_load_gpa: {dtb_addr:?}"); debug!("id: {vm_id}, VCpuIdPCpuSets: {vcpu_id_pcpu_sets:#x?}"); let mut vcpus = Vec::with_capacity(vcpu_id_pcpu_sets.len()); - for (vcpu_id, phys_cpu_set, _pcpu_id) in vcpu_id_pcpu_sets { - #[cfg(target_arch = "aarch64")] - let arch_config = AxVCpuCreateConfig { - mpidr_el1: _pcpu_id as _, - dtb_addr: dtb_addr.unwrap_or_default().as_usize(), - }; - #[cfg(target_arch = "riscv64")] - let arch_config = AxVCpuCreateConfig { - hart_id: vcpu_id as _, - dtb_addr: dtb_addr.unwrap_or_default().as_usize(), - }; - #[cfg(target_arch = "loongarch64")] - let arch_config = AxVCpuCreateConfig { - cpu_id: vcpu_id, - dtb_addr: dtb_addr.unwrap_or_default().as_usize(), - boot_args: resources.config.cpu_config.boot_args, - boot_stack_top: resources.config.cpu_config.boot_stack_top, - firmware_boot: resources.config.cpu_config.firmware_boot, - iocsr_state: loongarch_iocsr_state.clone(), - }; + for (vcpu_id, phys_cpu_set, phys_cpu_id) in vcpu_id_pcpu_sets { + let arch_config = CurrentArch::build_vcpu_create_config( + &create_state, + VcpuCreateContext { + vcpu_id, + phys_cpu_id, + dtb_addr, + firmware_boot, + }, + )?; // FIXME: VCpu is neither `Send` nor `Sync` by design, check whether // 1. we should make it `Send` and `Sync`, or @@ -78,16 +47,8 @@ impl PreparedVcpus { vcpus.push(Arc::new(VCpu::new( vm_id, vcpu_id, - 0, // Currently not used. phys_cpu_set, - #[cfg(target_arch = "aarch64")] - arch_config, - #[cfg(target_arch = "loongarch64")] - arch_config, - #[cfg(target_arch = "riscv64")] arch_config, - #[cfg(target_arch = "x86_64")] - (), )?)); } @@ -96,47 +57,16 @@ impl PreparedVcpus { pub(super) fn setup(&self, resources: &AxVMResources) -> AxResult { for vcpu in &self.vcpus { - #[cfg(target_arch = "aarch64")] - let setup_config = { - let passthrough = resources.config.interrupt_mode() == VMInterruptMode::Passthrough; - crate::vcpu::AxVCpuSetupConfig { - passthrough_interrupt: passthrough, - passthrough_timer: passthrough, - } - }; - #[cfg(target_arch = "loongarch64")] - let setup_config = { - let passthrough = resources.config.interrupt_mode() == VMInterruptMode::Passthrough; - crate::vcpu::AxVCpuSetupConfig { - passthrough_interrupt: passthrough, - passthrough_timer: passthrough, - boot_args: resources.config.cpu_config.boot_args, - boot_stack_top: resources.config.cpu_config.boot_stack_top, - firmware_boot: resources.config.cpu_config.firmware_boot, - } - }; - #[cfg(not(any( - target_arch = "aarch64", - target_arch = "loongarch64", - target_arch = "x86_64" - )))] - #[allow(clippy::let_unit_value)] - let setup_config = ::SetupConfig::default(); - #[cfg(target_arch = "x86_64")] - let setup_config = { - let mut config = crate::vcpu::AxVCpuSetupConfig { - emulate_com1: resources - .config - .emu_devices() - .iter() - .any(|dev| dev.emu_type == EmulatedDeviceType::Console), - ..Default::default() - }; - for port in resources.config.pass_through_ports() { - config.add_passthrough_port_range(port.base, port.length)?; - } - config - }; + let setup_config = CurrentArch::build_vcpu_setup_config(VcpuSetupContext { + interrupt_mode: resources.config.interrupt_mode(), + emulates_console: resources + .config + .emu_devices() + .iter() + .any(|dev| dev.emu_type == EmulatedDeviceType::Console), + passthrough_ports: resources.config.pass_through_ports(), + firmware_boot: guest_uses_firmware_boot(resources), + })?; let entry = if vcpu.id() == 0 { resources.config.bsp_entry() @@ -159,3 +89,12 @@ impl PreparedVcpus { self.vcpus.into_boxed_slice() } } + +fn guest_uses_firmware_boot(resources: &AxVMResources) -> bool { + matches!( + resources.config.boot_policy(), + GuestBootPolicy::AdjustKernelForBootProtocol { + protocol: VMBootProtocol::Uefi, + } + ) +} diff --git a/virtualization/axvmconfig/src/lib.rs b/virtualization/axvmconfig/src/lib.rs index 459ed175cc..1033762ba0 100644 --- a/virtualization/axvmconfig/src/lib.rs +++ b/virtualization/axvmconfig/src/lib.rs @@ -570,6 +570,53 @@ mod vm_interrupt_mode_serde { } } +struct BootProtocolSupport { + protocol: VMBootProtocol, + supported_arches: &'static [&'static str], + requires_firmware_path: bool, + requires_firmware_load_addr: bool, + optional_firmware_requires_load_addr: bool, +} + +const BOOT_PROTOCOL_MATRIX: &[BootProtocolSupport] = &[ + BootProtocolSupport { + protocol: VMBootProtocol::Direct, + supported_arches: &["x86_64", "aarch64", "riscv64", "loongarch64"], + requires_firmware_path: false, + requires_firmware_load_addr: false, + optional_firmware_requires_load_addr: false, + }, + BootProtocolSupport { + protocol: VMBootProtocol::Multiboot, + supported_arches: &["x86_64"], + requires_firmware_path: false, + requires_firmware_load_addr: false, + optional_firmware_requires_load_addr: true, + }, + BootProtocolSupport { + protocol: VMBootProtocol::Uefi, + supported_arches: &["x86_64", "loongarch64"], + requires_firmware_path: true, + requires_firmware_load_addr: true, + optional_firmware_requires_load_addr: false, + }, +]; + +fn boot_protocol_support(protocol: VMBootProtocol) -> &'static BootProtocolSupport { + BOOT_PROTOCOL_MATRIX + .iter() + .find(|support| support.protocol == protocol) + .expect("all VMBootProtocol variants must be described") +} + +fn boot_protocol_name(protocol: VMBootProtocol) -> &'static str { + match protocol { + VMBootProtocol::Direct => "direct", + VMBootProtocol::Multiboot => "multiboot", + VMBootProtocol::Uefi => "uefi", + } +} + /// The configuration structure for the guest VM base info. #[cfg_attr(all(feature = "std", any(windows, unix)), derive(schemars::JsonSchema))] #[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)] @@ -691,11 +738,9 @@ impl VMKernelConfig { } fn validate_boot_config_for_arch(&self, arch: &str) -> AxResult<()> { + let protocol = self.effective_boot_protocol(); if !self.enable_bios { - if matches!( - self.effective_boot_protocol(), - VMBootProtocol::Multiboot | VMBootProtocol::Uefi - ) { + if protocol != VMBootProtocol::Direct { return Err(ax_errno::ax_err_type!( InvalidInput, "boot_protocol requires enable_bios = true" @@ -704,57 +749,48 @@ impl VMKernelConfig { return Ok(()); } - match self.effective_boot_protocol() { - VMBootProtocol::Uefi => { - if !matches!(arch, "x86_64" | "loongarch64") { - warn!( - "boot_protocol=uefi is only supported on x86_64 and loongarch64; \ - rejecting config on {arch}" - ); - return Err(ax_errno::ax_err_type!( - InvalidInput, - "UEFI boot is only supported on x86_64 and loongarch64" - )); - } - if self.boot_firmware_path().is_none() { - return Err(ax_errno::ax_err_type!( - InvalidInput, - "UEFI boot requires uefi_firmware_path or legacy bios_path" - )); - } - if self.bios_load_addr.is_none() { - return Err(ax_errno::ax_err_type!( - InvalidInput, - "UEFI boot requires bios_load_addr" - )); - } - } - VMBootProtocol::Multiboot => { - if arch != "x86_64" { - warn!( - "boot_protocol=multiboot is only supported on x86_64; rejecting config on \ - {arch}" - ); - return Err(ax_errno::ax_err_type!( - InvalidInput, - "multiboot firmware boot is only supported on x86_64" - )); - } - if self.bios_path.is_some() && self.bios_load_addr.is_none() { - return Err(ax_errno::ax_err_type!( - InvalidInput, - "external BIOS boot requires bios_load_addr" - )); - } - } - VMBootProtocol::Direct => { - if self.enable_bios { - return Err(ax_errno::ax_err_type!( - InvalidInput, - "direct boot must not set enable_bios = true" - )); - } - } + if protocol == VMBootProtocol::Direct { + return Err(ax_errno::ax_err_type!( + InvalidInput, + "direct boot must not set enable_bios = true" + )); + } + + let support = boot_protocol_support(protocol); + if !support.supported_arches.contains(&arch) { + warn!( + "boot_protocol={} is only supported on {}; rejecting config on {arch}", + boot_protocol_name(protocol), + support.supported_arches.join(", ") + ); + return Err(ax_errno::ax_err_type!( + InvalidInput, + "boot protocol is not supported on this architecture" + )); + } + + if support.requires_firmware_path && self.boot_firmware_path().is_none() { + return Err(ax_errno::ax_err_type!( + InvalidInput, + "firmware boot requires uefi_firmware_path or legacy bios_path" + )); + } + + if support.requires_firmware_load_addr && self.bios_load_addr.is_none() { + return Err(ax_errno::ax_err_type!( + InvalidInput, + "firmware boot requires bios_load_addr" + )); + } + + if support.optional_firmware_requires_load_addr + && self.bios_path.is_some() + && self.bios_load_addr.is_none() + { + return Err(ax_errno::ax_err_type!( + InvalidInput, + "external firmware boot requires bios_load_addr" + )); } Ok(()) diff --git a/virtualization/loongarch_vcpu/src/exception.rs b/virtualization/loongarch_vcpu/src/exception.rs index 379eb65db4..b1d2e582ae 100644 --- a/virtualization/loongarch_vcpu/src/exception.rs +++ b/virtualization/loongarch_vcpu/src/exception.rs @@ -6,7 +6,7 @@ use core::{ }; use ax_errno::AxResult; -use axvm_types::{AxVCpuExitReason, GuestPhysAddr, MappingFlags, VCpuId, VMId}; +use axvm_types::{GuestPhysAddr, MappingFlags, VCpuId, VMId, VmExit}; use crate::{context_frame::LoongArchContextFrame, host}; @@ -655,25 +655,25 @@ fn write_guest_iocsr( addr: usize, len: usize, value: usize, -) -> Option { +) -> Option { let vcpu = state.vcpu(vcpu_id)?; match addr { - LOONGARCH_IOCSR_IPI_STATUS => Some(AxVCpuExitReason::Nothing), + LOONGARCH_IOCSR_IPI_STATUS => Some(VmExit::Nothing), LOONGARCH_IOCSR_IPI_EN => { vcpu.ipi_enable.store(value, Ordering::Release); - Some(AxVCpuExitReason::Nothing) + Some(VmExit::Nothing) } LOONGARCH_IOCSR_IPI_SET => { vcpu.ipi_status.fetch_or(value, Ordering::AcqRel); ctx.gcsr_estat |= IPI_BIT; - Some(AxVCpuExitReason::Nothing) + Some(VmExit::Nothing) } LOONGARCH_IOCSR_IPI_CLEAR => { let new_status = vcpu.ipi_status.fetch_and(!value, Ordering::AcqRel) & !value; if new_status == 0 { ctx.gcsr_estat &= !IPI_BIT; } - Some(AxVCpuExitReason::Nothing) + Some(VmExit::Nothing) } LOONGARCH_IOCSR_IPI_SEND => { let target_cpu = (value >> IOCSR_SEND_CPU_SHIFT) & IOCSR_SEND_CPU_MASK; @@ -695,13 +695,13 @@ fn write_guest_iocsr( action ); } - Some(AxVCpuExitReason::Nothing) + Some(VmExit::Nothing) } LOONGARCH_IOCSR_MAIL_BUF0..=LOONGARCH_IOCSR_MAIL_BUF3 => { if let Some(mail_index) = iocsr_mail_buf_index(addr) { vcpu.mail_buf[mail_index].store(value, Ordering::Release); } - Some(AxVCpuExitReason::Nothing) + Some(VmExit::Nothing) } LOONGARCH_IOCSR_MBUF_SEND => { let target_cpu = (value >> IOCSR_SEND_CPU_SHIFT) & IOCSR_SEND_CPU_MASK; @@ -730,7 +730,7 @@ fn write_guest_iocsr( if target_cpu == vcpu_id { ctx.gcsr_estat |= IPI_BIT; } - Some(AxVCpuExitReason::Nothing) + Some(VmExit::Nothing) } LOONGARCH_IOCSR_ANY_SEND => { let target_cpu = (value >> IOCSR_SEND_CPU_SHIFT) & IOCSR_SEND_CPU_MASK; @@ -746,12 +746,12 @@ fn write_guest_iocsr( LOONGARCH_IOCSR_ANY_SEND_BUF_SHIFT, ); } - Some(AxVCpuExitReason::Nothing) + Some(VmExit::Nothing) } EXTIOI_VIRT_CONFIG => { vcpu.eiointc_virt_config.store(value, Ordering::Release); update_guest_eiointc_irq(ctx, vcpu); - Some(AxVCpuExitReason::Nothing) + Some(VmExit::Nothing) } EIOINTC_NODEMAP_BASE..EIOINTC_NODEMAP_END => { write_atomic_u32_slots( @@ -761,26 +761,26 @@ fn write_guest_iocsr( len, value, ); - Some(AxVCpuExitReason::Nothing) + Some(VmExit::Nothing) } EIOINTC_IPMAP_BASE..EIOINTC_IPMAP_END => { write_atomic_u32_slots(&vcpu.eiointc_ipmap, addr, EIOINTC_IPMAP_BASE, len, value); update_guest_eiointc_irq(ctx, vcpu); - Some(AxVCpuExitReason::Nothing) + Some(VmExit::Nothing) } EIOINTC_ENABLE_BASE..EIOINTC_ENABLE_END => { write_atomic_u32_slots(&vcpu.eiointc_enable, addr, EIOINTC_ENABLE_BASE, len, value); update_guest_eiointc_irq(ctx, vcpu); - Some(AxVCpuExitReason::Nothing) + Some(VmExit::Nothing) } EIOINTC_BOUNCE_BASE..EIOINTC_BOUNCE_END => { write_atomic_u32_slots(&vcpu.eiointc_bounce, addr, EIOINTC_BOUNCE_BASE, len, value); - Some(AxVCpuExitReason::Nothing) + Some(VmExit::Nothing) } EIOINTC_ISR_COMPAT_BASE..EIOINTC_ISR_COMPAT_END => { clear_eiointc_isr_slots(vcpu, addr, EIOINTC_ISR_COMPAT_BASE, len, value); update_guest_eiointc_irq(ctx, vcpu); - Some(AxVCpuExitReason::Nothing) + Some(VmExit::Nothing) } EIOINTC_ISR_BASE..EIOINTC_ISR_END if is_eiointc_isr_addr(addr) => { clear_eiointc_isr_slots(vcpu, addr, EIOINTC_ISR_BASE, len, value); @@ -793,7 +793,7 @@ fn write_guest_iocsr( addr - EIOINTC_ISR_BASE, read_eiointc_isr_slots(vcpu, addr, EIOINTC_ISR_BASE, len), ); - Some(AxVCpuExitReason::Nothing) + Some(VmExit::Nothing) } EIOINTC_COREMAP_BASE..EIOINTC_COREMAP_END => { write_atomic_u32_slots( @@ -804,9 +804,9 @@ fn write_guest_iocsr( value, ); update_guest_eiointc_irq(ctx, vcpu); - Some(AxVCpuExitReason::Nothing) + Some(VmExit::Nothing) } - EIOINTC_GUEST_OWNED_BASE..EIOINTC_GUEST_OWNED_END => Some(AxVCpuExitReason::Nothing), + EIOINTC_GUEST_OWNED_BASE..EIOINTC_GUEST_OWNED_END => Some(VmExit::Nothing), _ => None, } } @@ -995,7 +995,7 @@ fn advance_guest_pc(ctx: &mut LoongArchContextFrame) { ctx.advance_guest_pc(); } -fn emulate_cpucfg(ctx: &mut LoongArchContextFrame, ins: usize) -> AxVCpuExitReason { +fn emulate_cpucfg(ctx: &mut LoongArchContextFrame, ins: usize) -> VmExit { let rd = extract_field(ins, 0, 5); let rj = extract_field(ins, 5, 5); let cpucfg_idx = ctx.x[rj]; @@ -1013,7 +1013,7 @@ fn emulate_cpucfg(ctx: &mut LoongArchContextFrame, ins: usize) -> AxVCpuExitReas } ctx.set_gpr(rd, value); advance_guest_pc(ctx); - AxVCpuExitReason::Nothing + VmExit::Nothing } fn emulate_csrx( @@ -1022,14 +1022,14 @@ fn emulate_csrx( vm_id: VMId, vcpu_id: VCpuId, guest_timer_token: &mut Option, -) -> AxVCpuExitReason { +) -> VmExit { let rd = extract_field(ins, 0, 5); let rj = extract_field(ins, 5, 5); let csr = extract_field(ins, 10, 14); emulate_guest_csr(ctx, rd, rj, csr, vm_id, vcpu_id, guest_timer_token); advance_guest_pc(ctx); - AxVCpuExitReason::Nothing + VmExit::Nothing } /// Timer CSR numbers (matching GCSR encoding in LoongArch LVZ). @@ -1307,16 +1307,16 @@ fn emulate_guest_csr( ctx.set_gpr(rd, return_value); } -fn emulate_cacop(ctx: &mut LoongArchContextFrame, _ins: usize) -> AxVCpuExitReason { +fn emulate_cacop(ctx: &mut LoongArchContextFrame, _ins: usize) -> VmExit { log::trace!( "LoongArch GSPR cacop emulation skipped at guest_pc={:#x}", get_guest_pc(ctx) ); advance_guest_pc(ctx); - AxVCpuExitReason::Nothing + VmExit::Nothing } -fn emulate_idle(ctx: &mut LoongArchContextFrame, ins: usize) -> AxVCpuExitReason { +fn emulate_idle(ctx: &mut LoongArchContextFrame, ins: usize) -> VmExit { let level = extract_field(ins, 0, 15); let pending_enabled = ctx.gcsr_estat & ctx.gcsr_ectl & LOCAL_INTERRUPT_MASK; let idle_log_index = IDLE_EXIT_LOGS.fetch_add(1, Ordering::Relaxed); @@ -1350,10 +1350,10 @@ fn emulate_idle(ctx: &mut LoongArchContextFrame, ins: usize) -> AxVCpuExitReason ); } inject_guest_interrupt_at(ctx, vector, get_guest_pc(ctx).wrapping_add(4)); - return AxVCpuExitReason::Nothing; + return VmExit::Nothing; } advance_guest_pc(ctx); - AxVCpuExitReason::Idle + VmExit::Idle } fn emulate_iocsr( @@ -1362,7 +1362,7 @@ fn emulate_iocsr( ins: usize, vm_id: VMId, vcpu_id: VCpuId, -) -> AxVCpuExitReason { +) -> VmExit { let ty = extract_field(ins, 10, 3); let rd = extract_field(ins, 0, 5); let rj = extract_field(ins, 5, 5); @@ -1543,7 +1543,7 @@ fn emulate_iocsr( } advance_guest_pc(ctx); - AxVCpuExitReason::Nothing + VmExit::Nothing } fn log_eiointc_trace( @@ -1571,7 +1571,7 @@ fn emulate_gspr( vm_id: VMId, vcpu_id: VCpuId, guest_timer_token: &mut Option, -) -> AxVCpuExitReason { +) -> VmExit { let ins = get_badi(ctx) as u32 as usize; const OPCODE_CPUCFG: usize = 0b0000000000000000011011; const OPCODE_CPUCFG_LEN: usize = 22; @@ -1640,7 +1640,7 @@ pub fn handle_exception_sync( vm_id: VMId, vcpu_id: VCpuId, guest_timer_token: &mut Option, -) -> AxResult { +) -> AxResult { let ecode = get_exception_code(ctx); let esubcode = get_exception_subcode(ctx); if SYNC_EXIT_LOGS.fetch_add(1, Ordering::Relaxed) < 32 { @@ -1680,7 +1680,7 @@ pub fn handle_exception_sync( let badv = get_badv(ctx); if should_inject_guest_virtual_fault(ctx, badv, true) { inject_guest_tlb_refill(ctx, badv); - return Ok(AxVCpuExitReason::Nothing); + return Ok(VmExit::Nothing); } ctx.sepc = get_guest_pc(ctx); if NESTED_FAULT_LOGS.fetch_add(1, Ordering::Relaxed) < 8 { @@ -1697,7 +1697,7 @@ pub fn handle_exception_sync( ctx.host_estat ); } - return Ok(AxVCpuExitReason::NestedPageFault { + return Ok(VmExit::NestedPageFault { addr: GuestPhysAddr::from(direct_map_guest_addr_to_gpa(badv)), access_flags: get_refill_access_flags(ctx), }); @@ -1719,7 +1719,7 @@ pub fn handle_exception_sync( ctx.get_a6() as u64, ]; advance_guest_pc(ctx); - Ok(AxVCpuExitReason::Hypercall { nr, args }) + Ok(VmExit::Hypercall { nr, args }) } ECODE_GSPR => Ok(emulate_gspr( iocsr_state, @@ -1732,7 +1732,7 @@ pub fn handle_exception_sync( let badv = get_badv(ctx); if should_inject_guest_virtual_fault(ctx, badv, false) { inject_guest_regular_exception(ctx, ecode, esubcode, badv); - return Ok(AxVCpuExitReason::Nothing); + return Ok(VmExit::Nothing); } let mut access_flags = MappingFlags::empty(); if matches!(ecode, ECODE_PIS | ECODE_PME) { @@ -1757,7 +1757,7 @@ pub fn handle_exception_sync( ctx.host_estat ); } - Ok(AxVCpuExitReason::NestedPageFault { + Ok(VmExit::NestedPageFault { addr: GuestPhysAddr::from(direct_map_guest_addr_to_gpa(badv)), access_flags, }) @@ -1780,7 +1780,7 @@ pub fn handle_exception_sync( get_badv(ctx), get_badi(ctx) ), - ECODE_RSE => Ok(AxVCpuExitReason::Halt), + ECODE_RSE => Ok(VmExit::Halt), _ => panic!( "Unhandled synchronous exception: ecode={:#x}, esubcode={:#x}, sepc={:#x}, \ gera={:#x}, badv={:#x}, badi={:#x}", @@ -1800,7 +1800,7 @@ pub fn handle_exception_sync( result } -pub fn handle_exception_irq(ctx: &mut LoongArchContextFrame) -> AxResult { +pub fn handle_exception_irq(ctx: &mut LoongArchContextFrame) -> AxResult { let guest_is = get_guest_interrupt_status(ctx); let is = guest_is; @@ -1820,7 +1820,7 @@ pub fn handle_exception_irq(ctx: &mut LoongArchContextFrame) -> AxResult AxResult Option { +) -> Option { let access_flags = refine_access_flags_from_insn(insn, access_flags); let fault_addr = fault_addr.as_usize(); let op = (insn >> MEMORY_ACCESS_OP_SHIFT) & MEMORY_ACCESS_OP_MASK; let rd = (insn >> MEMORY_ACCESS_RD_SHIFT) & INSN_REG_MASK; let exit_reason = match op { - LD_B_OP if access_flags.contains(MappingFlags::READ) => AxVCpuExitReason::MmioRead { + LD_B_OP if access_flags.contains(MappingFlags::READ) => VmExit::MmioRead { addr: GuestPhysAddr::from(fault_addr), width: AccessWidth::Byte, reg: rd, reg_width: AccessWidth::Qword, signed_ext: true, }, - LD_H_OP if access_flags.contains(MappingFlags::READ) => AxVCpuExitReason::MmioRead { + LD_H_OP if access_flags.contains(MappingFlags::READ) => VmExit::MmioRead { addr: GuestPhysAddr::from(fault_addr), width: AccessWidth::Word, reg: rd, reg_width: AccessWidth::Qword, signed_ext: true, }, - LD_W_OP if access_flags.contains(MappingFlags::READ) => AxVCpuExitReason::MmioRead { + LD_W_OP if access_flags.contains(MappingFlags::READ) => VmExit::MmioRead { addr: GuestPhysAddr::from(fault_addr), width: AccessWidth::Dword, reg: rd, reg_width: AccessWidth::Qword, signed_ext: true, }, - LD_D_OP if access_flags.contains(MappingFlags::READ) => AxVCpuExitReason::MmioRead { + LD_D_OP if access_flags.contains(MappingFlags::READ) => VmExit::MmioRead { addr: GuestPhysAddr::from(fault_addr), width: AccessWidth::Qword, reg: rd, reg_width: AccessWidth::Qword, signed_ext: false, }, - LD_BU_OP if access_flags.contains(MappingFlags::READ) => AxVCpuExitReason::MmioRead { + LD_BU_OP if access_flags.contains(MappingFlags::READ) => VmExit::MmioRead { addr: GuestPhysAddr::from(fault_addr), width: AccessWidth::Byte, reg: rd, reg_width: AccessWidth::Qword, signed_ext: false, }, - LD_HU_OP if access_flags.contains(MappingFlags::READ) => AxVCpuExitReason::MmioRead { + LD_HU_OP if access_flags.contains(MappingFlags::READ) => VmExit::MmioRead { addr: GuestPhysAddr::from(fault_addr), width: AccessWidth::Word, reg: rd, reg_width: AccessWidth::Qword, signed_ext: false, }, - LD_WU_OP if access_flags.contains(MappingFlags::READ) => AxVCpuExitReason::MmioRead { + LD_WU_OP if access_flags.contains(MappingFlags::READ) => VmExit::MmioRead { addr: GuestPhysAddr::from(fault_addr), width: AccessWidth::Dword, reg: rd, reg_width: AccessWidth::Qword, signed_ext: false, }, - ST_B_OP if access_flags.contains(MappingFlags::WRITE) => AxVCpuExitReason::MmioWrite { + ST_B_OP if access_flags.contains(MappingFlags::WRITE) => VmExit::MmioWrite { addr: GuestPhysAddr::from(fault_addr), width: AccessWidth::Byte, data: ctx.gpr(rd) as u64, }, - ST_H_OP if access_flags.contains(MappingFlags::WRITE) => AxVCpuExitReason::MmioWrite { + ST_H_OP if access_flags.contains(MappingFlags::WRITE) => VmExit::MmioWrite { addr: GuestPhysAddr::from(fault_addr), width: AccessWidth::Word, data: ctx.gpr(rd) as u64, }, - ST_W_OP if access_flags.contains(MappingFlags::WRITE) => AxVCpuExitReason::MmioWrite { + ST_W_OP if access_flags.contains(MappingFlags::WRITE) => VmExit::MmioWrite { addr: GuestPhysAddr::from(fault_addr), width: AccessWidth::Dword, data: ctx.gpr(rd) as u64, }, - ST_D_OP if access_flags.contains(MappingFlags::WRITE) => AxVCpuExitReason::MmioWrite { + ST_D_OP if access_flags.contains(MappingFlags::WRITE) => VmExit::MmioWrite { addr: GuestPhysAddr::from(fault_addr), width: AccessWidth::Qword, data: ctx.gpr(rd) as u64, @@ -187,38 +187,34 @@ fn decode_ptr_mmio_fault( insn: usize, fault_addr: usize, access_flags: MappingFlags, -) -> Option { +) -> Option { let op = (insn >> PTR_OP_SHIFT) & PTR_OP_MASK; let rd = (insn >> MEMORY_ACCESS_RD_SHIFT) & INSN_REG_MASK; let exit_reason = match op { - LDPTR_W_PREFIX if access_flags.contains(MappingFlags::READ) => AxVCpuExitReason::MmioRead { + LDPTR_W_PREFIX if access_flags.contains(MappingFlags::READ) => VmExit::MmioRead { addr: GuestPhysAddr::from(fault_addr), width: AccessWidth::Dword, reg: rd, reg_width: AccessWidth::Qword, signed_ext: true, }, - LDPTR_D_PREFIX if access_flags.contains(MappingFlags::READ) => AxVCpuExitReason::MmioRead { + LDPTR_D_PREFIX if access_flags.contains(MappingFlags::READ) => VmExit::MmioRead { addr: GuestPhysAddr::from(fault_addr), width: AccessWidth::Qword, reg: rd, reg_width: AccessWidth::Qword, signed_ext: false, }, - STPTR_W_PREFIX if access_flags.contains(MappingFlags::WRITE) => { - AxVCpuExitReason::MmioWrite { - addr: GuestPhysAddr::from(fault_addr), - width: AccessWidth::Dword, - data: ctx.gpr(rd) as u64, - } - } - STPTR_D_PREFIX if access_flags.contains(MappingFlags::WRITE) => { - AxVCpuExitReason::MmioWrite { - addr: GuestPhysAddr::from(fault_addr), - width: AccessWidth::Qword, - data: ctx.gpr(rd) as u64, - } - } + STPTR_W_PREFIX if access_flags.contains(MappingFlags::WRITE) => VmExit::MmioWrite { + addr: GuestPhysAddr::from(fault_addr), + width: AccessWidth::Dword, + data: ctx.gpr(rd) as u64, + }, + STPTR_D_PREFIX if access_flags.contains(MappingFlags::WRITE) => VmExit::MmioWrite { + addr: GuestPhysAddr::from(fault_addr), + width: AccessWidth::Qword, + data: ctx.gpr(rd) as u64, + }, _ => return decode_indexed_mmio_fault(ctx, insn, fault_addr, access_flags), }; @@ -231,76 +227,76 @@ fn decode_indexed_mmio_fault( insn: usize, fault_addr: usize, access_flags: MappingFlags, -) -> Option { +) -> Option { let op = (insn >> INDEXED_ACCESS_OP_SHIFT) & INDEXED_ACCESS_OP_MASK; let rd = (insn >> MEMORY_ACCESS_RD_SHIFT) & INSN_REG_MASK; let exit_reason = match op { - LDX_B_OP if access_flags.contains(MappingFlags::READ) => AxVCpuExitReason::MmioRead { + LDX_B_OP if access_flags.contains(MappingFlags::READ) => VmExit::MmioRead { addr: GuestPhysAddr::from(fault_addr), width: AccessWidth::Byte, reg: rd, reg_width: AccessWidth::Qword, signed_ext: true, }, - LDX_H_OP if access_flags.contains(MappingFlags::READ) => AxVCpuExitReason::MmioRead { + LDX_H_OP if access_flags.contains(MappingFlags::READ) => VmExit::MmioRead { addr: GuestPhysAddr::from(fault_addr), width: AccessWidth::Word, reg: rd, reg_width: AccessWidth::Qword, signed_ext: true, }, - LDX_W_OP if access_flags.contains(MappingFlags::READ) => AxVCpuExitReason::MmioRead { + LDX_W_OP if access_flags.contains(MappingFlags::READ) => VmExit::MmioRead { addr: GuestPhysAddr::from(fault_addr), width: AccessWidth::Dword, reg: rd, reg_width: AccessWidth::Qword, signed_ext: true, }, - LDX_D_OP if access_flags.contains(MappingFlags::READ) => AxVCpuExitReason::MmioRead { + LDX_D_OP if access_flags.contains(MappingFlags::READ) => VmExit::MmioRead { addr: GuestPhysAddr::from(fault_addr), width: AccessWidth::Qword, reg: rd, reg_width: AccessWidth::Qword, signed_ext: false, }, - LDX_BU_OP if access_flags.contains(MappingFlags::READ) => AxVCpuExitReason::MmioRead { + LDX_BU_OP if access_flags.contains(MappingFlags::READ) => VmExit::MmioRead { addr: GuestPhysAddr::from(fault_addr), width: AccessWidth::Byte, reg: rd, reg_width: AccessWidth::Qword, signed_ext: false, }, - LDX_HU_OP if access_flags.contains(MappingFlags::READ) => AxVCpuExitReason::MmioRead { + LDX_HU_OP if access_flags.contains(MappingFlags::READ) => VmExit::MmioRead { addr: GuestPhysAddr::from(fault_addr), width: AccessWidth::Word, reg: rd, reg_width: AccessWidth::Qword, signed_ext: false, }, - LDX_WU_OP if access_flags.contains(MappingFlags::READ) => AxVCpuExitReason::MmioRead { + LDX_WU_OP if access_flags.contains(MappingFlags::READ) => VmExit::MmioRead { addr: GuestPhysAddr::from(fault_addr), width: AccessWidth::Dword, reg: rd, reg_width: AccessWidth::Qword, signed_ext: false, }, - STX_B_OP if access_flags.contains(MappingFlags::WRITE) => AxVCpuExitReason::MmioWrite { + STX_B_OP if access_flags.contains(MappingFlags::WRITE) => VmExit::MmioWrite { addr: GuestPhysAddr::from(fault_addr), width: AccessWidth::Byte, data: ctx.gpr(rd) as u64, }, - STX_H_OP if access_flags.contains(MappingFlags::WRITE) => AxVCpuExitReason::MmioWrite { + STX_H_OP if access_flags.contains(MappingFlags::WRITE) => VmExit::MmioWrite { addr: GuestPhysAddr::from(fault_addr), width: AccessWidth::Word, data: ctx.gpr(rd) as u64, }, - STX_W_OP if access_flags.contains(MappingFlags::WRITE) => AxVCpuExitReason::MmioWrite { + STX_W_OP if access_flags.contains(MappingFlags::WRITE) => VmExit::MmioWrite { addr: GuestPhysAddr::from(fault_addr), width: AccessWidth::Dword, data: ctx.gpr(rd) as u64, }, - STX_D_OP if access_flags.contains(MappingFlags::WRITE) => AxVCpuExitReason::MmioWrite { + STX_D_OP if access_flags.contains(MappingFlags::WRITE) => VmExit::MmioWrite { addr: GuestPhysAddr::from(fault_addr), width: AccessWidth::Qword, data: ctx.gpr(rd) as u64, diff --git a/virtualization/loongarch_vcpu/src/vcpu.rs b/virtualization/loongarch_vcpu/src/vcpu.rs index c1bc5e41d6..c2b1d8ea8d 100644 --- a/virtualization/loongarch_vcpu/src/vcpu.rs +++ b/virtualization/loongarch_vcpu/src/vcpu.rs @@ -3,9 +3,7 @@ use ax_errno::AxResult; use ax_errno::ax_err; #[cfg(target_arch = "loongarch64")] use ax_memory_addr::VirtAddr; -use axvm_types::{ - AxVCpuExitReason, GuestPhysAddr, HostPhysAddr, MappingFlags, VCpuId, VMId, VmArchVcpuOps, -}; +use axvm_types::{GuestPhysAddr, HostPhysAddr, MappingFlags, VCpuId, VMId, VmArchVcpuOps, VmExit}; #[cfg(target_arch = "loongarch64")] use loongArch64::register::prmd; @@ -179,8 +177,8 @@ impl VmArchVcpuOps for LoongArchVCpu { Ok(()) } - fn set_ept_root(&mut self, ept_root: HostPhysAddr) -> AxResult { - self.stage2_root = ept_root; + fn set_nested_page_table_root(&mut self, nested_page_table_root: HostPhysAddr) -> AxResult { + self.stage2_root = nested_page_table_root; Ok(()) } @@ -197,7 +195,7 @@ impl VmArchVcpuOps for LoongArchVCpu { Ok(()) } - fn run(&mut self) -> AxResult { + fn run(&mut self) -> AxResult { #[cfg(target_arch = "loongarch64")] { unsafe { @@ -271,7 +269,7 @@ impl VmArchVcpuOps for LoongArchVCpu { &mut self, fault_addr: GuestPhysAddr, access_flags: MappingFlags, - ) -> Option { + ) -> Option { let gcsr_badi = self.ctx.gcsr_badi; let exit = crate::mmio::decode_mmio_fault(&mut self.ctx, self.last_badi, fault_addr, access_flags) @@ -569,7 +567,7 @@ impl LoongArchVCpu { } #[cfg(target_arch = "loongarch64")] - fn vmexit_handler(&mut self, exit_reason: TrapKind) -> AxResult { + fn vmexit_handler(&mut self, exit_reason: TrapKind) -> AxResult { self.last_badi = if self.ctx.host_badi != 0 { self.ctx.host_badi } else { diff --git a/virtualization/riscv_vcpu/src/vcpu.rs b/virtualization/riscv_vcpu/src/vcpu.rs index 9b5ccfef26..f02c9eef51 100644 --- a/virtualization/riscv_vcpu/src/vcpu.rs +++ b/virtualization/riscv_vcpu/src/vcpu.rs @@ -13,9 +13,7 @@ // limitations under the License. use ax_errno::{AxError, AxErrorKind, AxResult}; -use axvm_types::{ - AccessWidth, AxVCpuExitReason, GuestPhysAddr, GuestVirtAddr, HostPhysAddr, MappingFlags, -}; +use axvm_types::{AccessWidth, GuestPhysAddr, GuestVirtAddr, HostPhysAddr, MappingFlags, VmExit}; use riscv::register::{scause, sie, sstatus}; use riscv_decode::{ Instruction, @@ -79,13 +77,13 @@ struct RISCVVCpuSbi { /// Result of reading an instruction for virtual-instruction emulation. enum VirtualInstructionRead { Instruction(u32), - Handled(AxVCpuExitReason), + Handled(VmExit), } /// Result of decoding the trapped guest load/store instruction. enum InstructionDecode { Decoded(Instruction, usize), - Handled(AxVCpuExitReason), + Handled(VmExit), } impl Default for RISCVVCpuSbi { @@ -164,14 +162,14 @@ impl axvm_types::VmArchVcpuOps for RISCVVCpu { Ok(()) } - fn set_ept_root(&mut self, ept_root: HostPhysAddr) -> AxResult { + fn set_nested_page_table_root(&mut self, nested_page_table_root: HostPhysAddr) -> AxResult { // AxVM builds a 4-level guest stage-2 page table on RISC-V, so hgatp // must use Sv48x4 as well. - self.regs.virtual_hs_csrs.hgatp = 9usize << 60 | usize::from(ept_root) >> 12; + self.regs.virtual_hs_csrs.hgatp = 9usize << 60 | usize::from(nested_page_table_root) >> 12; Ok(()) } - fn run(&mut self) -> AxResult { + fn run(&mut self) -> AxResult { unsafe { sstatus::clear_sie(); sie::set_sext(); @@ -381,26 +379,26 @@ impl RISCVVCpu { fn handle_guest_instruction_fetch_fault( &mut self, fault: guest_mem::GuestInstructionFetchFault, - ) -> AxResult { + ) -> AxResult { match fault { // HLVX reports load-class faults, but the emulated operation is a // guest instruction fetch. Convert them before injecting to VS mode. guest_mem::GuestInstructionFetchFault::PageFault { addr } => { self.inject_guest_exception(Exception::InstructionPageFault, addr); - Ok(AxVCpuExitReason::Nothing) + Ok(VmExit::Nothing) } guest_mem::GuestInstructionFetchFault::AccessFault { addr } => { self.inject_guest_exception(Exception::InstructionFault, addr); - Ok(AxVCpuExitReason::Nothing) + Ok(VmExit::Nothing) } guest_mem::GuestInstructionFetchFault::Misaligned { addr } => { self.inject_guest_exception(Exception::InstructionMisaligned, addr); - Ok(AxVCpuExitReason::Nothing) + Ok(VmExit::Nothing) } guest_mem::GuestInstructionFetchFault::GuestPageFault { addr } => { // G-stage faults must stay visible to AxVM so it can populate or // reject the nested mapping. - Ok(AxVCpuExitReason::NestedPageFault { + Ok(VmExit::NestedPageFault { addr, access_flags: MappingFlags::EXECUTE, }) @@ -419,7 +417,7 @@ impl RISCVVCpu { } } - fn vmexit_handler(&mut self) -> AxResult { + fn vmexit_handler(&mut self) -> AxResult { self.regs.trap_csrs.load_from_hw(); let scause = scause::read(); @@ -471,7 +469,7 @@ impl RISCVVCpu { } legacy::LEGACY_SHUTDOWN => { // sbi_call_legacy_0(LEGACY_SHUTDOWN) - return Ok(AxVCpuExitReason::SystemDown); + return Ok(VmExit::SystemDown); } _ => { warn!( @@ -485,11 +483,11 @@ impl RISCVVCpu { self.sbi.pmu.record_set_timer(); self.program_guest_timer(param[0]); self.sbi_return(RET_SUCCESS, 0); - return Ok(AxVCpuExitReason::Nothing); + return Ok(VmExit::Nothing); } _ => { self.sbi_return(RET_ERR_NOT_SUPPORTED, 0); - return Ok(AxVCpuExitReason::Nothing); + return Ok(VmExit::Nothing); } }, // Handle HSM extension @@ -499,28 +497,28 @@ impl RISCVVCpu { let start_addr = a[1]; let opaque = a[2]; self.advance_pc(4); - return Ok(AxVCpuExitReason::CpuUp { + return Ok(VmExit::CpuUp { target_cpu: hartid as _, entry_point: GuestPhysAddr::from(start_addr), arg: opaque as _, }); } hsm::HART_STOP => { - return Ok(AxVCpuExitReason::CpuDown { _state: 0 }); + return Ok(VmExit::CpuDown { _state: 0 }); } hsm::HART_SUSPEND => { // Todo: support these parameters. let _suspend_type = a[0]; let _resume_addr = a[1]; let _opaque = a[2]; - return Ok(AxVCpuExitReason::Halt); + return Ok(VmExit::Halt); } _ => todo!(), }, // Handle hypercall EID_HVC => { self.advance_pc(4); - return Ok(AxVCpuExitReason::Hypercall { + return Ok(VmExit::Hypercall { nr: function_id as _, args: [ param[0] as _, @@ -541,7 +539,7 @@ impl RISCVVCpu { if num_bytes == 0 { self.sbi_return(RET_SUCCESS, 0); - return Ok(AxVCpuExitReason::Nothing); + return Ok(VmExit::Nothing); } let mut buf = alloc::vec![0u8; num_bytes]; @@ -557,7 +555,7 @@ impl RISCVVCpu { self.sbi_return(RET_ERR_FAILED, 0); } - return Ok(AxVCpuExitReason::Nothing); + return Ok(VmExit::Nothing); } // Read to memory region from debug console. FID_CONSOLE_READ => { @@ -566,7 +564,7 @@ impl RISCVVCpu { if num_bytes == 0 { self.sbi_return(RET_SUCCESS, 0); - return Ok(AxVCpuExitReason::Nothing); + return Ok(VmExit::Nothing); } let mut buf = alloc::vec![0u8; num_bytes]; @@ -586,19 +584,19 @@ impl RISCVVCpu { self.sbi_return(ret.error, ret.value); } - return Ok(AxVCpuExitReason::Nothing); + return Ok(VmExit::Nothing); } // Write a single byte to debug console. FID_CONSOLE_WRITE_BYTE => { let byte = (param[0] & 0xff) as u8; print_byte(byte); self.sbi_return(RET_SUCCESS, 0); - return Ok(AxVCpuExitReason::Nothing); + return Ok(VmExit::Nothing); } // Unknown FID. _ => { self.sbi_return(RET_ERR_NOT_SUPPORTED, 0); - return Ok(AxVCpuExitReason::Nothing); + return Ok(VmExit::Nothing); } }, srst::EID_SRST => match function_id { @@ -606,14 +604,14 @@ impl RISCVVCpu { let reset_type = param[0]; if reset_type == srst::RESET_TYPE_SHUTDOWN as _ { // Shutdown the system. - return Ok(AxVCpuExitReason::SystemDown); + return Ok(VmExit::SystemDown); } else { unimplemented!("Unsupported reset type {}", reset_type); } } _ => { self.sbi_return(RET_ERR_NOT_SUPPORTED, 0); - return Ok(AxVCpuExitReason::Nothing); + return Ok(VmExit::Nothing); } }, pmu::EID_PMU => { @@ -659,7 +657,7 @@ impl RISCVVCpu { }; self.advance_pc(4); - Ok(AxVCpuExitReason::Nothing) + Ok(VmExit::Nothing) } Trap::Exception(Exception::VirtualInstruction) => self.handle_virtual_instruction(), Trap::Interrupt(Interrupt::SupervisorTimer) => { @@ -670,7 +668,7 @@ impl RISCVVCpu { sie::clear_stimer(); } - Ok(AxVCpuExitReason::Nothing) + Ok(VmExit::Nothing) } Trap::Interrupt(Interrupt::SupervisorExternal) => { // 9 == Interrupt::SupervisorExternal @@ -678,7 +676,7 @@ impl RISCVVCpu { // It's a great fault in the `riscv` crate that `Interrupt` and `Exception` are not // explicitly numbered, and they provide no way to convert them to a number. Also, // `as usize` will give use a wrong value. - Ok(AxVCpuExitReason::ExternalInterrupt { vector: S_EXT as _ }) + Ok(VmExit::ExternalInterrupt { vector: S_EXT as _ }) } Trap::Exception( gpf @ (Exception::LoadGuestPageFault | Exception::StoreGuestPageFault), @@ -714,7 +712,7 @@ impl RISCVVCpu { } #[cfg(feature = "sstc")] - fn handle_virtual_instruction(&mut self) -> AxResult { + fn handle_virtual_instruction(&mut self) -> AxResult { let instr = match self.read_virtual_instruction()? { VirtualInstructionRead::Instruction(instr) => instr, VirtualInstructionRead::Handled(exit_reason) => return Ok(exit_reason), @@ -800,11 +798,11 @@ impl RISCVVCpu { } self.advance_pc(4); - Ok(AxVCpuExitReason::Nothing) + Ok(VmExit::Nothing) } #[cfg(not(feature = "sstc"))] - fn handle_virtual_instruction(&mut self) -> AxResult { + fn handle_virtual_instruction(&mut self) -> AxResult { self.sbi.pmu.record_illegal_insn(); Err(ax_errno::ax_err_type!( Unsupported, @@ -902,7 +900,7 @@ impl RISCVVCpu { } /// Handle a guest page fault. Return an exit reason. - fn handle_guest_page_fault(&mut self, _writing: bool) -> AxResult { + fn handle_guest_page_fault(&mut self, _writing: bool) -> AxResult { let fault_addr = self.regs.trap_csrs.gpt_page_fault_addr(); let sepc = self.regs.guest_regs.sepc; let sepc_vaddr = GuestVirtAddr::from(sepc); @@ -980,7 +978,7 @@ impl RISCVVCpu { }, _ => { // Not a load or store instruction, so we cannot handle it here, return a nested page fault. - return Ok(AxVCpuExitReason::NestedPageFault { + return Ok(VmExit::NestedPageFault { addr: fault_addr, access_flags: MappingFlags::empty(), }); @@ -997,7 +995,7 @@ impl RISCVVCpu { signed_ext, } => { self.sbi.pmu.record_access_load(); - AxVCpuExitReason::MmioRead { + VmExit::MmioRead { addr: fault_addr, width, reg: i.rd() as _, @@ -1013,7 +1011,7 @@ impl RISCVVCpu { GprIndex::from_raw(source_reg).unwrap_unchecked() }); - AxVCpuExitReason::MmioWrite { + VmExit::MmioWrite { addr: fault_addr, width, data: value as _, diff --git a/virtualization/x86_vcpu/src/lib.rs b/virtualization/x86_vcpu/src/lib.rs index 0ccc9e2c1f..8ead5edcb0 100644 --- a/virtualization/x86_vcpu/src/lib.rs +++ b/virtualization/x86_vcpu/src/lib.rs @@ -36,6 +36,10 @@ mod test_utils; /// Maximum number of x86 host I/O port ranges configured for one vCPU. pub const X86_MAX_PASSTHROUGH_PORT_RANGES: usize = 16; +/// x86 vCPU creation configuration. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub struct X86VCpuCreateConfig; + /// x86 host I/O port range that should trap and be handled by the VMM. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct X86PassthroughPortRange { diff --git a/virtualization/x86_vcpu/src/no_backend.rs b/virtualization/x86_vcpu/src/no_backend.rs index d422d7e5bb..f81aeb5de7 100644 --- a/virtualization/x86_vcpu/src/no_backend.rs +++ b/virtualization/x86_vcpu/src/no_backend.rs @@ -9,10 +9,10 @@ use ax_errno::{AxResult, ax_err}; use axvm_types::{ - AxVCpuExitReason, GuestPhysAddr, HostPhysAddr, VCpuId, VMId, VmArchPerCpuOps, VmArchVcpuOps, + GuestPhysAddr, HostPhysAddr, VCpuId, VMId, VmArchPerCpuOps, VmArchVcpuOps, VmExit, }; -use crate::X86VCpuSetupConfig; +use crate::{X86VCpuCreateConfig, X86VCpuSetupConfig}; /// Stub per-CPU state; never instantiated in no-backend builds. pub struct X86ArchPerCpuState; @@ -39,10 +39,10 @@ impl VmArchPerCpuOps for X86ArchPerCpuState { pub struct X86ArchVCpu; impl VmArchVcpuOps for X86ArchVCpu { - type CreateConfig = (); + type CreateConfig = X86VCpuCreateConfig; type SetupConfig = X86VCpuSetupConfig; - fn new(_vm_id: VMId, _vcpu_id: VCpuId, _config: ()) -> AxResult { + fn new(_vm_id: VMId, _vcpu_id: VCpuId, _config: X86VCpuCreateConfig) -> AxResult { ax_err!(Unsupported, "no hypervisor backend (vmx/svm) enabled") } @@ -50,7 +50,7 @@ impl VmArchVcpuOps for X86ArchVCpu { ax_err!(Unsupported, "no hypervisor backend (vmx/svm) enabled") } - fn set_ept_root(&mut self, _ept_root: HostPhysAddr) -> AxResult { + fn set_nested_page_table_root(&mut self, _nested_page_table_root: HostPhysAddr) -> AxResult { ax_err!(Unsupported, "no hypervisor backend (vmx/svm) enabled") } @@ -58,7 +58,7 @@ impl VmArchVcpuOps for X86ArchVCpu { ax_err!(Unsupported, "no hypervisor backend (vmx/svm) enabled") } - fn run(&mut self) -> AxResult { + fn run(&mut self) -> AxResult { ax_err!(Unsupported, "no hypervisor backend (vmx/svm) enabled") } diff --git a/virtualization/x86_vcpu/src/svm/vcpu.rs b/virtualization/x86_vcpu/src/svm/vcpu.rs index 9c12ce70ff..9149312f7d 100644 --- a/virtualization/x86_vcpu/src/svm/vcpu.rs +++ b/virtualization/x86_vcpu/src/svm/vcpu.rs @@ -9,8 +9,8 @@ use ax_errno::{AxResult, ax_err, ax_err_type}; use ax_memory_addr::AddrRange; use axdevice_base::{BaseDeviceOps, SysRegAddrRange}; use axvm_types::{ - AccessWidth, AxVCpuExitReason, GuestPhysAddr, GuestVirtAddr, HostPhysAddr, MappingFlags, - NestedPageFaultInfo, Port, SysRegAddr, VCpuId, VMId, VmArchVcpuOps, + AccessWidth, GuestPhysAddr, GuestVirtAddr, HostPhysAddr, MappingFlags, NestedPageFaultInfo, + Port, SysRegAddr, VCpuId, VMId, VmArchVcpuOps, VmExit, }; use bit_field::BitField; use tock_registers::interfaces::{ReadWriteable, Readable, Writeable}; @@ -28,8 +28,8 @@ use super::{ vmcb::{InterceptCrRw, InterceptExceptions, NestedCtl, VmcbTlbControl, set_vmcb_segment}, }; use crate::{ - X86VCpuSetupConfig, msr::Msr, regs::GeneralRegisters, restore_host_interrupt_flag, - x86_real_mode_entry_state, xstate::XState, + X86VCpuCreateConfig, X86VCpuSetupConfig, msr::Msr, regs::GeneralRegisters, + restore_host_interrupt_flag, x86_real_mode_entry_state, xstate::XState, }; const QEMU_EXIT_PORT: u16 = 0x604; @@ -648,14 +648,14 @@ impl SvmVcpu { &mut self, exit_info: &super::vmcb::SvmExitInfo, msr: u32, - ) -> AxResult { + ) -> AxResult { const VM_EXIT_INSTR_LEN_MSR: u8 = 2; let write = exit_info.exit_info_1 != 0; if write { if msr == X2APIC_EOI_MSR { self.advance_rip(VM_EXIT_INSTR_LEN_MSR)?; - return Ok(AxVCpuExitReason::InterruptEnd { + return Ok(VmExit::InterruptEnd { vector: self.handle_local_apic_eoi(), }); } else { @@ -677,7 +677,7 @@ impl SvmVcpu { } self.advance_rip(VM_EXIT_INSTR_LEN_MSR)?; - Ok(AxVCpuExitReason::Nothing) + Ok(VmExit::Nothing) } fn handle_ignored_msr_access(&mut self, exit_info: &super::vmcb::SvmExitInfo) -> AxResult { @@ -1041,7 +1041,7 @@ impl SvmVcpu { exit_info: &super::vmcb::SvmExitInfo, addr: GuestPhysAddr, write: bool, - ) -> AxResult> { + ) -> AxResult> { let addr_usize = addr.as_usize(); let local_apic = (X86_LOCAL_APIC_BASE..X86_LOCAL_APIC_BASE + X86_LOCAL_APIC_SIZE).contains(&addr_usize); @@ -1099,9 +1099,9 @@ impl SvmVcpu { )?; self.regs_mut() .set_reg_of_index(reg as u8, val as u32 as u64); - AxVCpuExitReason::Nothing + VmExit::Nothing } else { - AxVCpuExitReason::MmioRead { + VmExit::MmioRead { addr, width: AccessWidth::Dword, reg, @@ -1123,9 +1123,9 @@ impl SvmVcpu { addr: GuestPhysAddr, data: u64, local_apic: bool, - ) -> AxResult { + ) -> AxResult { if !local_apic { - return Ok(AxVCpuExitReason::MmioWrite { + return Ok(VmExit::MmioWrite { addr, width: AccessWidth::Dword, data, @@ -1134,7 +1134,7 @@ impl SvmVcpu { let offset = addr.as_usize() - X86_LOCAL_APIC_BASE; if offset == X86_LOCAL_APIC_EOI_OFFSET { - return Ok(AxVCpuExitReason::InterruptEnd { + return Ok(VmExit::InterruptEnd { vector: self.handle_local_apic_eoi(), }); } @@ -1145,7 +1145,7 @@ impl SvmVcpu { AccessWidth::Dword, data as usize, )?; - Ok(AxVCpuExitReason::Nothing) + Ok(VmExit::Nothing) } fn skip_simple_prefixes(&self, rip: &mut GuestVirtAddr, rex: &mut u8) -> AxResult { @@ -1400,11 +1400,11 @@ fn pending_event_interrupt_type(event: PendingEvent) -> u32 { } } -fn svm_intr_exit_reason(_vector: Option) -> AxVCpuExitReason { +fn svm_intr_exit_reason(_vector: Option) -> VmExit { // SVM_EXIT_INTR is a host IRQ exit point. Unlike VMX external-interrupt // exits, VMCB exit_int_info is not a reliable dispatch key for the host // IRQ framework, so the caller must let the host consume the pending IRQ. - AxVCpuExitReason::PreemptionTimer + VmExit::PreemptionTimer } fn service_pending_host_interrupt() { @@ -1478,7 +1478,7 @@ impl Debug for SvmVcpu { } impl VmArchVcpuOps for SvmVcpu { - type CreateConfig = (); + type CreateConfig = X86VCpuCreateConfig; type SetupConfig = X86VCpuSetupConfig; fn new(vm_id: VMId, vcpu_id: VCpuId, _config: Self::CreateConfig) -> AxResult { @@ -1490,8 +1490,8 @@ impl VmArchVcpuOps for SvmVcpu { Ok(()) } - fn set_ept_root(&mut self, ept_root: HostPhysAddr) -> AxResult { - self.npt_root = Some(ept_root); + fn set_nested_page_table_root(&mut self, nested_page_table_root: HostPhysAddr) -> AxResult { + self.npt_root = Some(nested_page_table_root); Ok(()) } @@ -1505,19 +1505,19 @@ impl VmArchVcpuOps for SvmVcpu { self.setup_vmcb(entry, npt_root, config) } - fn run(&mut self) -> AxResult { + fn run(&mut self) -> AxResult { { let exit_info = self.inner_run()?; let exit_code = match exit_info.exit_code { Ok(code) => code, Err(code) => { warn!("SVM unknown VM-exit code: {code:#x}, exit_info: {exit_info:#x?}"); - return Ok(AxVCpuExitReason::Halt); + return Ok(VmExit::Halt); } }; Ok(match exit_code { - SvmExitCode::INVALID | SvmExitCode::BUSY => AxVCpuExitReason::FailEntry { + SvmExitCode::INVALID | SvmExitCode::BUSY => VmExit::FailEntry { hardware_entry_failure_reason: match exit_code { SvmExitCode::INVALID => u64::MAX, SvmExitCode::BUSY => u64::MAX - 1, @@ -1526,7 +1526,7 @@ impl VmArchVcpuOps for SvmVcpu { }, SvmExitCode::VMMCALL => { self.advance_rip(3)?; - AxVCpuExitReason::Hypercall { + VmExit::Hypercall { nr: self.regs().rax, args: [ self.regs().rdi, @@ -1540,7 +1540,7 @@ impl VmArchVcpuOps for SvmVcpu { } SvmExitCode::RDTSC => { self.handle_rdtsc()?; - AxVCpuExitReason::PreemptionTimer + VmExit::PreemptionTimer } SvmExitCode::IOIO => { let (is_in, is_string, is_repeat, width, port) = @@ -1551,16 +1551,16 @@ impl VmArchVcpuOps for SvmVcpu { if is_string || is_repeat { warn!("SVM unsupported IOIO exit: {exit_info:#x?}"); warn!("VCpu {self:#x?}"); - AxVCpuExitReason::Halt + VmExit::Halt } else if is_in { - AxVCpuExitReason::IoRead { port, width } + VmExit::IoRead { port, width } } else if port == Port(QEMU_EXIT_PORT) && width == AccessWidth::Word && self.regs().rax == QEMU_EXIT_MAGIC { - AxVCpuExitReason::SystemDown + VmExit::SystemDown } else { - AxVCpuExitReason::IoWrite { + VmExit::IoWrite { port, width, data: self.regs().rax.get_bits(width.bits_range()), @@ -1574,12 +1574,12 @@ impl VmArchVcpuOps for SvmVcpu { } else { self.advance_rip(2)?; if exit_info.exit_info_1 == 0 { - AxVCpuExitReason::SysRegRead { + VmExit::SysRegRead { addr: SysRegAddr::new(self.regs().rcx as _), reg: 0, } } else { - AxVCpuExitReason::SysRegWrite { + VmExit::SysRegWrite { addr: SysRegAddr::new(self.regs().rcx as _), value: self.read_edx_eax(), } @@ -1597,7 +1597,7 @@ impl VmArchVcpuOps for SvmVcpu { self.advance_rip(instr_len)?; mmio_exit } else { - AxVCpuExitReason::NestedPageFault { + VmExit::NestedPageFault { addr: info.fault_guest_paddr, access_flags: info.access_flags, } @@ -1613,17 +1613,17 @@ impl VmArchVcpuOps for SvmVcpu { } SvmExitCode::HLT => { self.advance_rip(1)?; - AxVCpuExitReason::PreemptionTimer + VmExit::PreemptionTimer } SvmExitCode::PAUSE => { self.advance_rip(2)?; - AxVCpuExitReason::PreemptionTimer + VmExit::PreemptionTimer } - SvmExitCode::SHUTDOWN => AxVCpuExitReason::SystemDown, + SvmExitCode::SHUTDOWN => VmExit::SystemDown, _ => { warn!("SVM unsupported VM-exit: {exit_info:#x?}"); warn!("VCpu {self:#x?}"); - AxVCpuExitReason::Halt + VmExit::Halt } }) } @@ -1680,7 +1680,7 @@ impl VmArchVcpuOps for SvmVcpu { mod tests { use core::mem::MaybeUninit; - use axvm_types::AxVCpuExitReason; + use axvm_types::VmExit; use tock_registers::interfaces::{Readable, Writeable}; use x86_64::registers::rflags::RFlags; @@ -1792,7 +1792,7 @@ mod tests { let vector = 0x51; let exit = svm_intr_exit_reason(Some(vector)); - assert!(matches!(exit, AxVCpuExitReason::PreemptionTimer)); + assert!(matches!(exit, VmExit::PreemptionTimer)); } #[test] diff --git a/virtualization/x86_vcpu/src/vmx/vcpu.rs b/virtualization/x86_vcpu/src/vmx/vcpu.rs index 46798bccdd..86a9e281de 100644 --- a/virtualization/x86_vcpu/src/vmx/vcpu.rs +++ b/virtualization/x86_vcpu/src/vmx/vcpu.rs @@ -23,8 +23,8 @@ use ax_errno::{AxResult, ax_err, ax_err_type}; use ax_memory_addr::AddrRange; use axdevice_base::{BaseDeviceOps, SysRegAddrRange}; use axvm_types::{ - AccessWidth, AxVCpuExitReason, GuestPhysAddr, GuestVirtAddr, HostPhysAddr, MappingFlags, - NestedPageFaultInfo, Port, SysRegAddr, VCpuId, VMId, VmArchVcpuOps, + AccessWidth, GuestPhysAddr, GuestVirtAddr, HostPhysAddr, MappingFlags, NestedPageFaultInfo, + Port, SysRegAddr, VCpuId, VMId, VmArchVcpuOps, VmExit, }; use bit_field::BitField; use raw_cpuid::CpuId; @@ -47,8 +47,8 @@ use super::{ }, }; use crate::{ - X86VCpuSetupConfig, ept::GuestPageWalkInfo, host, msr::Msr, regs::GeneralRegisters, - restore_host_interrupt_flag, x86_real_mode_entry_state, xstate::XState, + X86VCpuCreateConfig, X86VCpuSetupConfig, ept::GuestPageWalkInfo, host, msr::Msr, + regs::GeneralRegisters, restore_host_interrupt_flag, x86_real_mode_entry_state, xstate::XState, }; const VMX_PREEMPTION_TIMER_SET_VALUE: u32 = 100_000; @@ -113,7 +113,7 @@ pub struct VmxVcpu { /// The guest entry point. entry: Option, /// The EPT root address. - ept_root: Option, + nested_page_table_root: Option, // /// Whether this VCPU is a host VCpu. Used in type 1.5 hypervisor. // is_host: bool, temporary removed because we don't care about type 1.5 now @@ -153,7 +153,7 @@ impl VmxVcpu { host_rflags: 0, launched: false, entry: None, - ept_root: None, + nested_page_table_root: None, // is_host: false, vmcs: VmxRegion::new(vmcs_revision_id, false)?, io_bitmap: IOBitmap::passthrough_all()?, @@ -170,8 +170,12 @@ impl VmxVcpu { } /// Set the new [`VmxVcpu`] context from guest OS. - pub fn setup(&mut self, ept_root: HostPhysAddr, entry: GuestPhysAddr) -> AxResult { - self.setup_vmcs(entry, ept_root, X86VCpuSetupConfig::default())?; + pub fn setup( + &mut self, + nested_page_table_root: HostPhysAddr, + entry: GuestPhysAddr, + ) -> AxResult { + self.setup_vmcs(entry, nested_page_table_root, X86VCpuSetupConfig::default())?; Ok(()) } @@ -517,7 +521,7 @@ impl VmxVcpu { fn setup_vmcs( &mut self, entry: GuestPhysAddr, - ept_root: HostPhysAddr, + nested_page_table_root: HostPhysAddr, config: X86VCpuSetupConfig, ) -> AxResult { let paddr = self.vmcs.phys_addr().as_usize() as u64; @@ -527,7 +531,7 @@ impl VmxVcpu { self.bind_to_current_processor()?; self.setup_msr_bitmap()?; self.setup_vmcs_guest(entry)?; - self.setup_vmcs_control(ept_root, true, config)?; + self.setup_vmcs_control(nested_page_table_root, true, config)?; self.unbind_from_current_processor()?; Ok(()) } @@ -630,7 +634,7 @@ impl VmxVcpu { fn setup_vmcs_control( &mut self, - ept_root: HostPhysAddr, + nested_page_table_root: HostPhysAddr, is_guest: bool, config: X86VCpuSetupConfig, ) -> AxResult { @@ -742,7 +746,7 @@ impl VmxVcpu { 0, )?; - vmcs::set_ept_pointer(ept_root)?; + vmcs::set_ept_pointer(nested_page_table_root)?; // No MSR switches if hypervisor doesn't use and there is only one vCPU. VmcsControl32::VMEXIT_MSR_STORE_COUNT.write(0)?; @@ -1019,7 +1023,7 @@ impl VmxVcpu { } } - fn handle_apic_msr_access(&mut self, write: bool, msr: u32) -> AxResult { + fn handle_apic_msr_access(&mut self, write: bool, msr: u32) -> AxResult { const VMEXIT_INSTR_LEN_RDMSR_WRMSR: u8 = 2; self.advance_rip(VMEXIT_INSTR_LEN_RDMSR_WRMSR)?; @@ -1031,7 +1035,7 @@ impl VmxVcpu { trace!("handle_vlapic_msr_write: msr={msr:#x}, value={value:#x}"); if msr == X2APIC_EOI_MSR { - Ok(AxVCpuExitReason::InterruptEnd { + Ok(VmExit::InterruptEnd { vector: self.vlapic.handle_eoi(), }) } else { @@ -1041,7 +1045,7 @@ impl VmxVcpu { AccessWidth::Qword, value, )?; - Ok(AxVCpuExitReason::Nothing) + Ok(VmExit::Nothing) } } else { let value = >::handle_read( @@ -1053,7 +1057,7 @@ impl VmxVcpu { trace!("handle_vlapic_msr_read: msr={msr:#x}, value={value:#x}"); self.write_edx_eax(value); - Ok(AxVCpuExitReason::Nothing) + Ok(VmExit::Nothing) } } @@ -1067,7 +1071,7 @@ impl VmxVcpu { Ok(()) } - fn handle_apic_access(&mut self, exit_info: &VmxExitInfo) -> AxResult { + fn handle_apic_access(&mut self, exit_info: &VmxExitInfo) -> AxResult { let apic_access_exit_info = self.apic_access_exit_info()?; let write = match apic_access_exit_info.access_type { @@ -1084,11 +1088,11 @@ impl VmxVcpu { let reg = apic_access_exit_info.offset as usize; let addr = GuestPhysAddr::from(X86_APIC_ACCESS_GPA + reg); - let mut exit_reason = AxVCpuExitReason::Nothing; + let mut exit_reason = VmExit::Nothing; if write { let value = self.decode_apic_mmio_write_value(exit_info)?; if reg == X86_LOCAL_APIC_EOI_OFFSET { - exit_reason = AxVCpuExitReason::InterruptEnd { + exit_reason = VmExit::InterruptEnd { vector: self.vlapic.handle_eoi(), }; } else { @@ -1153,7 +1157,7 @@ impl VmxVcpu { exit_info: &VmxExitInfo, addr: GuestPhysAddr, write: bool, - ) -> Option<(AxVCpuExitReason, u8)> { + ) -> Option<(VmExit, u8)> { // Keep EPT-violation MMIO decoding scoped to the PC APIC windows used // by the current x86 Linux direct-boot path. The VMX exit qualification // alone does not tell us whether an unmapped GPA is an emulated device @@ -1216,9 +1220,9 @@ impl VmxVcpu { .ok()?; self.regs_mut() .set_reg_of_index(reg as u8, val as u32 as u64); - AxVCpuExitReason::Nothing + VmExit::Nothing } else { - AxVCpuExitReason::MmioRead { + VmExit::MmioRead { addr, width: AccessWidth::Dword, reg, @@ -1240,9 +1244,9 @@ impl VmxVcpu { addr: GuestPhysAddr, data: u64, local_apic: bool, - ) -> Option { + ) -> Option { if !local_apic { - return Some(AxVCpuExitReason::MmioWrite { + return Some(VmExit::MmioWrite { addr, width: AccessWidth::Dword, data, @@ -1251,7 +1255,7 @@ impl VmxVcpu { let offset = addr.as_usize() - X86_APIC_ACCESS_GPA; if offset == X86_LOCAL_APIC_EOI_OFFSET { - return Some(AxVCpuExitReason::InterruptEnd { + return Some(VmExit::InterruptEnd { vector: self.vlapic.handle_eoi(), }); } @@ -1263,7 +1267,7 @@ impl VmxVcpu { data as usize, ) .ok()?; - Some(AxVCpuExitReason::Nothing) + Some(VmExit::Nothing) } fn skip_simple_prefixes(&self, rip: &mut GuestVirtAddr, rex: &mut u8) -> AxResult { @@ -1623,7 +1627,7 @@ impl Debug for VmxVcpu { } impl VmArchVcpuOps for VmxVcpu { - type CreateConfig = (); + type CreateConfig = X86VCpuCreateConfig; type SetupConfig = X86VCpuSetupConfig; @@ -1636,19 +1640,23 @@ impl VmArchVcpuOps for VmxVcpu { Ok(()) } - fn set_ept_root(&mut self, ept_root: HostPhysAddr) -> AxResult { - self.ept_root = Some(ept_root); + fn set_nested_page_table_root(&mut self, nested_page_table_root: HostPhysAddr) -> AxResult { + self.nested_page_table_root = Some(nested_page_table_root); Ok(()) } fn setup(&mut self, config: Self::SetupConfig) -> AxResult { - self.setup_vmcs(self.entry.unwrap(), self.ept_root.unwrap(), config) + self.setup_vmcs( + self.entry.unwrap(), + self.nested_page_table_root.unwrap(), + config, + ) } - fn run(&mut self) -> AxResult { + fn run(&mut self) -> AxResult { match self.inner_run()? { Some(exit_info) => Ok(if exit_info.entry_failure { - AxVCpuExitReason::FailEntry { + VmExit::FailEntry { // Todo: get `hardware_entry_failure_reason` somehow. hardware_entry_failure_reason: 0, } @@ -1656,7 +1664,7 @@ impl VmArchVcpuOps for VmxVcpu { match exit_info.exit_reason { VmxExitReason::VMCALL => { self.advance_rip(exit_info.exit_instruction_length as _)?; - AxVCpuExitReason::Hypercall { + VmExit::Hypercall { nr: self.regs().rax, args: [ self.regs().rdi, @@ -1677,19 +1685,19 @@ impl VmArchVcpuOps for VmxVcpu { if io_info.is_repeat || io_info.is_string { warn!("VMX unsupported IO-Exit: {io_info:#x?} of {exit_info:#x?}"); warn!("VCpu {self:#x?}"); - AxVCpuExitReason::Halt + VmExit::Halt } else { let width = match AccessWidth::try_from(io_info.access_size as usize) { Ok(width) => width, Err(_) => { warn!("VMX invalid IO-Exit: {io_info:#x?} of {exit_info:#x?}"); warn!("VCpu {self:#x?}"); - return Ok(AxVCpuExitReason::Halt); + return Ok(VmExit::Halt); } }; if io_info.is_in { - AxVCpuExitReason::IoRead { + VmExit::IoRead { port: Port(port), width, } @@ -1697,9 +1705,9 @@ impl VmArchVcpuOps for VmxVcpu { && width == AccessWidth::Word && self.regs().rax == QEMU_EXIT_MAGIC { - AxVCpuExitReason::SystemDown + VmExit::SystemDown } else { - AxVCpuExitReason::IoWrite { + VmExit::IoWrite { port: Port(port), width, data: self.regs().rax.get_bits(width.bits_range()), @@ -1710,28 +1718,28 @@ impl VmArchVcpuOps for VmxVcpu { VmxExitReason::EXTERNAL_INTERRUPT => { let int_info = self.interrupt_exit_info()?; assert!(int_info.valid); - AxVCpuExitReason::ExternalInterrupt { + VmExit::ExternalInterrupt { vector: int_info.vector as _, } } VmxExitReason::PREEMPTION_TIMER => { self.handle_vmx_preemption_timer()?; - AxVCpuExitReason::PreemptionTimer + VmExit::PreemptionTimer } VmxExitReason::HLT => { self.advance_rip(exit_info.exit_instruction_length as _)?; - AxVCpuExitReason::PreemptionTimer + VmExit::PreemptionTimer } - VmxExitReason::VIRTUALIZED_EOI => AxVCpuExitReason::InterruptEnd { + VmxExitReason::VIRTUALIZED_EOI => VmExit::InterruptEnd { vector: self.vlapic.handle_eoi(), }, VmxExitReason::APIC_WRITE => { let offset = self.apic_access_exit_info()?.offset as usize; if offset == X86_LOCAL_APIC_EOI_OFFSET { let vector = self.vlapic.handle_eoi(); - AxVCpuExitReason::InterruptEnd { vector } + VmExit::InterruptEnd { vector } } else { - AxVCpuExitReason::Nothing + VmExit::Nothing } } VmxExitReason::APIC_ACCESS => self.handle_apic_access(&exit_info)?, @@ -1749,7 +1757,7 @@ impl VmArchVcpuOps for VmxVcpu { self.advance_rip(instruction_len)?; mmio_exit } else { - AxVCpuExitReason::NestedPageFault { + VmExit::NestedPageFault { addr: info.fault_guest_paddr, access_flags: info.access_flags, } @@ -1761,7 +1769,7 @@ impl VmArchVcpuOps for VmxVcpu { self.handle_apic_msr_access(false, msr)? } else { // `reg` is unused here. - AxVCpuExitReason::SysRegRead { + VmExit::SysRegRead { addr: SysRegAddr::new(msr as _), reg: 0, } @@ -1774,7 +1782,7 @@ impl VmArchVcpuOps for VmxVcpu { } else { let value = (self.regs().rax & 0xffff_ffff) | ((self.regs().rdx & 0xffff_ffff) << 32); - AxVCpuExitReason::SysRegWrite { + VmExit::SysRegWrite { addr: SysRegAddr::new(msr as _), value, } @@ -1783,11 +1791,11 @@ impl VmArchVcpuOps for VmxVcpu { _ => { warn!("VMX unsupported VM-Exit: {exit_info:#x?}"); warn!("VCpu {self:#x?}"); - AxVCpuExitReason::Halt + VmExit::Halt } } }), - None => Ok(AxVCpuExitReason::Nothing), + None => Ok(VmExit::Nothing), } }