VCPU Lifecycle and Operations
Relevant source files
This document details the virtual CPU (VCPU) creation, configuration, execution cycle, and VM exit handling mechanisms within the arm_vcpu hypervisor system. It covers the core Aarch64VCpu implementation, context management, and the complete lifecycle from VCPU instantiation to guest execution and exit handling.
For hardware abstraction and platform integration details, see Hardware Abstraction and Platform Support. For low-level exception vector implementation, see Assembly Exception Vectors. For per-CPU state management across multiple VCPUs, see Per-CPU State Management.
VCPU Structure and Components
The Aarch64VCpu struct serves as the primary virtual CPU implementation, containing all necessary state for guest execution and host-guest context switching.
Core VCPU Structure
classDiagram
class Aarch64VCpu {
+TrapFrame ctx
+u64 host_stack_top
+GuestSystemRegisters guest_system_regs
+u64 mpidr
+PhantomData~H~ _phantom
+new(config) AxResult~Self~
+setup(config) AxResult
+run() AxResult~AxVCpuExitReason~
+set_entry(entry) AxResult
+set_ept_root(ept_root) AxResult
}
class TrapFrame {
+u64[31] gpr
+u64 sp_el0
+u64 elr
+u64 spsr
+set_exception_pc(pc)
+set_argument(arg)
+set_gpr(idx, val)
+gpr(idx) usize
}
class GuestSystemRegisters {
+u64 cntvoff_el2
+u32 cntkctl_el1
+u64 sp_el0
+u32 sctlr_el1
+u64 hcr_el2
+u64 vttbr_el2
+u64 vmpidr_el2
+store()
+restore()
}
class Aarch64VCpuCreateConfig {
+u64 mpidr_el1
+usize dtb_addr
}
Aarch64VCpu *-- TrapFrame : "contains"
Aarch64VCpu *-- GuestSystemRegisters : "contains"
Aarch64VCpu ..> Aarch64VCpuCreateConfig : "uses for creation"
The VCPU structure maintains strict field ordering requirements for assembly code interaction. The ctx and host_stack_top fields must remain in their current positions and order to support the low-level context switching assembly routines.
Sources: src/vcpu.rs(L40 - L51) src/context_frame.rs(L17 - L28) src/context_frame.rs(L145 - L197)
VCPU Creation and Configuration
Creation Process
The VCPU creation follows a two-phase initialization pattern through the AxArchVCpu trait implementation:
sequenceDiagram
participant Client as Client
participant Aarch64VCpu as Aarch64VCpu
participant TrapFrame as TrapFrame
participant GuestSystemRegisters as GuestSystemRegisters
Client ->> Aarch64VCpu: new(Aarch64VCpuCreateConfig)
Aarch64VCpu ->> TrapFrame: TrapFrame::default()
Aarch64VCpu ->> TrapFrame: set_argument(config.dtb_addr)
Aarch64VCpu ->> GuestSystemRegisters: GuestSystemRegisters::default()
Aarch64VCpu -->> Client: Aarch64VCpu instance
Client ->> Aarch64VCpu: setup(())
Aarch64VCpu ->> Aarch64VCpu: init_hv()
Aarch64VCpu ->> Aarch64VCpu: init_vm_context()
Aarch64VCpu -->> Client: AxResult
Client ->> Aarch64VCpu: set_entry(guest_entry_point)
Aarch64VCpu ->> TrapFrame: set_exception_pc(entry)
Client ->> Aarch64VCpu: set_ept_root(page_table_root)
Aarch64VCpu ->> GuestSystemRegisters: vttbr_el2 = ept_root
The new() method creates a VCPU instance with minimal initialization, setting up the trap context with the device tree address and initializing default system registers. The setup() method performs hypervisor-specific initialization including guest execution state configuration.
Sources: src/vcpu.rs(L69 - L80) src/vcpu.rs(L82 - L85) src/vcpu.rs(L87 - L97)
Hypervisor Context Initialization
The init_hv() and init_vm_context() methods configure the guest execution environment:
| Register/Field | Value | Purpose |
|---|---|---|
| SPSR_EL1 | EL1h + All exceptions masked | Guest starts in EL1 with interrupts disabled |
| CNTHCTL_EL2 | EL1PCEN + EL1PCTEN | Enable timer access from EL1 |
| SCTLR_EL1 | 0x30C50830 | System control register defaults |
| VTCR_EL2 | 40-bit PA, 4KB granule, stage-2 config | Stage-2 translation control |
| HCR_EL2 | VM + RW + TSC | Enable virtualization, 64-bit EL1, trap SMC |
| VMPIDR_EL2 | Configured MPIDR | Virtual multiprocessor ID |
Sources: src/vcpu.rs(L128 - L136) src/vcpu.rs(L139 - L166)
VCPU Execution Cycle
VM Entry and Exit Flow
The VCPU execution follows a carefully orchestrated context switching process:
flowchart TD start["vcpu.run()"] save_host["save_host_sp_el0()"] restore_vm["restore_vm_system_regs()"] run_guest["run_guest()"] save_regs["save_regs_to_stack!()"] save_stack["Save host stack to host_stack_top"] vm_entry["context_vm_entry"] guest_exec["Guest Execution"] exception["Exception/Interrupt"] vmexit["VM Exit"] save_guest["SAVE_REGS_FROM_EL1"] handler["vmexit_handler()"] store_regs["guest_system_regs.store()"] restore_host_sp["restore_host_sp_el0()"] dispatch["Exception dispatch"] sync["handle_exception_sync()"] irq["ExternalInterrupt"] exit_reason["AxVCpuExitReason"] dispatch --> irq dispatch --> sync exception --> vmexit exit_reason --> start guest_exec --> exception handler --> store_regs irq --> exit_reason restore_host_sp --> dispatch restore_vm --> run_guest run_guest --> save_regs save_guest --> handler save_host --> restore_vm save_regs --> save_stack save_stack --> vm_entry start --> save_host store_regs --> restore_host_sp sync --> exit_reason vm_entry --> guest_exec vmexit --> save_guest
The execution cycle begins with the run() method, which saves host state, restores guest system registers, and transitions to guest execution through the run_guest() naked function.
Sources: src/vcpu.rs(L99 - L111) src/vcpu.rs(L186 - L214) src/vcpu.rs(L255 - L282)
Context Switching Implementation
The run_guest() function implements a naked assembly function that performs the critical host-to-guest transition:
flowchart TD
subgraph subGraph2["Guest Context"]
guest_regs["Guest GPRs"]
guest_sys_regs["Guest System Registers"]
guest_exec["Guest Execution"]
end
subgraph Transition["Transition"]
save_macro["save_regs_to_stack!()"]
save_sp["Save stack pointer to host_stack_top"]
vm_entry["Branch to context_vm_entry"]
end
subgraph subGraph0["Host Context"]
host_regs["Host GPRs (x19-x30)"]
host_sp["Host SP_EL0"]
host_stack["Host Stack"]
end
guest_regs --> guest_exec
guest_sys_regs --> guest_exec
host_regs --> save_macro
host_stack --> save_sp
save_macro --> vm_entry
save_sp --> vm_entry
vm_entry --> guest_regs
vm_entry --> guest_sys_regs
The naked function ensures no compiler-generated prologue/epilogue interferes with the precise register state management required for hypervisor operation.
Sources: src/vcpu.rs(L186 - L214) src/vcpu.rs(L225 - L244)
VM Exit Handling
Exit Reason Processing
The vmexit_handler() method processes VM exits and converts low-level trap information into structured exit reasons:
flowchart TD vmexit["vmexit_handler(TrapKind)"] store["guest_system_regs.store()"] save_sp["Save guest SP_EL0 to ctx.sp_el0"] restore_host["restore_host_sp_el0()"] match_trap["Match TrapKind"] sync["TrapKind::Synchronous"] irq["TrapKind::Irq"] other["Other TrapKind"] handle_sync["handle_exception_sync(&mut ctx)"] fetch_irq["H::irq_fetch()"] panic["panic!(Unhandled exception)"] exit_reason["AxVCpuExitReason"] ext_int["AxVCpuExitReason::ExternalInterrupt"] return_result["Return to hypervisor"] exit_reason --> return_result ext_int --> return_result fetch_irq --> ext_int handle_sync --> exit_reason irq --> fetch_irq match_trap --> irq match_trap --> other match_trap --> sync other --> panic restore_host --> match_trap save_sp --> restore_host store --> save_sp sync --> handle_sync vmexit --> store
The exit handler ensures proper state preservation by storing guest system registers and restoring host SP_EL0 before processing the specific exit cause.
Sources: src/vcpu.rs(L255 - L282)
Exit Reason Types
The system generates specific exit reasons that inform the hypervisor about the cause of the VM exit:
| Exit Reason | Trigger | Handler Location |
|---|---|---|
| MmioRead/Write | Data abort to device memory | handle_data_abort |
| SystemRegisterRead/Write | Trapped system register access | handle_system_register |
| Hypercall | HVC instruction | handle_psci_call |
| CpuUp/Down/SystemDown | PSCI power management | handle_psci_call |
| ExternalInterrupt | Physical interrupt | vmexit_handler |
Sources: src/vcpu.rs(L276 - L281)
Context Management
Register State Preservation
The VCPU maintains two primary context structures for complete state preservation:
flowchart TD
subgraph subGraph2["Hardware Registers"]
hw_regs["Actual AArch64 System Registers"]
end
subgraph subGraph1["GuestSystemRegisters (System State)"]
el1_state["EL1 State (sctlr_el1, vbar_el1, etc)"]
subgraph subGraph0["TrapFrame (GPR State)"]
timer["Timer Registers (cntvoff_el2, cntkctl_el1)"]
vm_id["VM Identity (vmpidr_el2, vpidr_el2)"]
memory["Memory Management (ttbr0/1_el1, tcr_el1)"]
hyp_ctrl["Hypervisor Control (hcr_el2, vttbr_el2)"]
gpr["gpr[31] - General Purpose Registers"]
sp_el0["sp_el0 - EL0 Stack Pointer"]
elr["elr - Exception Link Register"]
spsr["spsr - Saved Program Status"]
end
end
TrapFrame["TrapFrame"]
GuestSystemRegisters["GuestSystemRegisters"]
GuestSystemRegisters --> hw_regs
TrapFrame --> hw_regs
The TrapFrame captures the basic CPU execution state, while GuestSystemRegisters preserves the complete virtual machine system configuration.
Sources: src/context_frame.rs(L17 - L136) src/context_frame.rs(L213 - L300)
Host State Management
Host state preservation uses a combination of stack storage and per-CPU variables:
sequenceDiagram
participant Host as Host
participant PerCPU as PerCPU
participant Guest as Guest
participant stack as stack
Note over Host,Guest: VM Entry Sequence
Host ->> stack: save_regs_to_stack!() - GPRs x19-x30
Host ->> PerCPU: save_host_sp_el0() - HOST_SP_EL0
Host ->> Guest: Context switch to guest
Note over Host,Guest: VM Exit Sequence
Guest ->> PerCPU: Store guest SP_EL0 to ctx.sp_el0
Guest ->> PerCPU: restore_host_sp_el0() - Restore HOST_SP_EL0
Guest ->> stack: restore_regs_from_stack!() - Restore GPRs
Guest ->> Host: Return to host execution
The per-CPU HOST_SP_EL0 variable ensures each CPU core maintains independent host state, supporting multi-core hypervisor operation.