Overview
Relevant source files
Purpose and Scope
The axvm crate provides a minimal virtual machine monitor (VMM) for the ArceOS hypervisor ecosystem. It implements core virtualization capabilities including virtual CPU management, memory virtualization, and device emulation across multiple hardware architectures. This document covers the fundamental architecture and capabilities of the axvm hypervisor.
For detailed information about the VM configuration system, see Configuration System. For build and development processes, see Development Guide.
High-Level Architecture
The axvm hypervisor is structured around four core modules that provide a clean abstraction layer for virtualization:
AxVM Core Components
flowchart TD
subgraph subGraph0["Main Exports"]
AxVM_Export["AxVM"]
AxVMHal_Export["AxVMHal"]
AxVCpuRef_Export["AxVCpuRef"]
AxVMRef_Export["AxVMRef"]
end
LibRS["lib.rsMain Library Interface"]
VM["vm.rsAxVM Core Implementation"]
VCPU["vcpu.rsMulti-Architecture VCPU"]
HAL["hal.rsHardware Abstraction Layer"]
CONFIG["config.rsVM Configuration"]
LibRS --> AxVCpuRef_Export
LibRS --> AxVMHal_Export
LibRS --> AxVMRef_Export
LibRS --> AxVM_Export
LibRS --> CONFIG
LibRS --> HAL
LibRS --> VCPU
LibRS --> VM
VM --> CONFIG
VM --> HAL
VM --> VCPU
The main library interface exports the AxVM struct as the primary virtualization abstraction, along with supporting types for hardware abstraction (AxVMHal) and CPU references (AxVCpuRef, AxVMRef). The has_hardware_support() function provides runtime detection of virtualization capabilities.
Sources: src/lib.rs(L6 - L33) Cargo.toml(L1 - L8)
Multi-Architecture Support
The axvm hypervisor provides unified virtualization across x86_64, RISC-V, and AArch64 architectures through architecture-specific backend implementations:
Architecture-Specific Backend Structure
The multi-architecture support is implemented through conditional compilation using cfg-if directives. Each architecture provides its own VCPU implementation while conforming to the common AxArchVCpuImpl interface. The AxVMPerCpu type is defined as axvcpu::AxPerCpu<vcpu::AxVMArchPerCpuImpl<U>> to provide per-CPU state management across all supported architectures.
Sources: Cargo.toml(L29 - L36) src/lib.rs(L26 - L32) Cargo.toml(L19 - L21)
Core Capabilities
The axvm hypervisor provides three primary virtualization capabilities through its modular architecture:
| Capability | Implementation | Key Components |
|---|---|---|
| Virtual CPU Management | Multi-architecture VCPU abstraction | AxArchVCpuImpl, architecture-specific backends |
| Memory Virtualization | Address space and page table management | axaddrspace,page_table_multiarch |
| Device Emulation | Hardware device virtualization | axdevice, MMIO handling |
VM Lifecycle and State Management
The VM lifecycle is managed through the core AxVM struct, which coordinates virtual CPU execution, handles VM exits for MMIO and I/O operations, and manages the overall VM state transitions.
Sources: src/lib.rs(L9 - L10) src/lib.rs(L22 - L24)
ArceOS Ecosystem Integration
The axvm hypervisor integrates deeply with the ArceOS ecosystem through a set of specialized crates that provide system-level abstractions:
Dependency Architecture
flowchart TD
subgraph subGraph3["External Dependencies"]
log_crate["logLogging framework"]
cfg_if["cfg-ifConditional compilation"]
spin["spinSynchronization primitives"]
end
subgraph subGraph2["ArceOS System Crates"]
axerrno["axerrnoError handling"]
memory_addr["memory_addrAddress types"]
page_table_multiarch["page_table_multiarchMulti-arch page tables"]
page_table_entry["page_table_entryARM EL2 support"]
percpu["percpuPer-CPU data structures"]
end
subgraph subGraph1["ArceOS Hypervisor Modules"]
axvcpu["axvcpuVirtual CPU abstraction"]
axaddrspace["axaddrspaceAddress space management"]
axdevice["axdeviceDevice emulation framework"]
axvmconfig["axvmconfigConfiguration management"]
end
subgraph subGraph0["AxVM Core"]
axvm["axvm crate"]
end
axvm --> axaddrspace
axvm --> axdevice
axvm --> axerrno
axvm --> axvcpu
axvm --> axvmconfig
axvm --> cfg_if
axvm --> log_crate
axvm --> memory_addr
axvm --> page_table_entry
axvm --> page_table_multiarch
axvm --> percpu
axvm --> spin
The crate uses the no_std environment for embedded and kernel-space deployment. All ArceOS hypervisor modules are sourced from the arceos-hypervisor GitHub organization, providing a cohesive ecosystem for virtualization components.
Sources: Cargo.toml(L10 - L27) src/lib.rs(L1 - L13)
The AxVMHal trait provides the hardware abstraction interface that must be implemented by the hosting environment, enabling axvm to run on different underlying platforms while maintaining a consistent virtualization interface.
Sources: src/lib.rs(L21)
Core Architecture
Relevant source files
This document provides a detailed examination of axvm's fundamental architecture, focusing on the core components that enable virtual machine creation, management, and execution. It covers the main AxVM struct, its internal organization, lifecycle management, and integration with hardware abstraction layers.
For information about specific vCPU architecture implementations, see Virtual CPU Architecture. For details about hardware abstraction interfaces, see Hardware Abstraction Layer. For VM configuration mechanisms, see Configuration System.
Architecture Overview
The axvm hypervisor is built around a central AxVM struct that coordinates virtual machine execution through a clean separation of concerns. The architecture employs generic type parameters to abstract hardware and vCPU implementations while maintaining compile-time optimization.
flowchart TD
subgraph subGraph4["Generic Type Parameters"]
HAL["H: AxVMHal"]
VCPUHAL["U: AxVCpuHal"]
end
subgraph subGraph3["VM Lifecycle Flags"]
Running["running: AtomicBool"]
ShuttingDown["shutting_down: AtomicBool"]
end
subgraph subGraph2["Mutable VM State"]
AddrSpace["address_space: Mutex<AddrSpace<H::PagingHandler>>"]
PhantomData["_marker: PhantomData<H>"]
end
subgraph subGraph1["Immutable VM State"]
VMId["id: usize"]
VMConfig["config: AxVMConfig"]
VCpuList["vcpu_list: Box[AxVCpuRef<U>]"]
VMDevices["devices: AxVmDevices"]
end
subgraph subGraph0["Core VM Structure"]
AxVM["AxVM<H, U>"]
AxVMInnerConst["AxVMInnerConst<U>"]
AxVMInnerMut["AxVMInnerMut<H>"]
end
AxVM --> AxVMInnerConst
AxVM --> AxVMInnerMut
AxVM --> HAL
AxVM --> Running
AxVM --> ShuttingDown
AxVM --> VCPUHAL
AxVMInnerConst --> VCpuList
AxVMInnerConst --> VMConfig
AxVMInnerConst --> VMDevices
AxVMInnerConst --> VMId
AxVMInnerMut --> AddrSpace
AxVMInnerMut --> PhantomData
Sources: src/vm.rs(L47 - L53) src/vm.rs(L31 - L39) src/vm.rs(L41 - L45)
Core VM Structure
The AxVM struct is organized into distinct sections that separate immutable configuration data from mutable runtime state. This design ensures thread safety while minimizing lock contention during VM execution.
Data Organization
| Component | Type | Purpose | Thread Safety |
|---|---|---|---|
| running | AtomicBool | VM execution state | Lock-free atomic operations |
| shutting_down | AtomicBool | VM shutdown state | Lock-free atomic operations |
| inner_const | AxVMInnerConst | Immutable VM configuration | Unsafe Send/Sync implementation |
| inner_mut | AxVMInnerMut | Mutable runtime state | Mutex-protected access |
The immutable section (AxVMInnerConst) contains the VM identifier, configuration, vCPU list, and device registry. The mutable section (AxVMInnerMut) contains the address space with its associated page table handler.
Sources: src/vm.rs(L47 - L53) src/vm.rs(L31 - L39) src/vm.rs(L41 - L45)
VM Creation and Initialization
VM creation follows a multi-stage initialization process that sets up vCPUs, memory regions, and devices before the VM can be executed.
sequenceDiagram
participant AxVMnew as "AxVM::new()"
participant vCPUCreation as "vCPU Creation"
participant MemorySetup as "Memory Setup"
participant DeviceSetup as "Device Setup"
participant AddrSpace as "AddrSpace"
AxVMnew ->> vCPUCreation: "Create VCpus from config"
Note over vCPUCreation: "Architecture-specific AxVCpuCreateConfig"
vCPUCreation ->> vCPUCreation: "Arc::new(VCpu::new())"
AxVMnew ->> MemorySetup: "Process memory_regions()"
MemorySetup ->> AddrSpace: "AddrSpace::new_empty()"
loop "For each memory region"
MemorySetup ->> MemorySetup: "Validate MappingFlags"
alt "VmMemMappingType::MapIentical"
MemorySetup ->> AddrSpace: "map_linear()"
else "VmMemMappingType::MapAlloc"
MemorySetup ->> AddrSpace: "map_alloc()"
end
end
loop "For each pass_through_device"
MemorySetup ->> AddrSpace: "map_linear() with DEVICE flags"
end
AxVMnew ->> DeviceSetup: "AxVmDevices::new()"
DeviceSetup ->> DeviceSetup: "Initialize from emu_configs"
AxVMnew ->> AxVMnew: "Create AxVM struct"
loop "For each vCPU"
AxVMnew ->> vCPUCreation: "vcpu.setup(entry, ept_root, config)"
end
Sources: src/vm.rs(L59 - L219) src/vm.rs(L95 - L163) src/vm.rs(L182 - L184) src/vm.rs(L204 - L217)
Memory Region Setup
The VM initialization process handles two distinct memory mapping types:
VmMemMappingType::MapIentical: Creates identity mappings where guest physical addresses map directly to host physical addressesVmMemMappingType::MapAlloc: Allocates new physical memory pages that may be non-contiguous in host physical memory
The address space spans from VM_ASPACE_BASE (0x0) to VM_ASPACE_SIZE (0x7fff_ffff_f000), providing a large virtual address space for guest execution.
Sources: src/vm.rs(L18 - L19) src/vm.rs(L122 - L162)
VM Lifecycle Management
The VM lifecycle is controlled through atomic boolean flags that coordinate state transitions across multiple threads without requiring locks.
State Validation
VM operations include state validation to prevent invalid transitions:
boot()checks hardware virtualization support and prevents double-bootingshutdown()prevents multiple shutdown attemptsrun_vcpu()validates vCPU existence before execution
Sources: src/vm.rs(L277 - L288) src/vm.rs(L299 - L310) src/vm.rs(L328 - L376)
vCPU Execution Loop
The core VM execution occurs in the run_vcpu() method, which implements a continuous loop that handles various exit reasons from hardware virtualization.
flowchart TD Start["run_vcpu(vcpu_id)"] ValidateVCpu["vcpu(vcpu_id)?"] BindVCpu["vcpu.bind()"] RunLoop["vcpu.run()"] ExitReason["Match exit_reason"] MmioRead["MmioRead"] MmioWrite["MmioWrite"] PageFault["NestedPageFault"] IoOps["IoRead/IoWrite"] Other["Other reasons"] DeviceRead["devices.handle_mmio_read()"] SetGPR["vcpu.set_gpr()"] Continue["Continue loop"] DeviceWrite["devices.handle_mmio_write()"] HandleFault["address_space.handle_page_fault()"] Unbind["vcpu.unbind()"] Return["Return exit_reason"] BindVCpu --> RunLoop Continue --> RunLoop DeviceRead --> SetGPR DeviceWrite --> Continue ExitReason --> IoOps ExitReason --> MmioRead ExitReason --> MmioWrite ExitReason --> Other ExitReason --> PageFault HandleFault --> Continue IoOps --> Continue MmioRead --> DeviceRead MmioWrite --> DeviceWrite Other --> Unbind PageFault --> HandleFault RunLoop --> ExitReason SetGPR --> Continue Start --> ValidateVCpu Unbind --> Return ValidateVCpu --> BindVCpu
The execution loop handles several categories of VM exits:
- MMIO Operations: Delegated to the device emulation layer via
AxVmDevices - Page Faults: Handled by the address space's page fault handler
- I/O Operations: Currently handled as no-ops
- Unhandled Exits: Return control to the caller
Sources: src/vm.rs(L328 - L376) src/vm.rs(L335 - L372)
Component Integration
The AxVM struct integrates with several key subsystems through well-defined interfaces:
Address Space Integration
flowchart TD AxVM["AxVM"] AddrSpaceMutex["Mutex<AddrSpace<H::PagingHandler>>"] AddrSpace["AddrSpace"] PageTableRoot["page_table_root()"] HandlePageFault["handle_page_fault()"] TranslateBuffer["translated_byte_buffer()"] EPTRoot["ept_root() for vCPU setup"] ImageLoad["get_image_load_region()"] AddrSpace --> HandlePageFault AddrSpace --> PageTableRoot AddrSpace --> TranslateBuffer AddrSpaceMutex --> AddrSpace AxVM --> AddrSpaceMutex PageTableRoot --> EPTRoot TranslateBuffer --> ImageLoad
Device Integration
flowchart TD AxVM["AxVM"] AxVmDevices["AxVmDevices"] MmioRead["handle_mmio_read()"] MmioWrite["handle_mmio_write()"] DeviceConfig["AxVmDeviceConfig"] EmuConfigs["emu_configs: Vec"] AxVM --> AxVmDevices AxVmDevices --> DeviceConfig AxVmDevices --> MmioRead AxVmDevices --> MmioWrite DeviceConfig --> EmuConfigs
vCPU Management
The VM maintains references to vCPUs through Arc<VCpu<AxArchVCpuImpl<U>>> allowing shared ownership across threads while providing architecture-independent access through the AxArchVCpu trait.
Sources: src/vm.rs(L21 - L26) src/vm.rs(L242 - L250) src/vm.rs(L260 - L270) src/vm.rs(L315 - L318)
Type System and Generics
The architecture leverages Rust's type system to provide compile-time guarantees about hardware compatibility and vCPU implementations:
H: AxVMHal: Hardware abstraction layer interfaceU: AxVCpuHal: vCPU hardware abstraction layer interface- Type Aliases:
AxVMRef<H, U>andAxVCpuRef<U>provide ergonomic reference types
This design enables architecture-specific optimizations while maintaining a unified API across different hardware platforms.
Sources: src/vm.rs(L21 - L29) src/lib.rs(L21 - L27)
Virtual Machine Implementation
Relevant source files
This document covers the core virtual machine implementation in AxVM, focusing on the AxVM struct and its lifecycle management. This includes VM creation, booting, vCPU execution coordination, memory management, and device emulation integration. For details about the underlying vCPU architecture abstraction, see Virtual CPU Architecture. For hardware abstraction interfaces, see Hardware Abstraction Layer.
AxVM Structure Overview
The AxVM struct serves as the central coordinator for all virtual machine operations. It uses a generic design with two type parameters to support multiple architectures and hardware abstraction layers.
classDiagram
class AxVM {
+running: AtomicBool
+shutting_down: AtomicBool
+inner_const: AxVMInnerConst
+inner_mut: AxVMInnerMut
+new(config: AxVMConfig) AxResult~AxVMRef~
+boot() AxResult
+shutdown() AxResult
+run_vcpu(vcpu_id: usize) AxResult~AxVCpuExitReason~
+vcpu(vcpu_id: usize) Option~AxVCpuRef~
+get_devices() &AxVmDevices
}
class AxVMInnerConst {
+id: usize
+config: AxVMConfig
+vcpu_list: Box[AxVCpuRef]
+devices: AxVmDevices
}
class AxVMInnerMut {
+address_space: Mutex~AddrSpace~
+_marker: PhantomData
}
AxVM *-- AxVMInnerConst : "contains"
AxVM *-- AxVMInnerMut : "contains"
Sources: src/vm.rs(L47 - L53) src/vm.rs(L31 - L40) src/vm.rs(L41 - L45)
The structure separates immutable data (AxVMInnerConst) from mutable data (AxVMInnerMut) to optimize concurrent access patterns. The type aliases provide convenient references for shared ownership.
| Type Alias | Definition | Purpose |
|---|---|---|
| VCpu | AxVCpu<AxArchVCpuImpl> | Architecture-independent vCPU interface |
| AxVCpuRef | Arc<VCpu> | Shared reference to a vCPU |
| AxVMRef<H, U> | Arc<AxVM<H, U>> | Shared reference to a VM |
Sources: src/vm.rs(L21 - L29)
VM Creation Process
VM creation follows a multi-stage initialization process that sets up all necessary components before the VM can be booted.
sequenceDiagram
participant Client as Client
participant AxVMnew as AxVM::new
participant VCpuCreation as VCpu Creation
participant AddressSpace as Address Space
participant DeviceSetup as Device Setup
participant VCpuSetup as VCpu Setup
Client ->> AxVMnew: "AxVMConfig"
Note over AxVMnew: "Parse vcpu affinities"
AxVMnew ->> VCpuCreation: "for each vcpu_id, phys_cpu_set"
VCpuCreation ->> VCpuCreation: "AxVCpuCreateConfig (arch-specific)"
VCpuCreation ->> AxVMnew: "Arc<VCpu>"
Note over AxVMnew: "Setup memory regions"
AxVMnew ->> AddressSpace: "AddrSpace::new_empty()"
AxVMnew ->> AddressSpace: "map_linear() / map_alloc()"
Note over AxVMnew: "Setup passthrough devices"
AxVMnew ->> AddressSpace: "map_linear() with DEVICE flags"
AxVMnew ->> DeviceSetup: "AxVmDevices::new()"
Note over AxVMnew: "Create AxVM instance"
AxVMnew ->> VCpuSetup: "vcpu.setup(entry, ept_root, config)"
VCpuSetup ->> AxVMnew: "Result"
AxVMnew ->> Client: "Arc<AxVM>"
Sources: src/vm.rs(L59 - L220)
vCPU Creation and Configuration
The VM creates vCPUs based on the configuration's CPU affinity settings. Each architecture requires specific configuration parameters:
| Architecture | Configuration Fields | Source Location |
|---|---|---|
| AArch64 | mpidr_el1,dtb_addr | src/vm.rs67-75 |
| RISC-V | hart_id,dtb_addr | src/vm.rs76-83 |
| x86_64 | Default (empty) | src/vm.rs84-85 |
Sources: src/vm.rs(L61 - L93)
Memory Region Setup
The VM supports two mapping types for memory regions:
MapIentical: Direct mapping where guest physical addresses map to identical host physical addressesMapAlloc: Dynamic allocation where the hypervisor allocates physical memory for the guest
flowchart TD config["Memory Region Config"] check_flags["Check MappingFlags validity"] map_type["Mapping Type?"] alloc_at["H::alloc_memory_region_at()"] map_linear1["address_space.map_linear()"] map_alloc["address_space.map_alloc()"] complete["Memory region mapped"] alloc_at --> map_linear1 check_flags --> map_type config --> check_flags map_alloc --> complete map_linear1 --> complete map_type --> alloc_at map_type --> map_alloc
Sources: src/vm.rs(L95 - L163)
For passthrough devices, the VM creates device mappings with the DEVICE flag to ensure proper memory attributes for hardware access.
Sources: src/vm.rs(L165 - L180)
VM Lifecycle Management
The VM follows a simple state machine with atomic boolean flags for coordination between multiple threads.
Sources: src/vm.rs(L49 - L50) src/vm.rs(L278 - L310)
Boot Process
The boot() method performs hardware support validation before allowing VM execution:
- Check hardware virtualization support via
has_hardware_support() - Verify VM is not already running
- Set
runningflag to true atomically
Sources: src/vm.rs(L278 - L288)
Shutdown Process
The shutdown() method initiates graceful VM termination:
- Check VM is not already shutting down
- Set
shutting_downflag to true - Note: VM re-initialization is not currently supported
Sources: src/vm.rs(L299 - L310)
vCPU Execution and Exit Handling
The run_vcpu() method implements the main VM execution loop, handling various exit reasons from the hardware virtualization layer.
flowchart TD start["run_vcpu(vcpu_id)"] bind["vcpu.bind()"] run_loop["vcpu.run()"] check_exit["Exit Reason?"] mmio_read["devices.handle_mmio_read()"] set_reg["vcpu.set_gpr(reg, val)"] continue_loop["continue_loop"] mmio_write["devices.handle_mmio_write()"] io_handled["Handle I/O (placeholder)"] page_fault["address_space.handle_page_fault()"] fault_handled["Handled?"] exit_loop["Break loop"] unbind["vcpu.unbind()"] return_exit["Return AxVCpuExitReason"] bind --> run_loop check_exit --> exit_loop check_exit --> io_handled check_exit --> mmio_read check_exit --> mmio_write check_exit --> page_fault continue_loop --> run_loop exit_loop --> unbind fault_handled --> continue_loop fault_handled --> exit_loop io_handled --> continue_loop mmio_read --> set_reg mmio_write --> continue_loop page_fault --> fault_handled run_loop --> check_exit set_reg --> continue_loop start --> bind unbind --> return_exit
Sources: src/vm.rs(L328 - L376)
Exit Reason Handling
The VM handles several categories of VM exits:
| Exit Type | Handler | Action |
|---|---|---|
| MmioRead | devices.handle_mmio_read() | Read from emulated device, set guest register |
| MmioWrite | devices.handle_mmio_write() | Write to emulated device |
| IoRead/IoWrite | Placeholder | Currently returns true (no-op) |
| NestedPageFault | address_space.handle_page_fault() | Handle EPT/NPT page fault |
Sources: src/vm.rs(L338 - L368)
Address Space and Memory Management
The VM uses a two-stage address translation system with an AddrSpace that manages guest-to-host physical address mappings.
Address Space Configuration
The VM address space uses fixed bounds to limit guest memory access:
| Constant | Value | Purpose |
|---|---|---|
| VM_ASPACE_BASE | 0x0 | Base guest physical address |
| VM_ASPACE_SIZE | 0x7fff_ffff_f000 | Maximum guest address space size |
Sources: src/vm.rs(L18 - L19)
EPT Root Access
The ept_root() method provides access to the page table root for hardware virtualization:
#![allow(unused)] fn main() { pub fn ept_root(&self) -> HostPhysAddr { self.inner_mut.address_space.lock().page_table_root() } }
Sources: src/vm.rs(L248 - L250)
Image Loading Support
The VM provides functionality to obtain host virtual addresses for loading guest images, handling potentially non-contiguous physical memory:
Sources: src/vm.rs(L260 - L270)
Device Integration
The VM integrates with the AxVM device emulation framework through the AxVmDevices component, which handles MMIO operations for emulated devices.
Device initialization occurs during VM creation, using the configuration's emulated device list:
Sources: src/vm.rs(L182 - L184)
Device access is coordinated through the exit handling mechanism, where MMIO reads and writes are forwarded to the appropriate device handlers.
Sources: src/vm.rs(L345 - L355)
Error Handling and Safety
The VM implementation uses Rust's type system and AxResult for comprehensive error handling:
- Hardware Support: Validates virtualization capabilities before boot
- State Validation: Prevents invalid state transitions (e.g., double boot)
- Resource Management: Uses
Arcfor safe sharing across threads - Memory Safety: Leverages Rust's ownership system for memory region management
Sources: src/vm.rs(L7) src/vm.rs(L279 - L287) src/vm.rs(L300 - L310)
Virtual CPU Architecture
Relevant source files
Purpose and Scope
The Virtual CPU Architecture in axvm provides a unified abstraction layer for virtual CPU management across multiple hardware architectures. This system enables axvm to support x86_64, RISC-V, and AArch64 platforms through architecture-specific backends while maintaining a consistent interface for the core hypervisor components.
This document covers the multi-architecture vCPU abstraction, architecture-specific implementations, configuration mechanisms, and hardware support detection. For information about the broader VM lifecycle and management, see Virtual Machine Implementation. For hardware abstraction interfaces beyond vCPUs, see Hardware Abstraction Layer.
Multi-Architecture Abstraction Layer
The vCPU architecture uses compile-time conditional compilation to select the appropriate backend for each target architecture. This design provides zero-cost abstraction while maintaining type safety across different hardware platforms.
Architecture Selection and Type Aliases
The core abstraction is implemented through type aliases that map to architecture-specific implementations based on the compilation target:
flowchart TD
subgraph subGraph3["AArch64 Backend"]
Aarch64VCpu["Aarch64VCpu"]
Aarch64PerCpu["Aarch64PerCpu"]
Aarch64VCpuCreateConfig["Aarch64VCpuCreateConfig"]
Aarch64HWSupport["ARM EL2 Support Check"]
end
subgraph subGraph2["RISC-V Backend"]
RISCVVCpu["RISCVVCpu"]
RISCVPerCpu["RISCVPerCpu"]
RISCVVCpuCreateConfig["RISCVVCpuCreateConfig"]
RISCVHWSupport["RISC-V H-Extension Check"]
end
subgraph subGraph1["x86_64 Backend"]
VmxArchVCpu["VmxArchVCpu"]
VmxArchPerCpuState["VmxArchPerCpuState"]
UnitConfig["() Unit Type"]
VmxHWSupport["VMX Hardware Check"]
end
subgraph subGraph0["Common Interface Types"]
AxArchVCpuImpl["AxArchVCpuImplMain vCPU Interface"]
AxVMArchPerCpuImpl["AxVMArchPerCpuImplPer-CPU State"]
AxVCpuCreateConfig["AxVCpuCreateConfigCreation Configuration"]
HasHWSupport["has_hardware_supportHardware Detection"]
end
AxArchVCpuImpl --> Aarch64VCpu
AxArchVCpuImpl --> RISCVVCpu
AxArchVCpuImpl --> VmxArchVCpu
AxVCpuCreateConfig --> Aarch64VCpuCreateConfig
AxVCpuCreateConfig --> RISCVVCpuCreateConfig
AxVCpuCreateConfig --> UnitConfig
AxVMArchPerCpuImpl --> Aarch64PerCpu
AxVMArchPerCpuImpl --> RISCVPerCpu
AxVMArchPerCpuImpl --> VmxArchPerCpuState
HasHWSupport --> Aarch64HWSupport
HasHWSupport --> RISCVHWSupport
HasHWSupport --> VmxHWSupport
Sources: src/vcpu.rs(L3 - L27)
Architecture-Specific Implementations
Each supported architecture provides its own implementation of the vCPU interface, tailored to the specific virtualization capabilities and requirements of that platform.
x86_64 Implementation
The x86_64 backend uses Intel VT-x and AMD-V virtualization extensions through the x86_vcpu crate. This implementation provides VMX-based virtualization with EPT (Extended Page Tables) support.
| Component | Type | Purpose |
|---|---|---|
| AxArchVCpuImpl | VmxArchVCpu | Main vCPU implementation using VMX |
| AxVMArchPerCpuImpl | VmxArchPerCpuState | Per-CPU VMX state management |
| AxVCpuCreateConfig | () | No additional configuration needed |
| Hardware Detection | has_hardware_support | VMX capability detection |
Sources: src/vcpu.rs(L4 - L8)
RISC-V Implementation
The RISC-V backend leverages the RISC-V hypervisor extension (H-extension) through the riscv_vcpu crate, providing virtualization support for RISC-V processors.
| Component | Type | Purpose |
|---|---|---|
| AxArchVCpuImpl | RISCVVCpu | H-extension based vCPU |
| AxVMArchPerCpuImpl | RISCVPerCpu | RISC-V per-CPU state |
| AxVCpuCreateConfig | RISCVVCpuCreateConfig | RISC-V specific configuration |
| Hardware Detection | has_hardware_support | H-extension capability check |
Sources: src/vcpu.rs(L16 - L20)
AArch64 Implementation
The AArch64 backend utilizes ARM virtualization extensions (EL2) through the arm_vcpu crate, enabling hypervisor functionality on ARM processors.
| Component | Type | Purpose |
|---|---|---|
| AxArchVCpuImpl | Aarch64VCpu | ARM EL2 based vCPU |
| AxVMArchPerCpuImpl | Aarch64PerCpu | ARM per-CPU state management |
| AxVCpuCreateConfig | Aarch64VCpuCreateConfig | ARM specific configuration |
| Hardware Detection | has_hardware_support | ARM virtualization support check |
Sources: src/vcpu.rs(L21 - L26)
vCPU Lifecycle and Integration
The vCPU architecture integrates with the broader axvm system through well-defined interfaces that handle creation, configuration, and execution of virtual CPUs.
vCPU Creation and Configuration Flow
sequenceDiagram
participant AxVM as "AxVM"
participant AxVCpuCreateConfig as "AxVCpuCreateConfig"
participant has_hardware_support as "has_hardware_support"
participant AxArchVCpuImpl as "AxArchVCpuImpl"
participant AxVMArchPerCpuImpl as "AxVMArchPerCpuImpl"
AxVM ->> has_hardware_support: "Check virtualization support"
has_hardware_support -->> AxVM: "Hardware capability result"
alt Hardware Supported
AxVM ->> AxVCpuCreateConfig: "Create vCPU configuration"
AxVM ->> AxArchVCpuImpl: "new(config)"
AxArchVCpuImpl ->> AxVMArchPerCpuImpl: "Initialize per-CPU state"
AxVMArchPerCpuImpl -->> AxArchVCpuImpl: "Per-CPU state ready"
AxArchVCpuImpl -->> AxVM: "vCPU instance created"
else Hardware Not Supported
AxVM ->> AxVM: "Return error or fallback"
end
Sources: src/vcpu.rs(L1 - L27)
Hardware Support Detection
Each architecture backend provides a has_hardware_support function that performs runtime detection of virtualization capabilities. This enables axvm to gracefully handle systems that may not have the necessary hardware extensions.
The hardware support detection is critical for:
- Determining if the host system can run the hypervisor
- Selecting appropriate virtualization features
- Providing meaningful error messages when virtualization is unavailable
Sources: src/vcpu.rs(L7 - L25)
Physical Frame Interface Requirements
The x86_64 implementation has a unique requirement where the PhysFrameIf trait must be implemented by the application layer. This design decision aligns with axvm's architecture principle of separating hypervisor functionality from OS resource management.
The PhysFrameIf implementation is handled by the vmm_app component rather than within axvm itself, allowing for flexible memory management strategies while maintaining clear separation of concerns.
Sources: src/vcpu.rs(L10 - L15)
Integration with VM Core
The vCPU architecture seamlessly integrates with the main AxVM implementation through the standardized interfaces, enabling the VM to manage virtual CPUs without needing architecture-specific knowledge. This abstraction allows the core VM logic to remain architecture-agnostic while leveraging the full capabilities of each supported platform.
Sources: src/vcpu.rs(L1 - L27)
Hardware Abstraction Layer
Relevant source files
The Hardware Abstraction Layer (HAL) provides a standardized interface for platform-specific operations that the axvm hypervisor requires from the underlying host system. This abstraction enables axvm to run on different host operating systems and hypervisors by delegating low-level hardware operations to implementations of the AxVMHal trait.
For information about virtual machine lifecycle management, see Virtual Machine Implementation. For details about multi-architecture CPU support, see Virtual CPU Architecture.
AxVMHal Trait Interface
The core of the hardware abstraction layer is the AxVMHal trait, which defines the contract between axvm and the underlying host system. This trait must be implemented by any host system that wants to run axvm virtual machines.
AxVMHal Trait Structure
classDiagram
class AxVMHal {
<<trait>>
+PagingHandler: page_table_multiarch::PagingHandler
+alloc_memory_region_at(base: HostPhysAddr, size: usize) bool
+dealloc_memory_region_at(base: HostPhysAddr, size: usize)
+virt_to_phys(vaddr: HostVirtAddr) HostPhysAddr
+current_time_nanos() u64
}
class PagingHandler {
<<interface>>
+page_table_multiarch::PagingHandler
}
class HostPhysAddr {
+axaddrspace::HostPhysAddr
}
class HostVirtAddr {
+axaddrspace::HostVirtAddr
}
AxVMHal --> PagingHandler : "associated type"
AxVMHal --> HostPhysAddr : "uses"
AxVMHal --> HostVirtAddr : "uses"
Sources: src/hal.rs(L1 - L22)
Memory Management Abstraction
The HAL provides two primary memory management operations that abstract the underlying host's memory allocation mechanisms:
| Method | Purpose | Return Value |
|---|---|---|
| alloc_memory_region_at | Allocates memory at a specific physical address | boolindicating success |
| dealloc_memory_region_at | Deallocates a previously allocated memory region | () |
These methods enable axvm to request specific physical memory regions from the host, which is essential for guest physical memory layout and device memory mapping.
Memory Management Flow
sequenceDiagram
participant AxVM as "AxVM"
participant AxVMHalImplementation as "AxVMHal Implementation"
participant HostSystem as "Host System"
AxVM ->> AxVMHalImplementation: "alloc_memory_region_at(base, size)"
AxVMHalImplementation ->> HostSystem: "Request physical memory allocation"
HostSystem -->> AxVMHalImplementation: "Success/failure"
AxVMHalImplementation -->> AxVM: "bool result"
Note over AxVM,HostSystem: "VM operation continues..."
AxVM ->> AxVMHalImplementation: "dealloc_memory_region_at(base, size)"
AxVMHalImplementation ->> HostSystem: "Release physical memory"
HostSystem -->> AxVMHalImplementation: "Memory freed"
AxVMHalImplementation -->> AxVM: "() completion"
Sources: src/hal.rs(L8 - L14)
Address Translation Interface
The virt_to_phys method provides virtual-to-physical address translation, enabling axvm to convert host virtual addresses to their corresponding physical addresses. This is crucial for setting up guest physical memory mappings and ensuring proper memory coherency.
Address Translation Architecture
flowchart TD
subgraph subGraph2["VM Memory Management"]
GuestPhys["Guest Physical Memory"]
HostMapping["Host Memory Mapping"]
end
subgraph subGraph1["Page Table Integration"]
PagingHandler["PagingHandler(page_table_multiarch)"]
PageTable["Multi-arch Page Tables"]
end
subgraph subGraph0["Address Translation Layer"]
VirtAddr["HostVirtAddr(axaddrspace)"]
VirtToPhys["virt_to_phys()"]
PhysAddr["HostPhysAddr(axaddrspace)"]
end
PagingHandler --> PageTable
PhysAddr --> GuestPhys
VirtAddr --> HostMapping
VirtAddr --> VirtToPhys
VirtToPhys --> PagingHandler
VirtToPhys --> PhysAddr
Sources: src/hal.rs(L16 - L17) src/hal.rs(L6)
Time Management
The HAL provides time services through the current_time_nanos method, which returns the current time in nanoseconds. This enables axvm to implement time-based features such as:
- Performance monitoring and profiling
- Timeout mechanisms for VM operations
- Guest time synchronization
- Event scheduling and timing
Sources: src/hal.rs(L19 - L20)
Integration with ArceOS Ecosystem
The HAL interfaces directly with several components from the ArceOS ecosystem:
HAL Ecosystem Integration
flowchart TD
subgraph subGraph3["Host Implementation"]
HostOS["Host OS/Hypervisor"]
MemMgmt["Memory Management"]
TimeService["Time Services"]
end
subgraph subGraph2["Page Table Infrastructure"]
PageTableMultiarch["page_table_multiarch"]
PagingHandlerImpl["PagingHandler impl"]
end
subgraph subGraph1["ArceOS Address Space"]
HostPhysAddr["HostPhysAddr"]
HostVirtAddr["HostVirtAddr"]
AddrSpace["axaddrspace"]
end
subgraph subGraph0["AxVM HAL Layer"]
AxVMHal["AxVMHal trait"]
PagingType["PagingHandler type"]
end
AddrSpace --> HostPhysAddr
AddrSpace --> HostVirtAddr
AxVMHal --> HostOS
AxVMHal --> HostPhysAddr
AxVMHal --> HostVirtAddr
MemMgmt --> AxVMHal
PagingHandlerImpl --> PagingType
PagingType --> PageTableMultiarch
TimeService --> AxVMHal
The HAL serves as the critical bridge between axvm's hardware-agnostic virtualization logic and the host system's specific capabilities, enabling portability across different host environments while maintaining performance and functionality.
Sources: src/hal.rs(L1 - L6)
Configuration System
Relevant source files
This document covers the AxVM hypervisor's configuration system, which manages VM setup parameters including CPU allocation, memory regions, device configurations, and image load addresses. The configuration system transforms TOML configuration files into structured data that drives VM creation and initialization.
For information about the VM creation process that uses these configurations, see Virtual Machine Implementation. For details about hardware abstraction interfaces, see Hardware Abstraction Layer.
Configuration Flow Overview
The configuration system follows a two-stage transformation process: external TOML configuration files are first parsed into raw configuration structures, then converted to internal VM configuration objects used during VM creation.
flowchart TD
subgraph subGraph1["Processed Configuration (axvm)"]
VMConfig["AxVMConfig"]
CPUConfig["cpu_config: AxVCpuConfig"]
ImageConfig["image_config: VMImageConfig"]
MemConfig["memory_regions: Vec"]
EmuDevices["emu_devices: Vec"]
PassDevices["pass_through_devices: Vec"]
end
subgraph subGraph0["Raw Configuration (axvmconfig crate)"]
CrateConfig["AxVMCrateConfig"]
BaseConfig["base: VM metadata"]
KernelConfig["kernel: Image addresses"]
DeviceConfig["devices: Device setup"]
end
TOML["TOML Configuration File"]
VMCreation["AxVM::new(config)"]
AddressSpace["Address Space Setup"]
VCPUSetup["vCPU Creation"]
DeviceSetup["Device Initialization"]
CrateConfig --> BaseConfig
CrateConfig --> DeviceConfig
CrateConfig --> KernelConfig
CrateConfig --> VMConfig
TOML --> CrateConfig
VMConfig --> CPUConfig
VMConfig --> EmuDevices
VMConfig --> ImageConfig
VMConfig --> MemConfig
VMConfig --> PassDevices
VMConfig --> VMCreation
VMCreation --> AddressSpace
VMCreation --> DeviceSetup
VMCreation --> VCPUSetup
Sources: src/config.rs(L1 - L12) src/config.rs(L63 - L87)
Configuration Structure Hierarchy
The configuration system defines several interconnected structures that represent different aspects of VM setup. The main configuration object AxVMConfig aggregates specialized configuration components.
classDiagram
class AxVMConfig {
-id: usize
-name: String
-vm_type: VMType
-cpu_num: usize
-phys_cpu_ids: Option~Vec~usize~~
-phys_cpu_sets: Option~Vec~usize~~
-cpu_config: AxVCpuConfig
-image_config: VMImageConfig
-memory_regions: Vec~VmMemConfig~
-emu_devices: Vec~EmulatedDeviceConfig~
-pass_through_devices: Vec~PassThroughDeviceConfig~
+id() usize
+name() String
+get_vcpu_affinities_pcpu_ids() Vec~(usize, Option~usize~, usize) ~
+bsp_entry() GuestPhysAddr
+ap_entry() GuestPhysAddr
+memory_regions() Vec~VmMemConfig~
+emu_devices() Vec~EmulatedDeviceConfig~
+pass_through_devices() Vec~PassThroughDeviceConfig~
}
class AxVCpuConfig {
+bsp_entry: GuestPhysAddr
+ap_entry: GuestPhysAddr
}
class VMImageConfig {
+kernel_load_gpa: GuestPhysAddr
+bios_load_gpa: Option~GuestPhysAddr~
+dtb_load_gpa: Option~GuestPhysAddr~
+ramdisk_load_gpa: Option~GuestPhysAddr~
}
class AxVMCrateConfig {
+base: BaseConfig
+kernel: KernelConfig
+devices: DeviceConfig
}
AxVMConfig *-- AxVCpuConfig
AxVMConfig *-- VMImageConfig
AxVMConfig ..> AxVMCrateConfig : "From trait"
Sources: src/config.rs(L24 - L31) src/config.rs(L34 - L44) src/config.rs(L47 - L61)
Configuration Transformation Process
The From<AxVMCrateConfig> trait implementation handles the conversion from raw TOML-derived configuration to the internal VM configuration format. This transformation maps external configuration fields to internal structures and converts address types to GuestPhysAddr.
Key Transformation Steps
| Source Field | Target Field | Transformation |
|---|---|---|
| cfg.base.id | id | Direct assignment |
| cfg.base.name | name | Direct assignment |
| cfg.base.cpu_num | cpu_num | Direct assignment |
| cfg.kernel.entry_point | cpu_config.bsp_entry | GuestPhysAddr::from() |
| cfg.kernel.kernel_load_addr | image_config.kernel_load_gpa | GuestPhysAddr::from() |
| cfg.kernel.bios_load_addr | image_config.bios_load_gpa | Option::map(GuestPhysAddr::from) |
| cfg.devices.emu_devices | emu_devices | Direct assignment |
Sources: src/config.rs(L63 - L87)
CPU Configuration
The CPU configuration system manages virtual CPU allocation, entry points, and physical CPU affinity mapping. The AxVCpuConfig structure defines entry addresses for both Bootstrap Processor (BSP) and Application Processor (AP) initialization.
vCPU Affinity Management
The get_vcpu_affinities_pcpu_ids method returns CPU mapping information as tuples containing:
- vCPU ID (0 to cpu_num-1)
- Physical CPU affinity mask (optional bitmap)
- Physical CPU ID (defaults to vCPU ID if not specified)
flowchart TD
subgraph subGraph0["CPU Configuration"]
CPUNum["cpu_num: usize"]
VCPUIDs["vCPU IDs: 0..cpu_num"]
PhysCPUIDs["phys_cpu_ids: Option>"]
PhysIDs["Physical IDs mapping"]
PhysCPUSets["phys_cpu_sets: Option>"]
AffinityMask["Affinity bitmasks"]
TupleGen["get_vcpu_affinities_pcpu_ids()"]
Result["Vec<(vcpu_id, affinity_mask, phys_id)>"]
end
AffinityMask --> TupleGen
CPUNum --> VCPUIDs
PhysCPUIDs --> PhysIDs
PhysCPUSets --> AffinityMask
PhysIDs --> TupleGen
TupleGen --> Result
VCPUIDs --> TupleGen
Sources: src/config.rs(L24 - L31) src/config.rs(L108 - L124)
Memory Configuration
Memory configuration defines the guest physical memory layout through a vector of VmMemConfig structures. Each memory region specifies mapping type, guest physical address ranges, and access permissions.
The VMImageConfig structure manages load addresses for different VM image components:
| Image Component | Configuration Field | Purpose |
|---|---|---|
| Kernel | kernel_load_gpa | Main kernel image load address |
| BIOS | bios_load_gpa | Optional BIOS/firmware load address |
| Device Tree | dtb_load_gpa | Optional device tree blob address |
| Ramdisk | ramdisk_load_gpa | Optional initial ramdisk address |
Sources: src/config.rs(L34 - L44) src/config.rs(L144 - L146)
Device Configuration
The device configuration system supports two types of devices: emulated devices and passthrough devices. Device configurations are stored in separate vectors and accessed through dedicated getter methods.
flowchart TD
subgraph subGraph1["External Types (axvmconfig)"]
EmulatedDeviceConfig["EmulatedDeviceConfig"]
PassThroughDeviceConfig["PassThroughDeviceConfig"]
end
subgraph subGraph0["Device Configuration"]
DeviceConfig["devices section (TOML)"]
EmuDevices["emu_devices: Vec"]
PassDevices["passthrough_devices: Vec"]
EmuGetter["emu_devices() getter"]
PassGetter["pass_through_devices() getter"]
VMSetup["VM Device Setup"]
end
DeviceConfig --> EmuDevices
DeviceConfig --> PassDevices
EmuDevices --> EmuGetter
EmuDevices --> EmulatedDeviceConfig
EmuGetter --> VMSetup
PassDevices --> PassGetter
PassDevices --> PassThroughDeviceConfig
PassGetter --> VMSetup
Sources: src/config.rs(L9 - L12) src/config.rs(L149 - L156)
Configuration Usage in VM Creation
The AxVMConfig object serves as the primary input to VM creation, providing all necessary parameters for initializing VM components. The configuration system exposes specific getter methods that VM creation logic uses to access different configuration aspects.
Configuration Access Methods
The configuration provides specialized accessor methods for different VM subsystems:
bsp_entry()/ap_entry(): CPU entry points for processor initializationimage_config(): Image load addresses for kernel and auxiliary componentsmemory_regions(): Memory layout and mapping configurationemu_devices()/pass_through_devices(): Device configuration for emulation and passthroughget_vcpu_affinities_pcpu_ids(): CPU affinity and physical mapping information
Sources: src/config.rs(L126 - L156)
Dependencies and Integration
Relevant source files
This document explains how the AxVM hypervisor integrates with the ArceOS ecosystem and manages its external dependencies. It covers the modular architecture that enables multi-platform virtualization support and the specific crate dependencies that provide core functionality.
For information about the core VM implementation details, see Virtual Machine Implementation. For architecture-specific virtualization backends, see Virtual CPU Architecture.
ArceOS Ecosystem Integration
AxVM is designed as a core component within the ArceOS hypervisor ecosystem, leveraging specialized crates for different aspects of virtualization. The integration follows a modular design where each major subsystem is provided by a dedicated crate.
Core Hypervisor Dependencies
The primary integration occurs through four key ArceOS-Hypervisor modules that provide the foundational virtualization capabilities:
flowchart TD AXVM["axvmCore Hypervisor"] AXVCPU["axvcpuVirtual CPU Management"] AXADDR["axaddrspaceAddress Space Management"] AXDEV["axdeviceDevice Emulation"] AXCONF["axvmconfigConfiguration Framework"] ARCH_VCPU["Architecture-SpecificvCPU Backends"] PAGETBL["page_table_multiarchMemory Translation"] MMIO["MMIO DeviceImplementations"] TOML["TOML ConfigurationParsing"] AXADDR --> PAGETBL AXCONF --> TOML AXDEV --> MMIO AXVCPU --> ARCH_VCPU AXVM --> AXADDR AXVM --> AXCONF AXVM --> AXDEV AXVM --> AXVCPU
Sources: Cargo.toml(L24 - L27)
Each ArceOS-Hypervisor module serves a specific role in the virtualization stack:
| Crate | Repository | Purpose |
|---|---|---|
| axvcpu | github.com/arceos-hypervisor/axvcpu.git | Provides architecture-agnostic vCPU abstraction and management |
| axaddrspace | github.com/arceos-hypervisor/axaddrspace.git | Handles guest address space management and memory translation |
| axdevice | github.com/arceos-hypervisor/axdevice.git | Implements device emulation and MMIO handling |
| axvmconfig | github.com/arceos-hypervisor/axvmconfig.git | Provides VM configuration parsing and validation |
Sources: Cargo.toml(L24 - L27)
Architecture-Specific Dependencies
AxVM supports multiple hardware architectures through conditional compilation and architecture-specific vCPU backends. This design allows the same codebase to provide native virtualization support across different platforms.
Multi-Architecture Dependency Resolution
flowchart TD
subgraph subGraph1["Architecture Backends"]
X86_VCPU["x86_vcpuIntel VT-x/AMD-V"]
RISCV_VCPU["riscv_vcpuRISC-V H-Extension"]
ARM_VCPU["arm_vcpuARM EL2"]
end
subgraph subGraph0["Target Architecture Selection"]
X86["target_arch = x86_64"]
RISCV["target_arch = riscv64"]
ARM["target_arch = aarch64"]
end
CONFIG["cfg-ifConditional Compilation"]
VTX["VT-x/SVMHardware Features"]
HEXT["HypervisorExtension"]
EL2["Exception Level 2Virtualization"]
ARM --> ARM_VCPU
ARM_VCPU --> EL2
CONFIG --> ARM
CONFIG --> RISCV
CONFIG --> X86
RISCV --> RISCV_VCPU
RISCV_VCPU --> HEXT
X86 --> X86_VCPU
X86_VCPU --> VTX
Sources: Cargo.toml(L29 - L36) Cargo.toml(L12)
The architecture-specific dependencies are resolved at compile time:
| Target Architecture | Crate | Hardware Support |
|---|---|---|
| x86_64 | x86_vcpu | Intel VT-x, AMD-V |
| riscv64 | riscv_vcpu | RISC-V Hypervisor Extension |
| aarch64 | arm_vcpu | ARM Virtualization Extensions (EL2) |
Sources: Cargo.toml(L29 - L36)
System Infrastructure Dependencies
AxVM relies on several ArceOS system-independent crates that provide low-level infrastructure for hypervisor operations.
Memory Management Infrastructure
flowchart TD MEMORY_MGMT["Memory ManagementInfrastructure"] MEMORY_ADDR["memory_addrAddress Abstractions"] PAGE_ENTRY["page_table_entryPage Table Entries"] PAGE_MULTI["page_table_multiarchMulti-arch Page Tables"] PERCPU["percpuPer-CPU Data Storage"] PHYS_ADDR["PhysAddrGuestPhysAddr"] VIRT_ADDR["VirtAddrGuestVirtAddr"] ARM_EL2["ARM EL2Page Entries"] EPT["Extended Page Tables"] NPT["Nested Page Tables"] CPU_STATE["Per-CPU VM State"] VCPU_DATA["vCPU Local Data"] MEMORY_ADDR --> PHYS_ADDR MEMORY_ADDR --> VIRT_ADDR MEMORY_MGMT --> MEMORY_ADDR MEMORY_MGMT --> PAGE_ENTRY MEMORY_MGMT --> PAGE_MULTI MEMORY_MGMT --> PERCPU PAGE_ENTRY --> ARM_EL2 PAGE_MULTI --> EPT PAGE_MULTI --> NPT PERCPU --> CPU_STATE PERCPU --> VCPU_DATA
Sources: Cargo.toml(L18 - L21)
Memory Management Crate Details
| Crate | Version | Features | Purpose |
|---|---|---|---|
| memory_addr | 0.3.1 | - | ProvidesPhysAddr,VirtAddr, and guest address types |
| page_table_entry | 0.5 | arm-el2 | ARM EL2-specific page table entry implementations |
| page_table_multiarch | 0.5 | - | Cross-architecture page table management |
| percpu | 0.2.0 | arm-el2 | Per-CPU data storage with ARM EL2 support |
Sources: Cargo.toml(L18 - L21)
Utility and System Dependencies
AxVM uses several external utility crates for core system functionality including error handling, logging, and synchronization.
External Utility Integration
flowchart TD UTILITIES["System Utilities"] LOG["log v0.4Logging Framework"] SPIN["spin v0.9Synchronization"] CFGIF["cfg-if v1.0Conditional Compilation"] AXERRNO["axerrno v0.1.0Error Handling"] LOG_IMPL["VM Event LoggingDebug Information"] LOCKS["SpinlocksMutex Primitives"] ARCH_SELECT["Architecture SelectionFeature Gates"] ERROR_CODES["Hypervisor Error CodesResult Types"] AXERRNO --> ERROR_CODES CFGIF --> ARCH_SELECT LOG --> LOG_IMPL SPIN --> LOCKS UTILITIES --> AXERRNO UTILITIES --> CFGIF UTILITIES --> LOG UTILITIES --> SPIN
Sources: Cargo.toml(L11 - L16)
Utility Dependency Functions
| Crate | Version | Usage in AxVM |
|---|---|---|
| log | 0.4 | VM event logging, debug output, error reporting |
| spin | 0.9 | Spinlock-based synchronization for VM state |
| cfg-if | 1.0 | Conditional compilation for architecture support |
| axerrno | 0.1.0 | ArceOS-compatible error handling and result types |
Sources: Cargo.toml(L11 - L16)
Feature Configuration
AxVM uses Cargo features to enable optional functionality and architecture-specific optimizations.
Default Feature Set
The hypervisor defines a minimal set of default features focused on x86_64 virtualization:
[features]
default = ["vmx"]
vmx = []
Sources: Cargo.toml(L6 - L8)
The vmx feature enables Intel VT-x and AMD-V support on x86_64 platforms, representing the most common virtualization use case.
Integration Patterns
AxVM follows specific integration patterns that enable clean separation of concerns while maintaining performance and flexibility.
Repository Integration Model
flowchart TD GITHUB["GitHub Organizationarceos-hypervisor"] AXVM_REPO["axvmCore Hypervisor"] AXVCPU_REPO["axvcpuvCPU Abstraction"] AXADDR_REPO["axaddrspaceMemory Management"] AXDEV_REPO["axdeviceDevice Emulation"] AXCONF_REPO["axvmconfigConfiguration"] X86_REPO["x86_vcpux86 Backend"] RISCV_REPO["riscv_vcpuRISC-V Backend"] ARM_REPO["arm_vcpuARM Backend"] AXVCPU_REPO --> ARM_REPO AXVCPU_REPO --> RISCV_REPO AXVCPU_REPO --> X86_REPO AXVM_REPO --> AXADDR_REPO AXVM_REPO --> AXCONF_REPO AXVM_REPO --> AXDEV_REPO AXVM_REPO --> AXVCPU_REPO GITHUB --> ARM_REPO GITHUB --> AXADDR_REPO GITHUB --> AXCONF_REPO GITHUB --> AXDEV_REPO GITHUB --> AXVCPU_REPO GITHUB --> AXVM_REPO GITHUB --> RISCV_REPO GITHUB --> X86_REPO
Sources: Cargo.toml(L24 - L36)
All ArceOS-Hypervisor components are developed in separate repositories under the arceos-hypervisor GitHub organization, enabling independent development cycles while maintaining API compatibility through semantic versioning.
Development Guide
Relevant source files
This document provides an overview of development practices and guidelines for contributors to the AxVM hypervisor project. It covers the build system, testing procedures, contribution workflow, and project governance aspects needed for effective development and collaboration.
For detailed information about specific development processes, see Build and Testing. For legal requirements and contribution guidelines, see License and Legal.
Development Workflow Overview
The AxVM project follows a standard GitHub-based development workflow with continuous integration and automated testing across multiple architectures. The development process emphasizes code quality, documentation completeness, and cross-platform compatibility.
Development Process Flow
flowchart TD Developer["Developer"] Fork["Fork Repository"] Clone["Clone Fork"] Branch["Create Feature Branch"] Code["Write Code"] LocalTest["Local Testing"] Commit["Commit Changes"] Push["Push to Fork"] PR["Create Pull Request"] CITrigger["CI Pipeline Triggered"] FormatCheck["Format Check (cargo fmt)"] ClippyCheck["Clippy Linting"] BuildMatrix["Multi-Architecture Build"] UnitTests["Unit Tests (x86_64 only)"] DocBuild["Documentation Build"] CIResult["CI Results"] CodeReview["Code Review"] FixIssues["Fix Issues"] Approved["Approved?"] Merge["Merge to Main"] AddressComments["Address Comments"] DocDeploy["Deploy Documentation"] AddressComments --> Code Approved --> AddressComments Approved --> Merge Branch --> Code BuildMatrix --> CIResult CIResult --> CodeReview CIResult --> FixIssues CITrigger --> BuildMatrix CITrigger --> ClippyCheck CITrigger --> DocBuild CITrigger --> FormatCheck CITrigger --> UnitTests ClippyCheck --> CIResult Clone --> Branch Code --> LocalTest CodeReview --> Approved Commit --> Push Developer --> Fork DocBuild --> CIResult FixIssues --> Code Fork --> Clone FormatCheck --> CIResult LocalTest --> Commit Merge --> DocDeploy PR --> CITrigger Push --> PR UnitTests --> CIResult
Sources: .github/workflows/ci.yml(L1 - L61)
Build System and Architecture Support
The project uses a comprehensive build matrix to ensure compatibility across multiple Rust toolchains and target architectures. This approach guarantees that AxVM functions correctly on all supported platforms.
Supported Build Targets
| Architecture | Target Triple | Use Case |
|---|---|---|
| x86_64 | x86_64-unknown-linux-gnu | Development and testing |
| x86_64 | x86_64-unknown-none | Bare-metal hypervisor |
| RISC-V | riscv64gc-unknown-none-elf | RISC-V bare-metal |
| AArch64 | aarch64-unknown-none-softfloat | ARM bare-metal |
Build Matrix Configuration
flowchart TD CI["CI Pipeline"] Matrix["Build Matrix"] ToolchainA["nightly-2024-12-25"] ToolchainB["nightly"] TargetA1["x86_64-unknown-linux-gnu"] TargetA2["x86_64-unknown-none"] TargetA3["riscv64gc-unknown-none-elf"] TargetA4["aarch64-unknown-none-softfloat"] TargetB1["x86_64-unknown-linux-gnu"] TargetB2["x86_64-unknown-none"] TargetB3["riscv64gc-unknown-none-elf"] TargetB4["aarch64-unknown-none-softfloat"] BuildStepsA1["Format → Clippy → Build → Test"] BuildStepsA2["Format → Clippy → Build"] BuildStepsA3["Format → Clippy → Build"] BuildStepsA4["Format → Clippy → Build"] BuildStepsB1["Format → Clippy → Build → Test"] BuildStepsB2["Format → Clippy → Build"] BuildStepsB3["Format → Clippy → Build"] BuildStepsB4["Format → Clippy → Build"] CI --> Matrix Matrix --> ToolchainA Matrix --> ToolchainB TargetA1 --> BuildStepsA1 TargetA2 --> BuildStepsA2 TargetA3 --> BuildStepsA3 TargetA4 --> BuildStepsA4 TargetB1 --> BuildStepsB1 TargetB2 --> BuildStepsB2 TargetB3 --> BuildStepsB3 TargetB4 --> BuildStepsB4 ToolchainA --> TargetA1 ToolchainA --> TargetA2 ToolchainA --> TargetA3 ToolchainA --> TargetA4 ToolchainB --> TargetB1 ToolchainB --> TargetB2 ToolchainB --> TargetB3 ToolchainB --> TargetB4
Sources: .github/workflows/ci.yml(L8 - L33)
Continuous Integration Pipeline
The CI system performs comprehensive validation of all changes through automated checks and builds. The pipeline includes code quality enforcement, multi-architecture compilation, and automated documentation generation.
CI Job Structure
flowchart TD
subgraph subGraph2["Doc Job Steps"]
DocCheckout["Checkout Code"]
DocRustSetup["Setup Rust Toolchain"]
DocBuild["cargo doc --no-deps"]
IndexGen["Generate Index HTML"]
PagesDeploy["Deploy to GitHub Pages"]
end
subgraph subGraph1["CI Job Steps"]
Checkout["Checkout Code"]
RustSetup["Setup Rust Toolchain"]
VersionCheck["Check Rust Version"]
FormatCheck["cargo fmt --check"]
ClippyRun["cargo clippy"]
BuildRun["cargo build"]
TestRun["cargo test (x86_64 only)"]
end
subgraph subGraph0["CI Jobs"]
CIJob["ci job"]
DocJob["doc job"]
end
BuildRun --> TestRun
CIJob --> Checkout
Checkout --> RustSetup
ClippyRun --> BuildRun
DocBuild --> IndexGen
DocCheckout --> DocRustSetup
DocJob --> DocCheckout
DocRustSetup --> DocBuild
FormatCheck --> ClippyRun
IndexGen --> PagesDeploy
RustSetup --> VersionCheck
VersionCheck --> FormatCheck
Sources: .github/workflows/ci.yml(L6 - L34) .github/workflows/ci.yml(L35 - L61)
Code Quality Standards
The project enforces strict code quality standards through automated tooling and manual review processes.
Automated Quality Checks
- Code Formatting: Enforced via
cargo fmt --all -- --check.github/workflows/ci.yml(L23) - Linting: Performed using
cargo clippywith custom configuration .github/workflows/ci.yml(L24 - L26) - Documentation: Required for all public APIs with broken link detection .github/workflows/ci.yml(L43)
Quality Enforcement Configuration
| Tool | Configuration | Purpose |
|---|---|---|
| clippy | -A clippy::new_without_default | Allow structs without Default trait |
| rustdoc | -D rustdoc::broken_intra_doc_links | Fail on broken documentation links |
| rustdoc | -D missing-docs | Require documentation for public items |
Sources: .github/workflows/ci.yml(L26) .github/workflows/ci.yml(L43)
Documentation System
AxVM maintains comprehensive documentation that is automatically built and deployed through the CI system. The documentation is generated using Rust's built-in documentation tools and hosted on GitHub Pages.
Documentation Build Process
sequenceDiagram
participant Developer as "Developer"
participant PullRequest as "Pull Request"
participant CISystem as "CI System"
participant rustdoc as "rustdoc"
participant GitHubPages as "GitHub Pages"
Developer ->> PullRequest: Submit Changes
PullRequest ->> CISystem: Trigger Doc Job
CISystem ->> rustdoc: cargo doc --no-deps --all-features
rustdoc ->> CISystem: Generate Documentation
CISystem ->> CISystem: Create Index Redirect
Note over CISystem: Generate redirect to main crate
CISystem ->> GitHubPages: Deploy Documentation
Note over GitHubPages: Only on main branch
The documentation system generates API documentation for all public interfaces and automatically redirects to the main crate documentation. Documentation deployment only occurs for changes merged to the default branch.
Sources: .github/workflows/ci.yml(L49 - L60)
Testing Strategy
The project implements a multi-layered testing approach with unit tests executed in the CI pipeline. Testing is currently limited to the x86_64-unknown-linux-gnu target due to the nature of bare-metal hypervisor code.
Test Execution Framework
- Unit Tests: Run via
cargo test --target x86_64-unknown-linux-gnu -- --nocapture.github/workflows/ci.yml(L31 - L33) - Build Verification: All target architectures are compiled to ensure compatibility .github/workflows/ci.yml(L28 - L29)
- Integration Testing: Performed through successful compilation across architectures
Sources: .github/workflows/ci.yml(L30 - L33)
Contributing Guidelines
Contributions to AxVM are governed by the Apache 2.0 license terms. All contributors must ensure their submissions comply with the project's quality standards and legal requirements.
Contribution Requirements
- Code Quality: All submissions must pass automated format, lint, and build checks
- Documentation: Public APIs must include comprehensive documentation
- Testing: Changes should include appropriate test coverage where applicable
- Legal Compliance: Contributions must be compatible with Apache 2.0 licensing
For detailed legal information and license terms, see License and Legal.
Sources: LICENSE.Apache2(L1 - L202)
Build and Testing
Relevant source files
This document covers the automated build system, continuous integration pipeline, testing procedures, and documentation generation for the AxVM hypervisor project. It explains the multi-architecture build matrix, CI/CD workflow stages, and quality assurance processes that ensure code reliability across supported platforms.
For information about development dependencies and integration with the ArceOS ecosystem, see Dependencies and Integration.
Build System Overview
The AxVM project uses a GitHub Actions-based continuous integration system that validates code quality, builds across multiple architectures, and generates documentation. The build system supports three primary target architectures (x86_64, RISC-V, and AArch64) with different execution environments ranging from hosted Linux to bare-metal embedded targets.
CI Pipeline Architecture
flowchart TD
subgraph subGraph3["Documentation Pipeline"]
doc_build["cargo doc --no-deps"]
index_redirect["index.html redirect"]
gh_pages_deploy["GitHub Pages deploy"]
end
subgraph subGraph2["Build Stages"]
format_check["cargo fmt --check"]
clippy_lint["cargo clippy"]
cargo_build["cargo build"]
unit_tests["cargo test"]
end
subgraph subGraph1["CI Job Matrix"]
matrix_strategy["strategy.matrix"]
toolchain_variants["rust-toolchain variants"]
target_variants["targets variants"]
nightly_2024["nightly-2024-12-25"]
nightly_latest["nightly"]
x86_linux["x86_64-unknown-linux-gnu"]
x86_none["x86_64-unknown-none"]
riscv_none["riscv64gc-unknown-none-elf"]
aarch64_none["aarch64-unknown-none-softfloat"]
end
subgraph subGraph0["GitHub Actions Workflow"]
trigger["push/pull_request triggers"]
ci_job["ci job"]
doc_job["doc job"]
end
cargo_build --> unit_tests
ci_job --> format_check
ci_job --> matrix_strategy
clippy_lint --> cargo_build
doc_build --> index_redirect
doc_job --> doc_build
format_check --> clippy_lint
index_redirect --> gh_pages_deploy
matrix_strategy --> target_variants
matrix_strategy --> toolchain_variants
target_variants --> aarch64_none
target_variants --> riscv_none
target_variants --> x86_linux
target_variants --> x86_none
toolchain_variants --> nightly_2024
toolchain_variants --> nightly_latest
trigger --> ci_job
trigger --> doc_job
Sources: .github/workflows/ci.yml(L1 - L61)
Supported Architecture Targets
The build system validates AxVM across multiple target architectures that align with the hypervisor's multi-architecture support design. Each target represents a different execution environment and use case.
| Target Triple | Architecture | Environment | Use Case |
|---|---|---|---|
| x86_64-unknown-linux-gnu | x86_64 | Hosted Linux | Development, unit testing |
| x86_64-unknown-none | x86_64 | Bare metal | Hypervisor deployment |
| riscv64gc-unknown-none-elf | RISC-V 64-bit | Bare metal ELF | RISC-V hypervisor |
| aarch64-unknown-none-softfloat | AArch64 | Bare metal, soft FP | ARM hypervisor |
Build Matrix Configuration
flowchart TD
subgraph Components["Components"]
rust_src["rust-src"]
clippy_comp["clippy"]
rustfmt_comp["rustfmt"]
end
subgraph subGraph1["Target Matrix"]
x86_linux["x86_64-unknown-linux-gnu"]
x86_bare["x86_64-unknown-none"]
riscv_bare["riscv64gc-unknown-none-elf"]
arm_bare["aarch64-unknown-none-softfloat"]
end
subgraph subGraph0["Toolchain Matrix"]
stable_toolchain["nightly-2024-12-25"]
latest_toolchain["nightly"]
end
latest_toolchain --> arm_bare
latest_toolchain --> riscv_bare
latest_toolchain --> x86_bare
latest_toolchain --> x86_linux
stable_toolchain --> arm_bare
stable_toolchain --> clippy_comp
stable_toolchain --> riscv_bare
stable_toolchain --> rust_src
stable_toolchain --> rustfmt_comp
stable_toolchain --> x86_bare
stable_toolchain --> x86_linux
Sources: .github/workflows/ci.yml(L10 - L19)
CI Pipeline Stages
The continuous integration pipeline consists of multiple validation stages that run sequentially for each target in the build matrix. The pipeline uses a fail-fast: false strategy to ensure all combinations are tested even if some fail.
Code Quality Validation
The first stage validates code formatting and style consistency:
sequenceDiagram
participant GitHubActions as "GitHub Actions"
participant RustToolchain as "Rust Toolchain"
participant cargofmt as "cargo fmt"
participant cargoclippy as "cargo clippy"
GitHubActions ->> RustToolchain: "Setup nightly toolchain"
GitHubActions ->> RustToolchain: "Install components: rust-src, clippy, rustfmt"
GitHubActions ->> RustToolchain: "Add target: ${{ matrix.targets }}"
GitHubActions ->> cargofmt: "cargo fmt --all -- --check"
cargofmt ->> GitHubActions: "Format validation result"
GitHubActions ->> cargoclippy: "cargo clippy --target ${{ matrix.targets }} --all-features"
Note over cargoclippy: "Allows clippy::new_without_default"
cargoclippy ->> GitHubActions: "Lint validation result"
The clippy stage includes specific configuration to suppress the new_without_default warning, allowing constructors without Default implementation.
Sources: .github/workflows/ci.yml(L22 - L26)
Build Validation
Each target architecture undergoes compilation validation with full feature enablement:
cargo build --target ${{ matrix.targets }} --all-features
The build stage uses continue-on-error for the latest nightly toolchain to prevent pipeline failures from upstream Rust changes while maintaining stability with the pinned nightly version.
Sources: .github/workflows/ci.yml(L27 - L29)
Unit Testing
Unit tests execute only on the x86_64-unknown-linux-gnu target, which provides the hosted environment necessary for test execution:
cargo test --target x86_64-unknown-linux-gnu -- --nocapture
This restriction reflects the practical limitation that bare-metal targets cannot execute standard Rust test harnesses without additional infrastructure.
Sources: .github/workflows/ci.yml(L30 - L33)
Documentation Generation
The documentation pipeline operates independently from the main CI job and includes automated deployment to GitHub Pages for the default branch.
Documentation Build Process
flowchart TD
subgraph subGraph2["Deployment Conditions"]
default_branch_check["github.ref == default-branch"]
single_commit["single-commit: true"]
target_folder["folder: target/doc"]
end
subgraph subGraph1["RUSTDOCFLAGS Environment"]
broken_links["-D rustdoc::broken_intra_doc_links"]
missing_docs["-D missing-docs"]
end
subgraph subGraph0["Documentation Job"]
doc_trigger["GitHub trigger"]
doc_checkout["Checkout repository"]
doc_toolchain["Setup nightly-2024-12-25"]
doc_build["cargo doc --no-deps --all-features"]
doc_index["Generate index.html redirect"]
doc_deploy["Deploy to gh-pages branch"]
end
broken_links --> doc_build
default_branch_check --> doc_deploy
doc_build --> doc_index
doc_checkout --> doc_toolchain
doc_index --> doc_deploy
doc_toolchain --> doc_build
doc_trigger --> doc_checkout
missing_docs --> doc_build
single_commit --> doc_deploy
target_folder --> doc_deploy
The documentation build enforces strict documentation standards through RUSTDOCFLAGS that treat broken intra-doc links and missing documentation as compilation errors.
Sources: .github/workflows/ci.yml(L35 - L61)
Index Page Generation
The pipeline automatically generates a redirect index page that points to the main crate documentation:
printf '<meta http-equiv="refresh" content="0;url=%s/index.html">' $(cargo tree | head -1 | cut -d' ' -f1) > target/doc/index.html
This command extracts the crate name from cargo tree output and creates an HTML redirect to the appropriate documentation entry point.
Sources: .github/workflows/ci.yml(L52 - L53)
Error Handling and Resilience
The CI configuration includes several resilience mechanisms to handle different failure scenarios:
| Condition | Behavior | Purpose |
|---|---|---|
| continue-on-error: ${{ matrix.rust-toolchain == 'nightly' }} | Allow latest nightly failures | Prevent upstream breakage |
| fail-fast: false | Continue other matrix jobs on failure | Complete validation coverage |
| Documentation error handling | Allow non-default branch doc failures | Prevent blocking on doc issues |
The pipeline distinguishes between the stable nightly toolchain (nightly-2024-12-25) and the latest nightly, allowing experimentation with newer compiler versions while maintaining build stability.
Sources: .github/workflows/ci.yml(L8 - L9) .github/workflows/ci.yml(L25) .github/workflows/ci.yml(L28) .github/workflows/ci.yml(L32) .github/workflows/ci.yml(L50)
Quality Assurance Standards
The CI pipeline enforces several quality standards across the codebase:
- Code Formatting: Uniform formatting via
rustfmtwith--checkflag - Linting: Comprehensive linting through
clippywith minimal suppressions - Documentation: Complete documentation coverage with broken link detection
- Multi-architecture Compatibility: Build validation across all supported targets
- Feature Completeness: All builds use
--all-featuresto ensure complete validation
These standards ensure that AxVM maintains high code quality and reliability across its supported platforms and use cases.
Sources: .github/workflows/ci.yml(L22 - L33) .github/workflows/ci.yml(L43) .github/workflows/ci.yml(L49 - L53)
License and Legal
Relevant source files
This document provides comprehensive legal information for the AxVM hypervisor project, including licensing terms, contribution guidelines, and intellectual property considerations. This information is essential for developers, contributors, and users who need to understand the legal framework governing the use, modification, and distribution of the AxVM codebase.
For build and testing procedures, see Build and Testing. For general development information, see Development Guide.
License Overview
The AxVM hypervisor is licensed under the Apache License 2.0, a permissive open-source license that allows for both commercial and non-commercial use while providing patent protection and clear contribution terms.
License Structure and Components
flowchart TD
subgraph subGraph3["Protection Mechanisms"]
Warranty["Warranty DisclaimerSection 7"]
Liability["Liability LimitationSection 8"]
Trademark["Trademark ProtectionSection 6"]
PatentTerm["Patent TerminationSection 3"]
end
subgraph subGraph2["Key Obligations"]
Attribution["Attribution RequirementsSection 4(c)"]
Notice["License Notice InclusionSection 4(a)"]
Changes["Change DocumentationSection 4(b)"]
NoticeTxt["NOTICE File HandlingSection 4(d)"]
end
subgraph subGraph1["Key Rights Granted"]
Copyright["Copyright LicenseSection 2"]
Patent["Patent LicenseSection 3"]
Redistribute["Redistribution RightsSection 4"]
Modify["Modification RightsSection 4"]
end
subgraph subGraph0["Apache License 2.0 Framework"]
License["LICENSE.Apache2"]
Terms["License Terms"]
Grants["Rights Granted"]
Conditions["Conditions & Obligations"]
Limitations["Limitations & Disclaimers"]
end
Conditions --> Attribution
Conditions --> Changes
Conditions --> Notice
Conditions --> NoticeTxt
Grants --> Copyright
Grants --> Modify
Grants --> Patent
Grants --> Redistribute
License --> Terms
Limitations --> Liability
Limitations --> PatentTerm
Limitations --> Trademark
Limitations --> Warranty
Terms --> Conditions
Terms --> Grants
Terms --> Limitations
Sources: LICENSE.Apache2(L1 - L202)
License Terms and Conditions
Copyright and Patent Grants
The Apache License 2.0 provides comprehensive rights to users of the AxVM hypervisor:
| Right Type | Scope | Section Reference |
|---|---|---|
| Copyright License | Perpetual, worldwide, non-exclusive, royalty-free | LICENSE.Apache266-71 |
| Patent License | Covers patent claims necessarily infringed by contributions | LICENSE.Apache274-87 |
| Distribution | Source and Object form distribution permitted | LICENSE.Apache289-128 |
| Modification | Right to create and distribute derivative works | LICENSE.Apache240-46 |
Key Definitions
The license establishes several critical definitions that govern how the AxVM codebase can be used:
flowchart TD
subgraph subGraph2["Legal Relationships"]
OwnershipChain["Copyright Ownership"]
SubmissionFlow["Contribution Submission"]
LicenseGrant["License Grant Flow"]
end
subgraph subGraph1["Work Classifications"]
Source["Source Form(.rs files, configs)"]
Object["Object Form(compiled binaries)"]
Derivative["Derivative Works(based on AxVM)"]
Contribution["Contribution(submitted changes)"]
end
subgraph subGraph0["Core Legal Entities"]
Work["Work(AxVM Source Code)"]
Contributor["Contributor(Code Authors)"]
Licensee["You(License Recipients)"]
Licensor["Licensor(Copyright Holders)"]
end
Contribution --> Work
Contributor --> Contribution
Contributor --> OwnershipChain
LicenseGrant --> Licensee
Licensor --> LicenseGrant
OwnershipChain --> Licensor
Work --> Derivative
Work --> Object
Work --> Source
Sources: LICENSE.Apache2(L8 - L64)
Redistribution Requirements
When redistributing AxVM or derivative works, the following obligations must be met:
Mandatory Inclusions
- License Copy: Include a copy of the Apache License 2.0 with any distribution
- Attribution Notices: Retain all copyright, patent, trademark, and attribution notices
- Change Documentation: Mark any modified files with prominent notices
- NOTICE File: If present, include readable copy of attribution notices
Source Code Distribution
flowchart TD
subgraph subGraph0["Required Components"]
LicenseCopy["LICENSE.Apache2Full license text"]
CopyrightNotices["Copyright NoticesIn source headers"]
ChangeMarkers["Change MarkersModified file notices"]
NoticeFile["NOTICE FileAttribution notices"]
end
OrigSource["Original AxVM Source"]
ModSource["Modified Source"]
Distribution["Source Distribution"]
Distribution --> CopyrightNotices
Distribution --> LicenseCopy
Distribution --> NoticeFile
ModSource --> ChangeMarkers
ModSource --> Distribution
OrigSource --> Distribution
Sources: LICENSE.Apache2(L89 - L121)
Contribution Guidelines
Contribution License Terms
All contributions to AxVM are subject to the Apache License 2.0 terms:
| Aspect | Requirement | Legal Basis |
|---|---|---|
| License Agreement | Contributions under Apache 2.0 terms | LICENSE.Apache2130-136 |
| Copyright Assignment | No explicit assignment required | Implied by license grant |
| Patent Grant | Automatic patent license for contributions | LICENSE.Apache274-87 |
| Warranty Disclaimer | No warranties provided by contributors | LICENSE.Apache2143-151 |
Submission Process
sequenceDiagram
participant Developer as Developer
participant AxVMRepository as "AxVM Repository"
participant Maintainer as Maintainer
participant Apache20Framework as "Apache 2.0 Framework"
Developer ->> Developer: "Create contribution"
Developer ->> Apache20Framework: "Implicit agreement to license terms"
Developer ->> AxVMRepository: "Submit pull request/patch"
Note over AxVMRepository: "Contribution becomes subject to<br>Apache 2.0 Section 5"
AxVMRepository ->> Maintainer: "Review contribution"
Maintainer ->> AxVMRepository: "Accept/merge contribution"
AxVMRepository ->> Apache20Framework: "Contribution incorporated under Apache 2.0"
Note over Apache20Framework: "Patent license automatically granted<br>Copyright license automatically granted"
Sources: LICENSE.Apache2(L48 - L60) LICENSE.Apache2(L130 - L136)
Intellectual Property Considerations
Patent Protection
The Apache License 2.0 provides specific patent protections:
- Automatic Patent License: Contributors automatically grant patent licenses for their contributions
- Patent Termination Clause: Patent licenses terminate if litigation is filed against the project
- Defensive Termination: Protects against patent trolling and frivolous litigation
Trademark Restrictions
The license explicitly does not grant trademark rights:
flowchart TD
subgraph subGraph1["Permitted Uses"]
ReasonableUse["Reasonable and Customary UseOrigin description"]
NoticeRepro["NOTICE File Reproduction"]
end
subgraph subGraph0["Trademark Scope"]
ProjectName["AxVM Project Name"]
ArceOSBrand["ArceOS Branding"]
LogosMarks["Logos and Marks"]
ServiceMarks["Service Marks"]
end
subgraph subGraph2["Prohibited Uses"]
CommercialBrand["Commercial Branding"]
Endorsement["Implied Endorsement"]
Confusion["Likelihood of Confusion"]
end
Prohibited["Prohibited"]
ArceOSBrand --> NoticeRepro
LogosMarks --> Prohibited
ProjectName --> ReasonableUse
ServiceMarks --> Prohibited
Sources: LICENSE.Apache2(L138 - L141)
Compliance and Attribution
License Compliance Checklist
For users and distributors of AxVM:
| Requirement | Binary Distribution | Source Distribution | Derivative Works |
|---|---|---|---|
| Include License | ✓ Required | ✓ Required | ✓ Required |
| Copyright Notices | ✓ Required | ✓ Required | ✓ Required |
| NOTICE File | ✓ If exists | ✓ If exists | ✓ If exists |
| Change Documentation | ✗ Not required | ✓ Required | ✓ Required |
| Patent License | ✓ Automatic | ✓ Automatic | ✓ Automatic |
Attribution Requirements
flowchart TD
subgraph subGraph1["Distribution Attribution"]
LicenseFile["LICENSE.Apache2Full license text"]
NoticeInclude["NOTICE FileThird-party attributions"]
end
subgraph subGraph0["Source Attribution"]
SourceHeaders["Source File HeadersCopyright notices"]
LicenseRef["License ReferencesSPDX identifiers"]
end
subgraph subGraph2["Modification Attribution"]
ChangeLog["Change DocumentationModified file markers"]
AuthorInfo["Author InformationContributor attribution"]
end
ChangeLog --> AuthorInfo
LicenseRef --> NoticeInclude
SourceHeaders --> LicenseFile
Sources: LICENSE.Apache2(L94 - L120)
Disclaimer and Limitation of Liability
Warranty Disclaimer
The AxVM hypervisor is provided "AS IS" without warranties of any kind. This includes:
- No warranty of merchantability or fitness for a particular purpose
- No warranty of title or non-infringement
- No express or implied warranties beyond those required by applicable law
Liability Limitations
Contributors and distributors have limited liability exposure:
| Damage Type | Liability | Coverage |
|---|---|---|
| Direct Damages | Limited | Only for deliberate/grossly negligent acts |
| Indirect Damages | Excluded | Including loss of goodwill, work stoppage |
| Special/Incidental | Excluded | Any character arising from license use |
| Commercial Losses | Excluded | Unless required by applicable law |
Sources: LICENSE.Apache2(L143 - L174)
This legal framework ensures that AxVM can be freely used, modified, and distributed while providing appropriate protections for contributors and clear obligations for users.
Overview
Relevant source files
This document provides a comprehensive introduction to AxVisor, a unified modular hypervisor built on the ArceOS unikernel framework. It covers the core concepts, architecture, and key components of AxVisor. For more detailed information about specific subsystems, refer to the respective wiki pages linked throughout this document.
What is AxVisor?
AxVisor is a hypervisor implemented based on the ArceOS unikernel framework. Its goal is to leverage the foundational operating system features provided by ArceOS to implement a unified modular hypervisor that supports multiple hardware architectures with a single codebase.
Key attributes that define AxVisor:
- Unified: AxVisor uses the same codebase to simultaneously support x86_64, ARM (aarch64), and RISC-V architectures. This maximizes the reuse of architecture-independent code and simplifies development and maintenance.
- Modular: The hypervisor's functionality is decomposed into multiple modules, each implementing a specific function. These modules communicate with each other through standard interfaces to achieve decoupling and reuse of functionality.
Sources: README.md(L21 - L28)
Architecture Overview
AxVisor follows a layered architecture approach with clearly defined components that have specific responsibilities:
flowchart TD
subgraph subGraph3["ArceOS Foundation"]
axtask["axtask - Task Management"]
axfs["axfs - Virtual File System"]
axalloc["axalloc - Memory Allocator"]
axconfig["axconfig - Configuration Management"]
end
subgraph subGraph2["Hardware Abstraction"]
axhal["axhal - Hardware Abstraction Layer"]
end
subgraph subGraph1["Virtual Machine Management"]
axvm["axvm - VM Management"]
axvcpu["axvcpu - Virtual CPU Management"]
axaddrspace["axaddrspace - Address Space Management"]
axdevice["axdevice - Device Emulation"]
end
subgraph subGraph0["Top Level"]
axvisor["axvisor - Top-level Hypervisor"]
end
axhal --> axalloc
axhal --> axconfig
axhal --> axfs
axhal --> axtask
axvcpu --> axaddrspace
axvisor --> axaddrspace
axvisor --> axconfig
axvisor --> axhal
axvisor --> axvcpu
axvisor --> axvm
axvm --> axaddrspace
axvm --> axdevice
axvm --> axvcpu
Sources: README.md(L29 - L35)
Key Components
- axvisor: The top-level hypervisor component that coordinates all subsystems.
- axvm: Manages virtual machines, their lifecycle, and interactions with the host system.
- axvcpu: Handles virtual CPU operations, including context switching, instruction emulation, and exit handling.
- axaddrspace: Manages guest memory address spaces, including page tables and memory region mapping.
- axhal: Hardware Abstraction Layer that provides unified interfaces to hardware across different architectures.
- axdevice: Emulates hardware devices for guests or manages device passthrough.
- axconfig: Handles configuration parsing and management for the hypervisor and virtual machines.
Sources: README.md(L29 - L35)
Virtual Machine Execution Flow
The following diagram illustrates the typical flow of VM initialization, boot, and execution in AxVisor:
sequenceDiagram
participant VMConfigTOML as "VMConfig (TOML)"
participant VMMinitrun as "VMM::init/run"
participant VirtualMachine as "Virtual Machine"
participant VirtualCPU as "Virtual CPU"
participant GuestOS as "Guest OS"
VMConfigTOML ->> VMMinitrun: Load VM Configuration
VMMinitrun ->> VirtualMachine: Initialize VM (id, name, vcpus)
VMMinitrun ->> VirtualMachine: Setup memory regions
VMMinitrun ->> VirtualMachine: Load guest image
alt Load from
alt filesystem
VirtualMachine ->> VirtualMachine: Load from FAT32
else Load from
else Load from
VirtualMachine ->> VirtualMachine: Load compiled image
end
end
VMMinitrun ->> VirtualCPU: Initialize VCPUs
VMMinitrun ->> VirtualCPU: Create VCPU tasks
VMMinitrun ->> VirtualMachine: Boot VM
VirtualCPU ->> VirtualCPU: Run VCPU execution loop
VirtualCPU ->> GuestOS: Execute guest code
loop VCPU execution
GuestOS ->> VirtualCPU: VM Exit (hypercall/interrupt/etc.)
VirtualCPU ->> VirtualCPU: Handle exit reason
VirtualCPU ->> GuestOS: Resume execution
end
GuestOS ->> VirtualCPU: Shutdown request
VirtualCPU ->> VirtualMachine: Signal VM shutdown
VirtualMachine ->> VMMinitrun: Decrement running VM count
Sources: README.md(L57 - L59) README.md(L116 - L202)
Cross-Platform Support
AxVisor achieves its cross-platform capability through architecture-specific implementations behind common interfaces:
flowchart TD
subgraph subGraph1["Architecture-Specific Implementations"]
x86_vcpu["x86_vcpu - x86 Implementation"]
arm_vcpu["arm_vcpu - ARM Implementation"]
riscv_vcpu["riscv_vcpu - RISC-V Implementation"]
x86_hal["x86_64 HAL Implementation"]
arm_hal["ARM/aarch64 HAL Implementation"]
riscv_hal["RISC-V HAL Implementation"]
end
subgraph subGraph0["AxVisor Common Interface"]
axvcpu_common["axvcpu - Common Interface"]
axhal_common["axhal - Common Interface"]
end
axhal_common --> arm_hal
axhal_common --> riscv_hal
axhal_common --> x86_hal
axvcpu_common --> arm_vcpu
axvcpu_common --> riscv_vcpu
axvcpu_common --> x86_vcpu
Sources: README.md(L38 - L47)
Configuration System
AxVisor uses TOML configuration files to manage virtual machine settings. The configuration system handles:
- VM identification (ID, name)
- Hardware allocation (CPU cores, memory size)
- Guest image loading method and parameters
- Virtual and passthrough devices
- Architecture-specific settings
Configuration and Image Loading
flowchart TD
subgraph subGraph1["Image Loading Methods"]
fs_load["Load from Filesystem"]
mem_load["Load from Memory"]
end
subgraph subGraph0["Configuration Flow"]
toml["TOML Config Files"]
vmconfig["VM Configuration Object"]
memory["Memory Configuration"]
devices["Device Configuration"]
image["Image Configuration"]
end
fs_image["FAT32 Filesystem Image"]
compiled_image["Statically Compiled Image"]
fs_load --> fs_image
image --> fs_load
image --> mem_load
mem_load --> compiled_image
toml --> vmconfig
vmconfig --> devices
vmconfig --> image
vmconfig --> memory
Sources: README.md(L61 - L75) README.md(L76 - L113)
Supported Guest Operating Systems
AxVisor supports multiple guest operating systems:
| Guest OS | Description | Status |
|---|---|---|
| ArceOS | The foundational unikernel framework | Supported |
| NimbOS | A simple operating system | Supported |
| Linux | Full-featured OS | Supported (mainly on aarch64) |
| Starry-OS | Educational OS | Supported |
Sources: README.md(L46 - L56)
Hardware Platform Support
AxVisor has been verified on the following platforms:
| Platform | Architecture | Status |
|---|---|---|
| QEMU ARM64 virt | aarch64 | Verified |
| Rockchip RK3568/RK3588 | aarch64 | Verified |
| 黑芝麻华山 A1000 | aarch64 | Verified |
| QEMU x86_64 | x86_64 | Supported |
| QEMU RISC-V | riscv64 | Supported |
Sources: README.md(L38 - L43)
Code Organization
AxVisor's codebase is organized around a modular architecture with the following key crates:
- axvisor: Top-level hypervisor implementation
- axvm: Virtual machine management
- axvcpu: Common virtual CPU interface
- arm_vcpu/x86_vcpu/riscv_vcpu: Architecture-specific VCPU implementations
- axaddrspace: Address space and memory virtualization
- axdevice: Device virtualization and management
- axvmconfig: Configuration management
For more detailed information about the system components, refer to System Components.
For information about VM management, see VM Management.
Sources: Cargo.lock(L118 - L121) Cargo.lock(L574 - L593) Cargo.lock(L536 - L545) Cargo.lock(L154 - L168) Cargo.lock(L1248 - L1269) Cargo.lock(L177 - L192) Cargo.lock(L239 - L261) Cargo.lock(L596 - L605)
Development Environment
AxVisor provides tools to simplify the development environment setup:
- dev_env.py: Script to localize relevant crates for easier development and debugging
- Makefile: Handles build configurations and targets
For more information on setting up a development environment, see Development Environment.
Sources: README.md(L210 - L212)
License
AxVisor is open-source software and uses the following licenses:
- Apache-2.0
- MulanPubL-2.0
- MulanPSL-2.0
- GPL-3.0-or-later
Sources: README.md(L222 - L229) LICENSE.Apache2 LICENSE.MulanPSL2 LICENSE.MulanPubL2 LICENSE.GPLv3
Architecture
Relevant source files
This page describes the core architectural design and principles of AxVisor, a unified modular hypervisor based on the ArceOS unikernel framework. It explains the high-level system structure, key components, and the relationships between them. For specific details on VM management, see VM Management, and for hardware abstraction details, see Hardware Abstraction Layer.
Architectural Overview
AxVisor is designed as a modular hypervisor with a layered architecture that achieves two primary goals:
- Unification: Using a single codebase to support multiple architectures (x86_64, ARM/aarch64, and RISC-V)
- Modularity: Decomposing hypervisor functionality into distinct modules that communicate through well-defined interfaces
The hypervisor is built on top of the ArceOS unikernel framework, which provides essential OS functionality like task scheduling, memory management, and interrupt handling.
flowchart TD
subgraph Implementation["Implementation"]
A1["ArceOS Components (axruntime, axalloc, axtask, etc.)"]
A2["Hardware Abstraction (axhal, architecture-specific modules)"]
A3["VM Management (axvm, axvcpu)"]
A4["Device Support (axdevice)"]
A5["Configuration (axvmconfig)"]
end
subgraph subGraph0["Architecture Layers"]
L1["Host OS Layer"]
L2["Hardware Abstraction Layer"]
L3["Virtual Machine Management Layer"]
L4["Device Virtualization Layer"]
L5["Configuration Layer"]
end
A2 --> A1
A3 --> A2
A4 --> A3
A5 --> A4
L1 --> A1
L2 --> A2
L2 --> L1
L3 --> A3
L3 --> L2
L4 --> A4
L4 --> L3
L5 --> A5
L5 --> L4
Sources: README.md(L29 - L36)
Core Modules and Their Relationships
AxVisor is composed of several key modules, each with specific responsibilities:
flowchart TD
subgraph subGraph0["System Modules"]
axvisor["axvisor - Top-level Hypervisor"]
axvm["axvm - Virtual Machine Management"]
axhal["axhal - Hardware Abstraction Layer"]
axvcpu["axvcpu - Virtual CPU Management"]
axaddrspace["axaddrspace - Address Space Management"]
axconfig["axconfig - Configuration Management"]
axstd["axstd - Standard Library"]
axdevice["axdevice - Device Emulation"]
axalloc["axalloc - Memory Allocator"]
end
axhal --> axalloc
axhal --> axconfig
axvisor --> axaddrspace
axvisor --> axconfig
axvisor --> axhal
axvisor --> axstd
axvisor --> axvcpu
axvisor --> axvm
axvm --> axaddrspace
axvm --> axdevice
axvm --> axvcpu
The key relationships between these modules:
| Module | Primary Responsibility | Dependencies |
|---|---|---|
| axvisor | Top-level hypervisor implementation | axvm,axhal,axvcpu,axaddrspace,axconfig |
| axvm | Virtual machine lifecycle management | axvcpu,axaddrspace,axdevice |
| axhal | Hardware abstraction for different architectures | axalloc,axconfig |
| axvcpu | Virtual CPU creation and execution | axaddrspace |
| axaddrspace | Memory virtualization and address space management | memory_addr,page_table_entry,page_table_multiarch |
| axdevice | Device emulation and passthrough | axaddrspace |
Sources: Cargo.toml(L15 - L46)
Cross-Architecture Implementation
One of AxVisor's key features is its unified architecture approach. The system uses a common interface for architecture-independent code, with architecture-specific implementations provided through separate modules.
flowchart TD
subgraph subGraph1["Hardware Features"]
x86_feat["x86 Features:- VT-x- x2APIC"]
arm_feat["ARM Features:- EL2 Mode- GICv2"]
riscv_feat["RISC-V Features:- SBI Runtime"]
end
subgraph subGraph0["Architecture Support"]
common["Common Interface Layer (axvcpu, axaddrspace)"]
x86["x86_64 Implementation (x86_vcpu)"]
arm["ARM Implementation (arm_vcpu)"]
riscv["RISC-V Implementation (riscv_vcpu)"]
end
arm --> arm_feat
common --> arm
common --> riscv
common --> x86
riscv --> riscv_feat
x86 --> x86_feat
This architecture allows AxVisor to:
- Share common virtualization logic across architectures
- Isolate architecture-specific implementation details
- Add new architecture support without changing core functionality
Sources: Cargo.toml(L35 - L37) Cargo.toml(L41 - L46)
Virtualization Flow
The core virtualization process in AxVisor follows these key steps:
sequenceDiagram
participant axvisorvmm as "axvisor::vmm"
participant axvisorvmmconfig as "axvisor::vmm::config"
participant axvmvm as "axvm::vm"
participant axvisorvmmvcpus as "axvisor::vmm::vcpus"
participant ArchitecturespecificVCPU as "Architecture-specific VCPU"
axvisorvmm ->> axvisorvmmconfig: Load VM configuration
axvisorvmmconfig ->> axvmvm: Create VM with configuration
axvmvm ->> axvmvm: Setup memory regions
axvisorvmmconfig ->> axvisorvmmconfig: Load guest image
axvisorvmm ->> axvisorvmmvcpus: Initialize VCPUs
axvisorvmmvcpus ->> axvisorvmmvcpus: Create VCPU tasks
axvisorvmm ->> axvmvm: Boot VM
axvmvm ->> axvisorvmmvcpus: Notify VCPUs to start
loop VCPU Execution
axvisorvmmvcpus ->> ArchitecturespecificVCPU: vm.run_vcpu(vcpu_id)
ArchitecturespecificVCPU ->> ArchitecturespecificVCPU: Handle VM exits
alt VM Exit: Hypercall
ArchitecturespecificVCPU ->> ArchitecturespecificVCPU: Handle hypercall
else VM Exit: External Interrupt
ArchitecturespecificVCPU ->> ArchitecturespecificVCPU: Handle interrupt
else VM Exit: System Shutdown
ArchitecturespecificVCPU ->> axvmvm: Notify VM shutdown
axvmvm ->> axvisorvmm: Decrement running VM count
end
end
This execution model enables AxVisor to:
- Load guest operating systems from either filesystem or memory
- Manage VM execution through architecture-specific VCPU implementations
- Handle various virtualization events through the VM exit mechanism
Sources: README.md(L99 - L112) README.md(L159 - L201)
Memory Management Architecture
Memory virtualization in AxVisor is built around the axaddrspace module, which provides memory region management and address translation services.
flowchart TD
subgraph subGraph3["Memory Virtualization"]
ma["memory_addr"]
subgraph subGraph2["Address Translation"]
addr_trans["Address Translation"]
gva_gpa["GVA → GPA"]
gpa_hpa["GPA → HPA"]
end
subgraph subGraph1["Memory Region Management"]
as["axaddrspace"]
mem_regions["Memory Regions"]
guest_phys["Guest Physical Address Space"]
host_phys["Host Physical Address Space"]
end
subgraph subGraph0["Page Table Management"]
pte["page_table_entry"]
ptm["page_table_multiarch"]
arch_pt["Architecture-specificpage table implementations"]
end
end
addr_trans --> gpa_hpa
addr_trans --> gva_gpa
as --> addr_trans
as --> ma
as --> mem_regions
as --> pte
as --> ptm
mem_regions --> guest_phys
mem_regions --> host_phys
pte --> ptm
ptm --> arch_pt
Key features of the memory management architecture:
- Separation of address spaces between host and guest
- Architecture-independent memory region management
- Architecture-specific page table implementations
- Two-stage address translation (Guest Virtual → Guest Physical → Host Physical)
Sources: Cargo.toml(L37) Cargo.toml(L42 - L44)
Configuration System
AxVisor uses a robust configuration system based on TOML files to define VM properties and behavior:
flowchart TD
subgraph subGraph1["Configuration Components"]
vm_id["VM ID"]
vm_name["VM Name"]
cpu_num["CPU Count"]
memory["Memory Configuration"]
devices["Device Configuration"]
images["Image Configuration"]
img_loc["image_location(fs or memory)"]
kernel_path["kernel_path"]
entry_point["entry_point"]
load_addr["kernel_load_addr"]
regions["Memory Regions"]
mem_attrs["Memory Attributes(READ|WRITE|EXECUTE)"]
end
subgraph subGraph0["Configuration Flow"]
toml["VM TOML Configuration Files"]
parser["axvmconfig Parser"]
vmconfig["VM Configuration Object"]
axvisor["axvisor Hypervisor"]
end
images --> entry_point
images --> img_loc
images --> kernel_path
images --> load_addr
memory --> regions
parser --> vmconfig
regions --> mem_attrs
toml --> parser
vmconfig --> axvisor
vmconfig --> cpu_num
vmconfig --> devices
vmconfig --> images
vmconfig --> memory
vmconfig --> vm_id
vmconfig --> vm_name
The configuration system enables:
- Declarative definition of VM properties
- Flexible image loading from filesystem or memory
- Consistent VM configuration across different architectures
- Separation of configuration from implementation logic
Sources: README.md(L72 - L75) Cargo.toml(L48 - L52)
Multi-Guest Support
AxVisor is designed to support multiple guest operating systems simultaneously:
| Guest OS | Supported Architectures | Features |
|---|---|---|
| ArceOS | x86_64, ARM, RISC-V | Full virtualization support |
| NimbOS | x86_64, ARM, RISC-V | Full virtualization support |
| Linux | ARM (aarch64) | Single-core and SMP with passthrough devices |
| Starry-OS | x86_64 | Full virtualization support |
The multi-guest architecture enables:
- Testing of different operating systems on the same hypervisor
- Comparison of performance across different guest types
- Development and debugging of guest-specific virtualization features
Sources: README.md(L45 - L56)
Conclusion
AxVisor's architecture follows key design principles that enable it to provide a unified hypervisor solution across multiple hardware architectures:
- Layered Design: Clear separation of concerns through a well-defined layering pattern
- Modular Structure: Functionality divided into well-defined modules with clear interfaces
- Architecture Independence: Separation of architecture-specific code from common functionality
- Configuration-Driven: Extensive use of configuration files for flexible deployment
- Flexible Guest Support: Support for multiple guest operating systems and image loading methods
This design enables AxVisor to maintain a single codebase while supporting diverse hardware platforms and guest operating systems, making it an adaptable and maintainable hypervisor solution.
Sources: README.md(L21 - L28) README.md(L29 - L36)
System Components
Relevant source files
This document details the main crates and their relationships within the AxVisor hypervisor system. It provides an overview of the modular architecture of AxVisor, explaining how various components interact to create a unified hypervisor framework that supports multiple architectures. For information about VM configuration, see VM Configuration. For implementation details of the VMM (Virtual Machine Manager), see VMM Implementation.
Component Architecture Overview
AxVisor follows a modular design approach with clearly defined components that have specific responsibilities. This architecture enables:
- Cross-architecture support (x86_64, ARM/aarch64, RISC-V)
- Clear separation of concerns
- Hardware abstraction
- Simplified development and testing
The system is organized into several categories of components that work together to provide hypervisor functionality.
flowchart TD
subgraph subGraph2["ArceOS Foundation"]
axstd["axstd - Standard Library"]
axhal["axhal - Hardware Abstraction Layer"]
axtask["axtask - Task Management"]
axalloc["axalloc - Memory Allocation"]
axconfig["axconfig - System Configuration"]
axfs["axfs - Virtual Filesystem"]
end
subgraph subGraph1["Architecture-Specific Components"]
x86_vcpu["x86_vcpu - x86 VCPU Implementation"]
arm_vcpu["arm_vcpu - ARM VCPU Implementation"]
riscv_vcpu["riscv_vcpu - RISC-V VCPU Implementation"]
end
subgraph subGraph0["Core Hypervisor Components"]
axvisor["axvisor - Top-level Hypervisor"]
axvm["axvm - VM Management"]
axvcpu["axvcpu - Virtual CPU Interface"]
axaddrspace["axaddrspace - Memory Virtualization"]
axdevice["axdevice - Device Emulation"]
axvmconfig["axvmconfig - VM Configuration"]
end
axstd --> axalloc
axstd --> axconfig
axstd --> axfs
axstd --> axhal
axstd --> axtask
axvcpu --> arm_vcpu
axvcpu --> riscv_vcpu
axvcpu --> x86_vcpu
axvisor --> axaddrspace
axvisor --> axstd
axvisor --> axvcpu
axvisor --> axvm
axvm --> axaddrspace
axvm --> axdevice
axvm --> axvcpu
axvm --> axvmconfig
Sources: Cargo.toml(L14 - L45)
Core Hypervisor Components
The main components of AxVisor can be categorized into core hypervisor functionality crates that are independent of the underlying architecture.
axvisor
The top-level hypervisor crate that ties all components together and provides the main entry point for the hypervisor. It coordinates the virtual machine lifecycle and manages the hypervisor state.
axvm
The Virtual Machine Manager responsible for creating, running, and managing virtual machines. It handles VM lifecycle operations such as creation, initialization, execution, and shutdown.
Key responsibilities:
- VM resource allocation and management
- Guest image loading
- Coordinating interactions between VCPUs, memory, and devices
- VM state management
axvcpu
Defines the common Virtual CPU interface used across all architectures. It provides an abstraction layer for architecture-specific VCPU implementations.
Key responsibilities:
- Common VCPU interface definition
- VCPU state management
- Architecture-agnostic VCPU operations
axaddrspace
Handles memory virtualization and address space management for virtual machines.
Key responsibilities:
- Memory region allocation and management
- Second-stage page table management
- Memory protection mechanisms
- Address translation services
axdevice
Provides device emulation capabilities for virtual machines, allowing guest operating systems to interact with virtualized hardware.
Key responsibilities:
- Device model implementation
- I/O handling
- Device state management
axvmconfig
Manages VM configurations through TOML-based configuration files, defining VM properties such as memory size, CPU count, and device settings.
Sources: Cargo.toml(L34 - L37)
Architecture-Specific Components
AxVisor supports multiple architectures through specialized implementation crates that implement the common interfaces defined by the core components.
flowchart TD
subgraph subGraph3["Architecture-Specific Implementations"]
axvcpu["axvcpu - Common VCPU Interface"]
x86_vcpu["x86_vcpu - x86 Implementation"]
arm_vcpu["arm_vcpu - ARM Implementation"]
riscv_vcpu["riscv_vcpu - RISC-V Implementation"]
subgraph subGraph2["RISC-V Specifics"]
hs_mode["HS Mode"]
sbi_rt["SBI Runtime"]
trap_handler["Trap Handler"]
end
subgraph subGraph1["ARM Specifics"]
el2["EL2 Mode"]
gicv2["GICv2 Interrupt Controller"]
hvc["HVC Exception Handler"]
end
subgraph subGraph0["x86_64 Specifics"]
vt_x["VT-x Virtualization"]
x2apic["x2APIC Support"]
vmexit_handler["VM Exit Handler"]
end
end
arm_vcpu --> el2
arm_vcpu --> gicv2
arm_vcpu --> hvc
axvcpu --> arm_vcpu
axvcpu --> riscv_vcpu
axvcpu --> x86_vcpu
riscv_vcpu --> hs_mode
riscv_vcpu --> sbi_rt
riscv_vcpu --> trap_handler
x86_vcpu --> vmexit_handler
x86_vcpu --> vt_x
x86_vcpu --> x2apic
Sources: Cargo.lock(L138 - L148) Cargo.lock(L154 - L168) Cargo.lock(L1247 - L1269) Cargo.lock(L1721 - L1739)
x86_vcpu
Implements the VCPU interface for x86_64 architecture using Intel VT-x or AMD SVM technologies.
Key features:
- VM entry/exit handling
- Extended Page Tables (EPT) support
- Interrupt virtualization
- MSR virtualization
arm_vcpu
Implements the VCPU interface for ARM/aarch64 architecture using ARM Virtualization Extensions.
Key features:
- EL2 (Hypervisor mode) operations
- Stage-2 translation
- GICv2 interrupt controller virtualization
- Hypervisor calls (HVC) handling
riscv_vcpu
Implements the VCPU interface for RISC-V architecture using the RISC-V Hypervisor Extension.
Key features:
- Hypervisor-Supervisor (HS) mode operations
- Guest-page-table handling
- SBI services virtualization
- Trap handling
Sources: Cargo.lock(L1721 - L1739) Cargo.lock(L154 - L168) Cargo.lock(L1247 - L1269)
ArceOS Foundation
AxVisor is built on top of the ArceOS (Architecture-centric OS) unikernel framework, which provides essential OS services and hardware abstractions.
axstd
A minimal standard library for ArceOS that provides common functionality and interfaces to the underlying OS services.
axhal
The Hardware Abstraction Layer that provides a uniform interface to hardware across different architectures.
Key responsibilities:
- CPU and interrupt management
- Timer services
- Memory management primitives
- Platform-specific initialization
axtask
Task management services including task creation, scheduling, and synchronization.
Key responsibilities:
- Task creation and lifecycle management
- Scheduling
- Inter-task synchronization
axalloc
Memory allocation services and memory management.
Key responsibilities:
- Physical memory allocation
- Heap management
- Memory pools
axconfig
System configuration management.
Key responsibilities:
- System parameters
- Feature flags
- Runtime configuration
axfs
Virtual filesystem support.
Key responsibilities:
- File operations
- Filesystem implementations (FAT32, etc.)
- Device filesystem
Sources: Cargo.toml(L24 - L32) Cargo.lock(L118 - L134)
Component Relationships and Dependencies
The components in AxVisor have well-defined relationships and dependencies that enable the modular architecture to function as a cohesive system.
flowchart TD
subgraph subGraph6["AxVisor System"]
axvisor["axvisor - Entry Point"]
subgraph subGraph5["OS Services"]
axstd["axstd - Standard Library"]
axhal["axhal - Hardware Abstraction Layer"]
axtask["axtask - Task Management"]
axalloc["axalloc - Memory Allocator"]
axconfig["axconfig - System Configuration"]
axfs["axfs - Filesystem"]
end
subgraph subGraph4["Device Emulation"]
axdevice["axdevice - Device Emulation"]
axdevice_base["axdevice_base - Base Device Interface"]
end
subgraph subGraph3["Memory Management"]
axaddrspace["axaddrspace - Address Space Management"]
page_tables["Page Tables"]
end
subgraph subGraph2["CPU Virtualization"]
axvcpu["axvcpu - VCPU Interface"]
arch_vcpu["Architecture-specific VCPU Implementations"]
end
subgraph subGraph1["VM Management"]
axvm["axvm - VM Management"]
axvmconfig["axvmconfig - VM Configuration"]
end
end
subgraph subGraph0["User Applications"]
vm_configs["VM Configuration Files"]
end
axaddrspace --> page_tables
axalloc --> axhal
axdevice --> axaddrspace
axdevice --> axdevice_base
axstd --> axalloc
axstd --> axconfig
axstd --> axfs
axstd --> axhal
axstd --> axtask
axtask --> axhal
axvcpu --> arch_vcpu
axvcpu --> axaddrspace
axvisor --> axstd
axvisor --> axvm
axvm --> axaddrspace
axvm --> axdevice
axvm --> axvcpu
axvm --> axvmconfig
vm_configs --> axvmconfig
Sources: Cargo.toml(L24 - L45)
Component Interaction Flow
The following diagram illustrates how the components interact during VM creation and execution:
sequenceDiagram
participant VMConfiguration as VM Configuration
participant axvisor as axvisor
participant axvm as axvm
participant axvcpu as axvcpu
participant ArchspecificVCPU as Arch-specific VCPU
participant axaddrspace as axaddrspace
participant axdevice as axdevice
VMConfiguration ->> axvisor: Load configuration
axvisor ->> axvm: Create VM(config)
axvm ->> axaddrspace: Create address space
axaddrspace -->> axvm: Address space created
axvm ->> axdevice: Initialize devices
axdevice -->> axvm: Devices initialized
axvm ->> axvcpu: Create VCPUs
axvcpu ->> ArchspecificVCPU: Create architecture-specific VCPU
ArchspecificVCPU -->> axvcpu: VCPU created
axvcpu -->> axvm: VCPUs created
axvm ->> axvm: Load guest image
axvm ->> axvcpu: Run VCPUs
loop VCPU Execution
axvcpu ->> ArchspecificVCPU: Run VCPU
alt VM Exit: I/O Operation
ArchspecificVCPU ->> axdevice: Handle I/O
axdevice -->> ArchspecificVCPU: I/O handled
else VM Exit: Memory Access
ArchspecificVCPU ->> axaddrspace: Handle memory access
axaddrspace -->> ArchspecificVCPU: Memory access handled
else VM Exit: Hypercall
ArchspecificVCPU ->> axvm: Handle hypercall
axvm -->> ArchspecificVCPU: Hypercall handled
end
ArchspecificVCPU -->> axvcpu: VCPU exited
end
axvcpu ->> axvm: VM stopped
axvm ->> axvisor: VM execution completed
Sources: Cargo.lock(L549 - L571) Cargo.lock(L576 - L593) Cargo.lock(L536 - L545) Cargo.lock(L177 - L191)
Component Key Features and Capabilities
Below is a table summarizing the key features and capabilities of the main components:
| Component | Primary Role | Key Features | Architecture Support |
|---|---|---|---|
| axvisor | Entry point and coordinator | VM lifecycle management, System initialization | All |
| axvm | VM management | VM creation, resource management, guest image loading | All |
| axvcpu | VCPU interface | Common VCPU operations, architecture-agnostic interface | All |
| x86_vcpu | x86 VCPU implementation | VT-x/SVM, EPT, VMCS management | x86_64 |
| arm_vcpu | ARM VCPU implementation | EL2 mode, GICv2, Stage-2 translation | ARM/aarch64 |
| riscv_vcpu | RISC-V VCPU implementation | HS mode, SBI services virtualization | RISC-V |
| axaddrspace | Memory virtualization | Memory regions, second-stage page tables, address translation | All |
| axdevice | Device emulation | Device models, I/O handling | All |
| axvmconfig | VM configuration | TOML-based VM configuration | All |
| axhal | Hardware abstraction | Platform initialization, interrupts, timers | All (platform-specific) |
| axtask | Task management | Scheduling, synchronization | All |
| axalloc | Memory allocation | Physical memory, heap management | All |
Sources: Cargo.toml(L14 - L45) Cargo.lock(L549 - L571)
Crate Dependency Tree
The core hypervisor crates have specific dependencies that demonstrate their relationships:
- axvisor depends on:
- axvm - For VM management
- axvcpu - For VCPU operations
- axaddrspace - For memory management
- axstd - For OS services
- Various utility crates (log, bitflags, spin, etc.)
- axvm depends on:
- axvcpu - For VCPU operations
- axaddrspace - For memory management
- axdevice - For device emulation
- axvmconfig - For configuration management
- axvcpu provides common interfaces implemented by:
- x86_vcpu - For x86_64
- arm_vcpu - For ARM/aarch64
- riscv_vcpu - For RISC-V
This modular structure allows AxVisor to maintain a single codebase while supporting multiple architectures through well-defined interfaces and architecture-specific implementations.
Sources: Cargo.toml(L34 - L45) Cargo.lock(L549 - L571) Cargo.lock(L576 - L593)
VM Management
Relevant source files
This document describes how virtual machines (VMs) are created, configured, and managed within the AxVisor hypervisor system. It explains the VM lifecycle from initialization through execution to shutdown, as well as the management of virtual CPUs (VCPUs) within each VM. For information about the underlying hardware abstraction layer, see Hardware Abstraction Layer.
VM Architecture Overview
AxVisor implements a modular VM management system that separates VM configuration, execution, and resource management concerns. The core components work together to create, execute, and monitor virtual machines.
flowchart TD
subgraph subGraph1["Task Management"]
TaskScheduler["ArceOS Task Scheduler"]
TaskExt["Task Extensions"]
end
subgraph subGraph0["VM Management System"]
VMM["VMM Module"]
VMConfig["VM Configuration"]
VMList["VM List"]
VCPUManager["VCPU Manager"]
ImageLoader["Image Loader"]
VMs["VMs"]
VCPUs["VCPUs"]
end
ImageLoader --> VMs
TaskExt --> VCPUs
TaskExt --> VMs
VCPUManager --> TaskScheduler
VCPUManager --> VCPUs
VMConfig --> VMs
VMList --> VMs
VMM --> VMConfig
VMM --> VMs
VMs --> VCPUs
Sources: src/vmm/mod.rs src/task.rs
VM Types and References
The VM management system uses specific types to represent VMs and VCPUs:
| Type | Description | Source |
|---|---|---|
| VM | Instantiated VM type usingaxvm::AxVM<AxVMHalImpl, AxVCpuHalImpl> | src/vmm/mod.rs16 |
| VMRef | VM reference type (shared viaArc) | src/vmm/mod.rs18 |
| VCpuRef | VCPU reference type (shared viaArc) | src/vmm/mod.rs20 |
These types provide the foundation for VM management operations, with reference types allowing shared access to VM and VCPU resources across different components and tasks.
Sources: src/vmm/mod.rs(L16 - L20)
VM Lifecycle
The VM lifecycle in AxVisor follows a defined sequence of operations, from initialization through execution to shutdown.
Sources: src/vmm/mod.rs(L28 - L65) src/vmm/vcpus.rs(L275 - L367)
VM Initialization
The VMM initializes VMs through the following process:
- Call
VMM::init()to initialize guest VMs and set up primary VCPUs - Load VM configurations from TOML files
- Create VM instances using
VM::new() - Load VM images based on configuration
- Set up primary VCPUs for each VM
sequenceDiagram
participant VMM as VMM
participant ConfigModule as Config Module
participant VMInstance as VM Instance
participant VCPUManager as VCPU Manager
VMM ->> ConfigModule: init_guest_vms()
ConfigModule ->> ConfigModule: Parse TOML configs
ConfigModule ->> VMInstance: Create VM with AxVMConfig
VMInstance -->> ConfigModule: Return VM instance
ConfigModule ->> ConfigModule: load_vm_images()
ConfigModule ->> VMM: Return created VMs
VMM ->> VCPUManager: setup_vm_primary_vcpu() for each VM
VCPUManager ->> VCPUManager: Create primary VCPU task
Sources: src/vmm/mod.rs(L28 - L39) src/vmm/config.rs(L25 - L43)
VM Execution
The VMM starts VM execution:
- Call
VMM::start()to boot all VMs - For each VM, call
vm.boot()to start execution - Notify the primary VCPU to begin execution
- Increment
RUNNING_VM_COUNTfor each successfully started VM - Wait until all VMs are stopped (when
RUNNING_VM_COUNTreaches 0)
VCPUs execute in their own tasks, handling various exit reasons such as hypercalls, interrupts, halts, and system shutdown events.
Sources: src/vmm/mod.rs(L42 - L65) src/vmm/vcpus.rs(L275 - L367)
VM Configuration
AxVisor uses TOML-based configuration files to define VM properties:
- Configuration files are stored in
configs/vms/directory - Default configurations are provided for different architectures (x86_64, aarch64, riscv64)
- Configurations define VM properties like ID, name, CPU count, memory regions, etc.
- Configuration processing involves:
- Parsing TOML into
AxVMCrateConfig - Converting to
AxVMConfigfor VM creation - Using configuration to load appropriate VM images
Sources: src/vmm/config.rs(L1 - L43)
VCPU Management
VCPUs are managed for each VM, with special handling for the primary VCPU:
classDiagram
class VMVCpus {
-usize _vm_id
-WaitQueue wait_queue
-Vec~AxTaskRef~ vcpu_task_list
-AtomicUsize running_halting_vcpu_count
+new(VMRef) VMVCpus
+add_vcpu_task(AxTaskRef)
+wait()
+wait_until(F)
+notify_one()
+mark_vcpu_running()
+mark_vcpu_exiting() bool
}
class VCPU_Task {
+stack_size KERNEL_STACK_SIZE
+TaskExt ext
+run() vcpu_run
}
class TaskExt {
+VMRef vm
+VCpuRef vcpu
+new(VMRef, VCpuRef) TaskExt
}
VCPU_Task "1" *-- "1" TaskExt : contains
VMVCpus "1" *-- "*" VCPU_Task : manages
Sources: src/vmm/vcpus.rs(L27 - L40) src/task.rs(L6 - L11)
VCPU Initialization
For each VM, VCPUs are initialized:
setup_vm_primary_vcpu()is called for each VM- A
VMVCpusstructure is created to manage VCPUs for the VM - The primary VCPU (ID 0) is set up first
- A task is created for the primary VCPU with the
vcpu_runentry point - The VCPU task is added to the VM's VCPU task list
- The VM's VCPU structure is stored in a global mapping
Sources: src/vmm/vcpus.rs(L218 - L231)
VCPU Task Creation
Each VCPU runs in its own ArceOS task:
alloc_vcpu_task()creates a task for a VCPU- Tasks are created with a 256 KiB kernel stack
- The task's entry point is set to
vcpu_run() - If configured, the VCPU is assigned to specific physical CPUs
- The task is initialized with
TaskExtcontaining references to the VM and VCPU - The task is spawned using
axtask::spawn_task()
Sources: src/vmm/vcpus.rs(L249 - L268) src/task.rs(L1 - L19)
VCPU Execution Loop
The main VCPU execution flow is:
- Wait for the VM to be in running state
- Mark the VCPU as running, incrementing the running count
- Enter execution loop:
- Run the VCPU using
vm.run_vcpu(vcpu_id) - Handle various exit reasons (hypercalls, interrupts, halts, etc.)
- Check if the VM is shutting down
- When the VM is shutting down:
- Mark the VCPU as exiting
- If this was the last VCPU, decrement
RUNNING_VM_COUNT - Wake the VMM wait queue if necessary
- Exit the execution loop
Sources: src/vmm/vcpus.rs(L275 - L367)
Secondary VCPU Management
AxVisor supports dynamic secondary VCPU initialization:
- When a primary VCPU requests to start a secondary VCPU (via
CpuUpexit):
vcpu_on()is called with the target VCPU ID, entry point, and argument- The target VCPU's entry point and registers are set
- A new task is created for the secondary VCPU
- The task is added to the VM's VCPU task list
- Secondary VCPUs follow the same execution loop as the primary VCPU
Sources: src/vmm/vcpus.rs(L179 - L208) src/vmm/vcpus.rs(L319 - L330)
VM Coordination and Shutdown
VMs and VCPUs coordinate through wait queues:
VMVCpusmaintains a wait queue for each VM- VCPUs can wait on the queue (e.g., when halted)
- Other VCPUs can notify waiting VCPUs (e.g., for interrupt handling)
- When a VM is shutting down, all VCPUs detect this condition
- Each VCPU marks itself as exiting
- The last exiting VCPU decrements
RUNNING_VM_COUNT - When
RUNNING_VM_COUNTreaches 0, the VMM itself is notified to exit
This coordination ensures proper cleanup when VMs are shut down.
Sources: src/vmm/vcpus.rs(L42 - L108) src/vmm/mod.rs(L55 - L65)
VM Image Loading
VM images are loaded according to configuration:
- Images can be loaded from a FAT32 filesystem or from memory
- Configuration specifies the kernel path, entry point, and load address
- The image loading process occurs after VM creation but before VCPU setup
This completes the VM preparation before execution begins.
Sources: src/vmm/config.rs(L39 - L41)
Hardware Abstraction Layer
Relevant source files
The Hardware Abstraction Layer (HAL) in AxVisor provides a uniform interface to access hardware features across multiple architectures. This component enables architecture-independent code to interact with hardware resources without needing to handle architecture-specific details directly. The HAL is a critical foundation that allows AxVisor to support x86_64, ARM/aarch64, and RISC-V architectures from a single codebase.
For detailed information about memory virtualization specifically, see Memory Management.
HAL Architecture Overview
AxVisor's HAL is implemented in the axhal crate, which provides platform-agnostic interfaces that delegate to architecture-specific implementations. This design enables higher-level components of AxVisor to operate independently of the underlying hardware.
flowchart TD
subgraph subGraph0["Hardware Abstraction Layer Structure"]
hal["axhal - Hardware Abstraction Layer"]
common["Common Interface"]
arch_spec["Architecture-Specific Implementations"]
mem_mgmt["Memory Management"]
int_mgmt["Interrupt Management"]
time_mgmt["Time Management"]
cpu_mgmt["CPU Features"]
x86_impl["x86_64 Implementation"]
arm_impl["ARM/aarch64 Implementation"]
riscv_impl["RISC-V Implementation"]
x86_paging["x86_64 Paging"]
x86_int["x86_64 Interrupts(x2APIC)"]
x86_cpu["x86_64 CPU Features(VT-x)"]
arm_paging["ARM Paging"]
arm_int["ARM Interrupts(GICv2)"]
arm_cpu["ARM CPU Features(EL2 mode)"]
riscv_paging["RISC-V Paging"]
riscv_int["RISC-V Interrupts"]
riscv_cpu["RISC-V CPU Features(SBI runtime)"]
end
arch_spec --> arm_impl
arch_spec --> riscv_impl
arch_spec --> x86_impl
arm_impl --> arm_cpu
arm_impl --> arm_int
arm_impl --> arm_paging
common --> cpu_mgmt
common --> int_mgmt
common --> mem_mgmt
common --> time_mgmt
hal --> arch_spec
hal --> common
riscv_impl --> riscv_cpu
riscv_impl --> riscv_int
riscv_impl --> riscv_paging
x86_impl --> x86_cpu
x86_impl --> x86_int
x86_impl --> x86_paging
Sources:
- Cargo.lock
- Cargo.toml
HAL Core Components
The HAL provides several key functionality areas that are essential for hypervisor operation:
1. Architecture-Independent Interfaces
The HAL defines common interfaces for hardware operations that higher-level components can use regardless of architecture:
flowchart TD
subgraph subGraph1["HAL Common Interfaces"]
hi["High-Level Components"]
hal_if["HAL Interface Functions"]
mem_if["Memory Interface- Page tables- Memory mapping"]
int_if["Interrupt Interface- Register handlers- Enable/disable"]
time_if["Time Interface- Current time- Timers"]
cpu_if["CPU Interface- Core features- Virtualization"]
subgraph Implementations["Implementations"]
arch_impl["Architecture-Specific Implementations"]
x86["x86_64"]
arm["ARM/aarch64"]
riscv["RISC-V"]
end
end
arch_impl --> arm
arch_impl --> riscv
arch_impl --> x86
hal_if --> arch_impl
hal_if --> cpu_if
hal_if --> int_if
hal_if --> mem_if
hal_if --> time_if
hi --> hal_if
Sources:
- Cargo.lock (lines 391-427)
2. Memory Management
The HAL provides memory management facilities including page table manipulation, address space management, and physical memory allocation:
| Component | Function | Architecture-Specific Aspects |
|---|---|---|
| Page Tables | Virtual-to-physical translation | x86_64: 4-level pagingARM: Stage 2 translationRISC-V: Sv39/Sv48 paging |
| Memory Mapping | Map/unmap regions of memory | Hardware-specific protection bits |
| Physical Memory | Allocate and track physical memory | Platform-specific memory layouts |
| Memory Barriers | Enforce memory access ordering | Different barrier instructions per architecture |
Sources:
- Cargo.lock (lines 1019-1032) - memory_addr and memory_set packages
- Cargo.lock (lines 1062-1084) - page_table_entry and page_table_multiarch packages
3. Interrupt Handling
The HAL manages hardware interrupts through an architecture-independent interface:
flowchart TD
subgraph subGraph1["Interrupt Handling Architecture"]
int_if["Interrupt Interface"]
subgraph Architecture-Specific["Architecture-Specific"]
int_reg["Register Handler"]
int_en["Enable/Disable"]
int_ack["Acknowledge"]
x86_int["x86_64 (x2APIC)"]
arm_int["ARM (GICv2)"]
riscv_int["RISC-V (PLIC)"]
end
end
int_ack --> arm_int
int_ack --> riscv_int
int_ack --> x86_int
int_en --> arm_int
int_en --> riscv_int
int_en --> x86_int
int_if --> int_ack
int_if --> int_en
int_if --> int_reg
int_reg --> arm_int
int_reg --> riscv_int
int_reg --> x86_int
Sources:
- Cargo.lock (lines 137-142) - arm_gicv2 package
- Cargo.lock (lines 1673-1683) - x2apic package
4. Timer and Time Management
The HAL provides interfaces for accessing system timers and managing time-related events:
| Time Feature | Function | Architecture Implementation |
|---|---|---|
| System Timer | Provides current system time | x86_64: TSC or HPETARM: Generic TimerRISC-V: TIME CSR |
| Timer Events | Schedule timer interrupts | Platform-specific timer devices |
| Time Synchronization | Sync time between host and guests | Architecture-specific counters |
Sources:
- Cargo.lock (lines 1460-1463) - timer_list package
Architecture-Specific Implementations
The HAL implements architecture-specific features for the supported platforms:
x86_64 Implementation
The x86_64 implementation provides:
- Memory management through 4-level paging
- Interrupt handling using x2APIC
- CPU virtualization using VT-x technology
- I/O ports and MMIO access
- MSR (Model Specific Registers) access
Sources:
- Cargo.lock (lines 1686-1694) - x86 package
- Cargo.lock (lines 1709-1718) - x86_64 package
ARM/aarch64 Implementation
The ARM implementation offers:
- Memory management through stage 2 translation tables
- Interrupt handling using GICv2
- CPU virtualization using EL2 execution level
- System register access
- Virtualization extensions for memory and interrupts
Sources:
- Cargo.lock (lines 6-12) - aarch64-cpu package
- Cargo.lock (lines 137-142) - arm_gicv2 package
- Cargo.lock (lines 145-151) - arm_pl011 package (UART)
RISC-V Implementation
The RISC-V implementation provides:
- Memory management through Sv39/Sv48 paging
- Interrupt handling using platform-specific interrupt controllers
- SBI (Supervisor Binary Interface) runtime for hypervisor services
- H-extension support for virtualization (where available)
Sources:
- Cargo.lock (lines 1203-1213) - riscv package
- Cargo.lock (lines 1320-1333) - sbi-rt and sbi-spec packages
Integration with Virtual CPUs
The HAL provides the foundation for virtual CPU (VCPU) implementations by exposing hardware virtualization features through a common interface:
flowchart TD
subgraph subGraph1["HAL Integration with VCPUs"]
axhal["axhal (HAL)"]
axvcpu["axvcpu (Virtual CPU Interface)"]
x86_vcpu["x86_vcpu Implementation"]
arm_vcpu["arm_vcpu Implementation"]
riscv_vcpu["riscv_vcpu Implementation"]
subgraph subGraph0["Architecture-Specific HAL Features Used by VCPUs"]
x86_vt["x86 VT-x Features- VMCS- EPT- VM Entry/Exit"]
arm_el2["ARM EL2 Features- Stage 2 MMU- vGIC- HVC"]
riscv_h["RISC-V H-Extension- Guest mode- Two-stage translation"]
end
end
arm_vcpu --> arm_el2
axhal --> axvcpu
axvcpu --> arm_vcpu
axvcpu --> riscv_vcpu
axvcpu --> x86_vcpu
riscv_vcpu --> riscv_h
x86_vcpu --> x86_vt
Sources:
- Cargo.lock (lines 538-544) - axvcpu package
- Cargo.lock (lines 154-168) - arm_vcpu package
- Cargo.lock (lines 1248-1269) - riscv_vcpu package
- Cargo.lock (lines 1721-1739) - x86_vcpu package
Hardware Resource Management
Memory Allocation
The HAL works with the memory allocator (axalloc) to provide physical memory management:
| Feature | Description |
|---|---|
| Physical Memory Allocation | Allocates physical memory pages for guest VMs |
| Memory Region Management | Manages contiguous regions of physical memory |
| Device Memory Mapping | Maps device MMIO regions into address spaces |
| DMA Operations | Manages DMA-accessible memory regions |
Sources:
- Cargo.lock (lines 196-205) - axalloc package
Device Access
The HAL provides access to physical devices and exposes platform-specific devices:
| Device Type | Function | Implementation |
|---|---|---|
| UART | Serial communication | x86_64: 8250/16550 UARTARM: PL011 UARTRISC-V: SBI console |
| Interrupt Controller | Interrupt management | x86_64: APIC/x2APICARM: GICv2RISC-V: PLIC |
| Timer | Time-keeping | Platform-specific timer devices |
| Platform-specific | Board-specific functionality | Custom device drivers |
Sources:
- Cargo.lock (lines 145-151) - arm_pl011 package
- Cargo.lock (lines 1035-1038) - ns16550a package
- Cargo.lock (lines 137-142) - arm_gicv2 package
HAL Initialization
During system boot, the HAL is initialized with architecture-specific procedures:
- Early platform detection and initialization
- Memory map discovery and setup
- Architecture-specific hardware initialization
- Interrupt controller setup
- Timer initialization
- Per-CPU state initialization
This initialization sequence ensures that the hardware is properly configured before higher-level hypervisor components begin execution.
Sources:
- Cargo.lock (lines 391-427) - axhal package
Summary
The Hardware Abstraction Layer in AxVisor provides a unified interface to access hardware features across multiple architectures. It implements architecture-specific features while presenting a common API to higher-level components. This design enables AxVisor to support multiple guest architectures (x86_64, ARM/aarch64, and RISC-V) from a single codebase, allowing for efficient hypervisor operation across diverse hardware platforms.
The HAL is tightly integrated with other components of AxVisor, particularly with memory management, virtual CPU implementations, and device emulation, forming the foundation upon which the entire hypervisor is built.
Memory Management
Relevant source files
This page documents the memory management subsystem in AxVisor, focusing on how memory virtualization and address space management are implemented in the hypervisor. For information about the hardware abstraction layer, see Hardware Abstraction Layer.
Overview
AxVisor's memory management system is responsible for:
- Virtualizing physical memory for guest VMs
- Managing address spaces for both hypervisor and VMs
- Handling stage-2 page tables for memory virtualization
- Performing address translations between different memory spaces
- Allocating and mapping memory regions for guest VMs
The system utilizes the axaddrspace crate to provide architecture-independent abstractions for memory management while supporting x86_64, ARM/aarch64, and RISC-V architectures.
flowchart TD
subgraph subGraph2["Hardware Abstraction"]
axhal["axhal"]
paging["Paging Subsystem"]
end
subgraph subGraph1["Virtual Machine"]
vm["VM"]
vcpu["VCPU"]
guest_memory["Guest Memory"]
end
subgraph subGraph0["Memory Management System"]
axaddrspace["axaddrspace"]
addrspace["AddressSpace"]
memory_regions["MemoryRegion Management"]
page_tables["Page Table Handling"]
addr_translation["Address Translation"]
end
addrspace --> guest_memory
addrspace --> page_tables
axaddrspace --> addr_translation
axaddrspace --> addrspace
axaddrspace --> memory_regions
axaddrspace --> page_tables
axhal --> paging
page_tables --> paging
vcpu --> guest_memory
vm --> guest_memory
vm --> memory_regions
vm --> vcpu
Sources: Cargo.toml(L35 - L37)
Address Spaces and Memory Virtualization
In AxVisor, memory virtualization involves three distinct address spaces:
- Guest Virtual Address (GVA) - Virtual addresses used by the guest OS
- Guest Physical Address (GPA) - Physical addresses from the guest OS perspective
- Host Physical Address (HPA) - Actual physical addresses in the host system
Memory virtualization is achieved through two levels of translation:
- First-stage translation (managed by the guest): GVA → GPA
- Second-stage translation (managed by the hypervisor): GPA → HPA
flowchart TD
subgraph subGraph1["Hypervisor Control"]
HPA["Host PhysicalAddress (HPA)"]
end
subgraph subGraph0["Guest OS Control"]
GVA["Guest VirtualAddress (GVA)"]
GPA["Guest PhysicalAddress (GPA)"]
end
GPA --> HPA
GVA --> GPA
GVA --> HPA
Sources: configs/platforms/x86_64-qemu-q35.toml(L15 - L30)
Address Space Management
The axaddrspace crate provides a unified interface for managing address spaces across different architectures. It handles the creation, manipulation, and destruction of second-stage page tables used for GPA to HPA translations.
Key components include:
AddressSpace: Represents a virtual address space for a guest VMMemoryRegion: Defines a contiguous region of memory with specific permissionsPageTable: Manages the page tables for address translation
classDiagram
class AddressSpace {
+root_page_table
+memory_regions
+create()
+map()
+unmap()
+query()
+contains()
}
class MemoryRegion {
+start
+size
+flags
+map_type
}
class PageTable {
+arch_specific_implementation
+map_page()
+unmap_page()
+translate()
}
AddressSpace "1" --> "many" MemoryRegion : contains
AddressSpace "1" --> "1" PageTable : uses
Sources: Cargo.toml(L37)
Memory Region Types
AxVisor supports different types of memory regions that can be mapped into a guest VM's address space:
| Region Type | Description | Use Case |
|---|---|---|
| MAP_ALLOC | Memory allocated from hypervisor and mapped to guest | Regular guest memory |
| MAP_IDENTICAL | Identity-mapped memory from host to guest | Device pass-through, shared memory |
| MMIO | Memory-mapped I/O regions | Device access |
| ROM | Read-only memory | BIOS, firmware |
Memory regions are configured with specific access permissions (read, write, execute) and mapping types.
Sources: configs/vms/arceos-x86_64.toml(L40 - L44) configs/platforms/x86_64-qemu-q35.toml(L37 - L45)
VM Memory Configuration
Virtual machines in AxVisor are configured with memory regions specified in their configuration files. Here's how memory regions are defined:
memory_regions = [
[base_paddr, size, flags, map_type],
]
Where:
base_paddr: Base guest physical addresssize: Size of the memory region in bytesflags: Access permissions (bit 0: read, bit 1: write, bit 2: execute)map_type: 0 for MAP_ALLOC, 1 for MAP_IDENTICAL
The hypervisor creates and manages these memory regions when a VM is initialized.
flowchart TD
subgraph subGraph0["Configuration Example"]
config["memory_regions = [[0x0000_0000, 0x100_0000, 0x7, 0],]"]
end
vmconfig["VM Configuration"]
mm_region["Memory Region Specification"]
vmm["VMM"]
addrspace["Address Space"]
map_operation["Memory Mapping"]
addrspace --> map_operation
config --> mm_region
mm_region --> vmm
vmconfig --> mm_region
vmm --> addrspace
Sources: configs/vms/arceos-x86_64.toml(L40 - L44) configs/vms/nimbos-x86_64.toml(L40 - L44)
Page Table Management
AxVisor uses the page_table_multiarch and page_table_entry crates to provide architecture-independent page table management. These crates abstract the architecture-specific details of page tables for x86_64, ARM/aarch64, and RISC-V.
Key functionalities include:
- Creating and managing page tables
- Mapping guest physical addresses to host physical addresses
- Handling page faults and TLB operations
- Supporting different page sizes for efficient memory mapping
The page tables implement the second-stage translation (GPA → HPA) required for memory virtualization.
Sources: Cargo.toml(L44 - L45)
Address Translation
Address translation in AxVisor involves converting between different address spaces:
- GPA to HPA Translation: Used when a guest VM accesses physical memory
- HPA to GPA Translation: Used for handling page faults and device emulation
- Linear Address Translation: Used for quick conversions between host virtual and physical addresses
The physical-virtual offset defined in the platform configuration allows for efficient linear mapping between host virtual and physical addresses.
flowchart TD
subgraph subGraph1["Translation Components"]
stage2_pt["Stage-2 Page Tables"]
tlb["TLB"]
page_fault["Page Fault Handler"]
end
subgraph subGraph0["Translation Process"]
gpa["Guest Physical Address (GPA)"]
hpa["Host Physical Address (HPA)"]
end
gpa --> hpa
gpa --> stage2_pt
hpa --> gpa
page_fault --> stage2_pt
stage2_pt --> hpa
stage2_pt --> tlb
Sources: configs/platforms/x86_64-qemu-q35.toml(L22 - L26)
Memory Isolation and Protection
AxVisor ensures memory isolation between guest VMs and between guests and the hypervisor through several mechanisms:
- Separate Address Spaces: Each VM has its own address space with second-stage page tables
- Permission Enforcement: Memory regions have specific access permissions enforced by hardware
- MMIO Protection: Special handling for memory-mapped I/O regions
- EPT/NPT/Stage-2 MMU: Hardware virtualization extensions enforce memory isolation
This ensures that one VM cannot access the memory of another VM or the hypervisor, providing strong security isolation.
Sources: Cargo.toml(L37)
Kernel Memory Layout
The hypervisor kernel itself has a specific memory layout defined by the linker script. This layout organizes different sections of the kernel in memory:
| Section | Description |
|---|---|
| .text | Executable code |
| .rodata | Read-only data |
| .data | Initialized data |
| .tdata/.tbss | Thread-local storage |
| .percpu | Per-CPU data |
| .bss | Uninitialized data |
The layout ensures proper memory alignment and access protection for each section.
Sources: scripts/lds/linker.lds.S(L11 - L76)
Timer Subsystem and Memory
The VMM timer subsystem interacts with memory management through the allocation and management of timer events. Each timer event is stored in memory and scheduled based on its deadline.
flowchart TD
subgraph subGraph1["Memory Management"]
allocation["Memory Allocation"]
percpu["Per-CPU Storage"]
end
subgraph subGraph0["Timer Subsystem"]
timer_list["TimerList"]
timer_event["VmmTimerEvent"]
register["register_timer()"]
check["check_events()"]
end
time_value["TimeValue"]
allocation --> timer_event
check --> timer_list
percpu --> timer_list
register --> timer_list
time_value --> timer_list
timer_list --> timer_event
Sources: src/vmm/timer.rs(L18 - L23) src/vmm/timer.rs(L44)
Architecture-Specific Considerations
While AxVisor provides architecture-independent abstractions for memory management, there are important architecture-specific considerations:
x86_64
- Uses Extended Page Tables (EPT) for second-stage translation
- Supports large pages (2MB, 1GB) for efficient mapping
- Handles specific x86 memory types and caching attributes
ARM/aarch64
- Uses Stage-2 translation tables
- Supports different EL2 translation regimes
- Handles ARM-specific memory attributes
RISC-V
- Uses Sv39/Sv48 page tables with appropriate extensions
- Implements RISC-V specific memory permission bits
- Handles RISC-V address translation modes
Each architecture implementation conforms to the common interfaces provided by axaddrspace, page_table_entry, and page_table_multiarch.
Sources: Cargo.toml(L44 - L45)
Conclusion
AxVisor's memory management system provides a robust, architecture-independent framework for virtualizing memory across different processor architectures. By abstracting the details of address space management and page table handling, it enables efficient memory virtualization while ensuring strong isolation between guest VMs.
The system leverages hardware virtualization features to minimize overhead while providing the flexibility needed to support diverse guest operating systems and configurations.
Configuration
Relevant source files
This document describes the configuration system used by AxVisor. Configuration files define essential parameters for both the hypervisor and the virtual machines it manages. This page covers the structure and format of configuration files, how they are processed, and the available configuration options. For specific VM configuration details, see VM Configuration.
Overview of Configuration System
AxVisor uses TOML files for configuration, which provide a clear and human-readable format for defining hypervisor and virtual machine parameters. Two primary types of configuration files exist:
- Platform Configurations - Define hardware-specific settings for the target platform
- VM Configurations - Define settings for individual virtual machines
These configuration files are processed during the build process and at runtime to initialize the hypervisor and its virtual machines.
flowchart TD
subgraph subGraph1["Configuration Processing"]
build_process["Build Process"]
vmm_init["VMM::init()"]
axconfig_rs["axconfig.rs"]
vms["Virtual Machines"]
end
subgraph subGraph0["Configuration System"]
config_files["TOML Configuration Files"]
platform_config["Platform Configsconfigs/platforms/*.toml"]
vm_config["VM Configsconfigs/vms/*.toml"]
hardware_params["Hardware ParametersMemory layoutDevice mappingsArchitecture settings"]
vm_params["VM ParametersCPU countMemory allocationDevice assignments"]
build_config["Build ConfigurationMakefile parameters"]
end
build_config --> config_files
build_process --> axconfig_rs
config_files --> platform_config
config_files --> vm_config
platform_config --> build_process
platform_config --> hardware_params
vm_config --> vm_params
vm_config --> vmm_init
vmm_init --> vms
Sources: README.md(L71 - L74) README.md(L77 - L79)
Configuration File Structure
Platform Configuration Files
Platform configuration files define the hardware-specific settings for the target platform. These files are located in the configs/platforms/ directory with filenames that correspond to the platform, such as x86_64-qemu-q35.toml.
A platform configuration file has the following main sections:
| Section | Purpose |
|---|---|
| Top-level | Architecture and platform identifiers |
| [plat] | Memory layout, address spaces, and offsets |
| [devices] | Device mappings, MMIO regions, and hardware configuration |
Example platform configuration excerpt:
# Architecture identifier
arch = "x86_64"
# Platform identifier
platform = "x86_64-qemu-q35"
[plat]
# Base virtual address of the kernel image
kernel-base-vaddr = "0xffff_8000_0020_0000"
# Linear mapping offset
phys-virt-offset = "0xffff_8000_0000_0000"
[devices]
# MMIO regions with format (`base_paddr`, `size`)
mmio-regions = [
[0xfec0_0000, 0x1000], # IO APIC
[0xfed0_0000, 0x1000], # HPET
[0xfee0_0000, 0x1000], # Local APIC
]
Sources: configs/platforms/x86_64-qemu-q35.toml(L1 - L54)
VM Configuration Files
VM configuration files define the properties of individual virtual machines that will be managed by AxVisor. These files are located in the configs/vms/ directory with names like arceos-x86_64.toml.
A VM configuration file contains these main sections:
| Section | Purpose |
|---|---|
| [base] | Basic VM information (ID, name, type, CPU count) |
| [kernel] | Kernel image details and loading parameters |
| [devices] | Device specifications (emulated and passthrough devices) |
Example VM configuration excerpt:
[base]
# Guest VM ID
id = 1
# Guest VM name
name = "arceos"
# Virtualization type
vm_type = 1
# The number of virtual CPUs
cpu_num = 1
[kernel]
# The entry point of the kernel image
entry_point = 0x8000
# The location of image: "memory" | "fs"
image_location = "fs"
# The file path of the kernel image
kernel_path = "arceos-x86_64.bin"
# The load address of the kernel image
kernel_load_addr = 0x20_0000
Sources: configs/vms/arceos-x86_64.toml(L1 - L37) configs/vms/nimbos-x86_64.toml(L1 - L39)
VM Configuration Options
The VM configuration file contains multiple sections that control various aspects of the virtual machine. Here's a detailed breakdown of the available options:
flowchart TD
subgraph subGraph0["VM Configuration Structure"]
vm_config["VM Configuration File(.toml)"]
base_section["[base] SectionVM Identity"]
kernel_section["[kernel] SectionGuest Image & Boot"]
devices_section["[devices] SectionDevice Configuration"]
base_options["id: VM identifiername: VM namevm_type: Virtualization typecpu_num: Number of vCPUsphys_cpu_sets: CPU pinning"]
kernel_options["entry_point: Entry addressimage_location: 'memory' or 'fs'kernel_path: Image pathkernel_load_addr: Load addressmemory_regions: Memory layout"]
optional_kernel["bios_path: BIOS imagebios_load_addr: BIOS load addressramdisk_path: Ramdisk imagedisk_path: Disk image"]
device_options["emu_devices: Emulated devicespassthrough_devices: Passthrough devices"]
end
base_section --> base_options
devices_section --> device_options
kernel_section --> kernel_options
kernel_section --> optional_kernel
vm_config --> base_section
vm_config --> devices_section
vm_config --> kernel_section
Sources: configs/vms/arceos-x86_64.toml(L1 - L78) configs/vms/nimbos-x86_64.toml(L1 - L78)
Base Section
The [base] section defines the core identity and properties of the VM:
| Option | Description | Example |
|---|---|---|
| id | Unique identifier for the VM | id = 1 |
| name | Human-readable name for the VM | name = "arceos" |
| vm_type | Type of virtualization | vm_type = 1 |
| cpu_num | Number of virtual CPUs | cpu_num = 1 |
| phys_cpu_sets | Physical CPU pinning | phys_cpu_sets = [1] |
Kernel Section
The [kernel] section defines how the guest kernel is loaded and executed:
| Option | Description | Example |
|---|---|---|
| entry_point | Entry point address of the kernel | entry_point = 0x8000 |
| image_location | Source location for guest image ("memory" or "fs") | image_location = "fs" |
| kernel_path | Path to the kernel image file | kernel_path = "arceos-x86_64.bin" |
| kernel_load_addr | Physical address to load the kernel | kernel_load_addr = 0x20_0000 |
| bios_path | Path to the BIOS image file (optional) | bios_path = "axvm-bios.bin" |
| bios_load_addr | Address to load the BIOS (optional) | bios_load_addr = 0x8000 |
| ramdisk_path | Path to the ramdisk image (optional) | ramdisk_path = "ramdisk.img" |
| ramdisk_load_addr | Address to load the ramdisk (optional) | ramdisk_load_addr = 0x1000_0000 |
| disk_path | Path to the disk image (optional) | disk_path = "disk.img" |
| memory_regions | Memory regions with format (base_paddr,size,flags,map_type) | memory_regions = [[0x0, 0x100_0000, 0x7, 0]] |
Devices Section
The [devices] section defines the virtual and passthrough devices for the VM:
| Option | Description | Format |
|---|---|---|
| emu_devices | Emulated devices | [Name, Base-Ipa, Ipa_len, Alloc-Irq, Emu-Type, EmuConfig] |
| passthrough_devices | Passthrough devices | [Name, Base-Ipa, Base-Pa, Length, Alloc-Irq] |
Example passthrough device entry:
passthrough_devices = [
[
"IO APIC",
0xfec0_0000,
0xfec0_0000,
0x1000,
0x1,
]
]
Sources: configs/vms/arceos-x86_64.toml(L50 - L78) configs/vms/nimbos-x86_64.toml(L50 - L78)
Image Loading Methods
AxVisor supports two methods for loading guest VM images:
- File System Loading (
image_location = "fs"):
- Loads the guest image from a FAT32 file system
- Requires setting up a disk image file with the guest image(s)
- Used when the guest images need to be changed without rebuilding the hypervisor
- Memory Loading (
image_location = "memory"):
- Loads the guest image from memory, bound to the hypervisor through static compilation
- Uses
include_bytes!to include the image in the hypervisor binary - Useful for embedded scenarios or when the guest image is fixed
flowchart TD
subgraph subGraph0["Image Loading Process"]
config["VM Configuration"]
load_method["image_location"]
fs_load["Load from File System"]
mem_load["Load from Memory"]
mount["Mount FAT32 Filesystem"]
read_fs["Read kernel_path from disk"]
load_mem["Load to kernel_load_addr"]
compile["Statically Compiled Imageinclude_bytes!()"]
load_mem2["Load to kernel_load_addr"]
start_vm["Start VM"]
end
compile --> load_mem2
config --> load_method
fs_load --> mount
load_mem --> start_vm
load_mem2 --> start_vm
load_method --> fs_load
load_method --> mem_load
mem_load --> compile
mount --> read_fs
read_fs --> load_mem
Sources: README.md(L78 - L112)
Build Configuration
The build configuration controls how AxVisor is compiled and which features are enabled. The primary method for configuring the build is through the make command and its parameters.
Key build parameters include:
| Parameter | Description | Example |
|---|---|---|
| ARCH | Target architecture | ARCH=aarch64 |
| LOG | Log level | LOG=info |
| VM_CONFIGS | Path to VM configuration file(s) | VM_CONFIGS=configs/vms/arceos-aarch64.toml |
| ACCEL | Enable hardware acceleration | ACCEL=n |
| APP_FEATURES | Additional features | APP_FEATURES=fs |
Example build command:
make ACCEL=n ARCH=aarch64 LOG=info VM_CONFIGS=configs/vms/arceos-aarch64.toml APP_FEATURES=fs run
Additionally, build-time dependencies are declared in Cargo.toml, including the TOML parser and configuration tools:
[build-dependencies]
toml = { git = "https://github.com/arceos-hypervisor/toml.git", branch = "no_std" }
axconfig = { git = "https://github.com/arceos-hypervisor/arceos.git", branch = "vmm" }
Sources: Cargo.toml(L47 - L52) README.md(L112 - L113)
Configuration Processing
Configuration files are processed both at build time and at runtime:
- Build-time Processing:
- Platform configurations are processed during the build
- Generate architecture-specific code based on the platform configuration
- Set up memory layouts and device mappings
- Runtime Processing:
- VM configurations are processed when AxVisor starts
- The VMM (Virtual Machine Manager) reads and applies VM configurations
- Creates VM instances according to the configuration
The configuration files are parsed using a TOML parser adapted for use in a no_std environment. The parsed configuration is then used to initialize various components of the hypervisor.
For example, timer configuration parameters are used to set up the timer subsystem:
// Timer interrupt frequency is configured in the platform configuration
const PERIODIC_INTERVAL_NANOS: u64 = axhal::time::NANOS_PER_SEC / axconfig::TICKS_PER_SEC as u64;
Sources: src/vmm/timer.rs(L12 - L13)
Configuration Tools
AxVisor provides additional tools to help with configuration:
- axvmconfig: A tool for generating VM configuration files
- Simplifies the process of creating complex VM configurations
- Provides validation of configuration parameters
- dev_env.py: A script for setting up the development environment
- Helps localize relevant crates for development and debugging
- Makes it easier to work with the modular components of AxVisor
Sources: README.md(L77 - L79) README.md(L210 - L214)
Conclusion
AxVisor's configuration system provides a flexible and powerful way to define both the hypervisor platform and the virtual machines it manages. By using TOML files, the configuration is human-readable and easy to modify. The separation between platform and VM configurations allows for a modular approach, where the same hypervisor build can host different VMs without requiring recompilation.
Understanding the configuration options is essential for successfully deploying AxVisor and managing virtual machines with different requirements and characteristics.
VM Configuration
Relevant source files
This page describes how to configure virtual machines (VMs) in AxVisor. The configuration system uses TOML files to define VM properties, including basic VM information, kernel image details, memory layout, and device specifications. For information about platform-specific configuration options, see Platform-Specific Configuration.
Configuration Overview
AxVisor uses a structured configuration approach that allows users to declaratively define VM properties using TOML files. These configuration files are processed during VM initialization and determine how VMs are created, what resources they receive, and how they interact with the host system.
flowchart TD
subgraph subGraph1["VM Management"]
vm["VM Object"]
vcpu["VCPU Objects"]
memory["Memory Regions"]
devices["Device Configuration"]
end
subgraph subGraph0["VM Configuration System"]
toml_config["TOML Configuration Files"]
vmm_init["VMM::init()"]
vm_creation["VM Creation"]
image_loading["Image Loading"]
end
toml_config --> vmm_init
vm --> devices
vm --> memory
vm --> vcpu
vm_creation --> image_loading
vm_creation --> vm
vmm_init --> vm_creation
Sources: README.md(L69 - L78) configs/vms/arceos-x86_64.toml(L1 - L78)
Configuration File Structure
The VM configuration file is divided into three main sections: [base], [kernel], and [devices].
Base Configuration
The [base] section defines fundamental VM properties:
[base]
# Guest vm id.
id = 1
# Guest vm name.
name = "arceos"
# Virtualization type.
vm_type = 1
# The number of virtual CPUs.
cpu_num = 1
# Guest vm physical cpu sets.
phys_cpu_sets = [1]
| Parameter | Description | Example |
|---|---|---|
| id | Unique identifier for the VM | 1 |
| name | Descriptive name for the VM | "arceos" |
| vm_type | Type of virtualization (1 = full virtualization) | 1 |
| cpu_num | Number of virtual CPUs allocated to the VM | 1 |
| phys_cpu_sets | Physical CPU cores assigned to the VM | [1] |
Sources: configs/vms/arceos-x86_64.toml(L1 - L13) configs/vms/nimbos-x86_64.toml(L1 - L13)
Kernel Configuration
The [kernel] section specifies how the guest kernel image should be loaded and executed:
[kernel]
# The entry point of the kernel image.
entry_point = 0x8000
# The location of image: "memory" | "fs".
image_location = "fs"
# The file path of the BIOS image.
bios_path = "axvm-bios.bin"
# The load address of the BIOS image.
bios_load_addr = 0x8000
# The file path of the kernel image.
kernel_path = "arceos-x86_64.bin"
# The load address of the kernel image.
kernel_load_addr = 0x20_0000
# Memory regions with format (`base_paddr`, `size`, `flags`, `map_type`).
# For `map_type`, 0 means `MAP_ALLOC`, 1 means `MAP_IDENTICAL`.
memory_regions = [
[0x0000_0000, 0x100_0000, 0x7, 0], # Low RAM 16M 0b111
]
| Parameter | Description | Example |
|---|---|---|
| entry_point | Starting execution address for the guest | 0x8000 |
| image_location | Source of the guest image ("memory" or "fs") | "fs" |
| bios_path | Path to BIOS image (for x86) | "axvm-bios.bin" |
| bios_load_addr | Memory address where BIOS should be loaded | 0x8000 |
| kernel_path | Path to kernel image | "arceos-x86_64.bin" |
| kernel_load_addr | Memory address where kernel should be loaded | 0x20_0000 |
| memory_regions | Memory regions allocated to the VM | [[0x0, 0x100_0000, 0x7, 0]] |
Sources: configs/vms/arceos-x86_64.toml(L14 - L44) configs/vms/nimbos-x86_64.toml(L14 - L44)
Device Configuration
The [devices] section defines both emulated and passthrough devices:
[devices]
# Emu_devices.
# Name Base-Ipa Ipa_len Alloc-Irq Emu-Type EmuConfig.
emu_devices = []
# Pass-through devices.
# Name Base-Ipa Base-Pa Length Alloc-Irq.
passthrough_devices = [
[
"IO APIC",
0xfec0_0000,
0xfec0_0000,
0x1000,
0x1,
],
[
"Local APIC",
0xfee0_0000,
0xfee0_0000,
0x1000,
0x1,
],
]
| Parameter | Description | Format |
|---|---|---|
| emu_devices | List of emulated devices | [Name, Base-Ipa, Ipa_len, Alloc-Irq, Emu-Type, EmuConfig] |
| passthrough_devices | List of passthrough devices | [Name, Base-Ipa, Base-Pa, Length, Alloc-Irq] |
Sources: configs/vms/arceos-x86_64.toml(L46 - L78) configs/vms/nimbos-x86_64.toml(L46 - L78)
Memory Region Configuration
Memory regions in the memory_regions parameter use a structured format:
flowchart TD
subgraph subGraph0["Memory Region Format"]
base_paddr["base_paddr: Starting Physical Address"]
size["size: Region Size in Bytes"]
flags["flags: Memory Permissions (Bit flags)"]
map_type["map_type: Mapping Type (0=MAP_ALLOC, 1=MAP_IDENTICAL)"]
end
read["Read Permission"]
write["Write Permission"]
execute["Execute Permission"]
flags --> execute
flags --> read
flags --> write
Memory Flags
The flags field defines memory permissions as bit flags:
- Bit 0 (0x1): Read permission
- Bit 1 (0x2): Write permission
- Bit 2 (0x4): Execute permission
Common combinations:
0x7(0b111): Read + Write + Execute0x3(0b011): Read + Write0x1(0b001): Read-only
Mapping Types
The map_type field defines how memory is mapped:
0(MAP_ALLOC): Allocate new memory for the VM1(MAP_IDENTICAL): Identity map to physical memory (for passthrough)
Sources: configs/vms/arceos-x86_64.toml(L40 - L44) configs/vms/nimbos-x86_64.toml(L40 - L44)
Image Loading Methods
AxVisor supports two methods for loading guest images:
flowchart TD
subgraph subGraph1["Memory Loading"]
mem_loading["Memory Loading"]
static["Reference include_bytes! data"]
load_mem["Load to kernel_load_addr"]
end
subgraph subGraph0["File System Loading"]
fs_loading["File System Loading"]
mount["Mount disk.img"]
read_file["Read kernel_path from FS"]
load_fs["Load to kernel_load_addr"]
end
config["VM Configuration"]
boot["Boot VM"]
config --> fs_loading
config --> mem_loading
fs_loading --> mount
load_fs --> boot
load_mem --> boot
mem_loading --> static
mount --> read_file
read_file --> load_fs
static --> load_mem
File System Loading
When image_location="fs" is specified:
- AxVisor mounts a FAT32 disk image file (typically
disk.img) - Reads the kernel image from the specified
kernel_path - Loads it into memory at
kernel_load_addr
Required configuration:
image_location = "fs"kernel_path(path within filesystem)kernel_load_addr(memory address for loading)- Optionally,
bios_pathandbios_load_addrfor x86
Memory Loading
When image_location="memory" is specified:
- The guest kernel image is statically compiled into the AxVisor binary
- The image is loaded from memory at runtime
Required configuration:
image_location = "memory"kernel_path(must point to a file in the workspace)kernel_load_addr(memory address for loading)- Optionally,
bios_pathandbios_load_addrfor x86
Sources: README.md(L79 - L112)
Configuration and Memory Layout Relationships
Each VM's memory layout is determined by both VM-specific configuration and platform-specific memory layouts:
flowchart TD
subgraph subGraph2["VM Memory Layout"]
vm_memory["VM Physical Memory"]
bios["BIOS Region"]
kernel["Kernel Region"]
device_mmio["Device MMIO Regions"]
end
subgraph subGraph1["VM Configuration"]
vm_config["VM Config (arceos-x86_64.toml)"]
memory_regions["Memory Regions"]
kernel_load_addr["Kernel Load Address"]
bios_load_addr["BIOS Load Address"]
end
subgraph subGraph0["Platform Configuration"]
plat_config["Platform Config (x86_64-qemu-q35.toml)"]
phys_memory["Physical Memory Base/Size"]
kernel_base["Kernel Base Address"]
aspace["Kernel Address Space"]
end
bios_load_addr --> bios
kernel_load_addr --> kernel
memory_regions --> vm_memory
phys_memory --> vm_memory
plat_config --> aspace
plat_config --> kernel_base
plat_config --> phys_memory
vm_config --> bios_load_addr
vm_config --> kernel_load_addr
vm_config --> memory_regions
Sources: configs/vms/arceos-x86_64.toml(L40 - L44) configs/platforms/x86_64-qemu-q35.toml(L13 - L28)
Using the axvmconfig Tool
For complex VM configurations, AxVisor provides the axvmconfig tool that can generate custom configuration files. To use this tool:
- Install the tool:
cargo install axvmconfig - Define your VM configuration using the tool's syntax
- Generate a TOML configuration file
See the axvmconfig documentation for detailed usage.
Sources: README.md(L72 - L74)
Example Configuration: ArceOS on x86_64
Here's an example of configuring an ArceOS guest on x86_64:
# Vm base info configs
[base]
id = 1
name = "arceos"
vm_type = 1
cpu_num = 1
phys_cpu_sets = [1]
# Vm kernel configs
[kernel]
entry_point = 0x8000
image_location = "fs"
bios_path = "axvm-bios.bin"
bios_load_addr = 0x8000
kernel_path = "arceos-x86_64.bin"
kernel_load_addr = 0x20_0000
memory_regions = [
[0x0000_0000, 0x100_0000, 0x7, 0], # Low RAM 16M with RWX permissions
]
# Device specifications
[devices]
emu_devices = []
passthrough_devices = [
["IO APIC", 0xfec0_0000, 0xfec0_0000, 0x1000, 0x1],
["Local APIC", 0xfee0_0000, 0xfee0_0000, 0x1000, 0x1],
["HPET", 0xfed0_0000, 0xfed0_0000, 0x1000, 0x1],
]
Sources: configs/vms/arceos-x86_64.toml(L1 - L78)
Boot Process with VM Configuration
During the boot process, AxVisor uses the VM configuration to set up and start the VM:
sequenceDiagram
participant VMConfigFile as "VM Config File"
participant VMMinit as "VMM::init()"
participant VMObject as "VM Object"
participant ImageLoader as "Image Loader"
participant VCPUSetup as "VCPU Setup"
VMConfigFile ->> VMMinit: Load configuration
VMMinit ->> VMObject: Create VM with id, name, cpu_num
VMObject ->> VMObject: Set up memory regions
alt image_location = "fs"
VMObject ->> ImageLoader: Load kernel from filesystem
else image_location = "memory"
VMObject ->> ImageLoader: Load kernel from memory
end
ImageLoader ->> VMObject: Load at kernel_load_addr
VMObject ->> VCPUSetup: Create VCPUs
VMObject ->> VMObject: Configure devices
VMMinit ->> VMObject: Boot VM
VMObject ->> VCPUSetup: Start execution at entry_point
Sources: README.md(L57 - L112) src/vmm/timer.rs(L98 - L104)
Timer Configuration
The VM timer subsystem is also affected by configuration settings:
flowchart TD
subgraph subGraph0["Timer Configuration"]
timer_freq["timer-frequency in Platform Config"]
periodic_interval["PERIODIC_INTERVAL_NANOS"]
vmm_timer["VMM Timer Events"]
end
timer_events["Timer Events"]
vcpu_execution["VCPU Execution"]
periodic_interval --> vmm_timer
timer_events --> vcpu_execution
timer_freq --> periodic_interval
vmm_timer --> timer_events
Sources: src/vmm/timer.rs(L1 - L104) configs/platforms/x86_64-qemu-q35.toml(L56 - L57)
Conclusion
AxVisor's VM configuration system provides a flexible and declarative way to define virtual machine properties. By using TOML configuration files, users can easily customize VM attributes without modifying code. The configuration system supports different image loading methods, memory configurations, and device setups, enabling a wide range of virtualization scenarios.
For platform-specific configuration options that complement VM configurations, see Platform-Specific Configuration.
Platform-Specific Configuration
Relevant source files
This document describes the architecture-specific configuration options in AxVisor. It covers the configuration files and settings required for each supported hardware platform (x86_64, ARM/aarch64, and RISC-V). For information about virtual machine configuration, see VM Configuration.
Configuration System Overview
AxVisor uses TOML configuration files to define platform-specific settings, which are located in the configs/platforms/ directory. These files contain architecture-dependent parameters such as memory layout, device specifications, and timer frequencies.
flowchart TD
subgraph subGraph2["Runtime System"]
vm_manager["VM Manager"]
hal["Hardware Abstraction Layer"]
end
subgraph subGraph1["Build System"]
axconfig["axconfigBuild-Time Config"]
build_process["Build Process"]
end
subgraph subGraph0["Configuration Files"]
platform_configs["Platform Configsconfigs/platforms/*.toml"]
vm_configs["VM Configsconfigs/vms/*.toml"]
end
axconfig --> build_process
platform_configs --> axconfig
platform_configs --> hal
platform_configs --> vm_configs
vm_configs --> vm_manager
Sources: configs/platforms/x86_64-qemu-q35.toml(L1 - L58) Cargo.toml(L47 - L52)
Platform Configuration File Structure
Each platform configuration file follows a standardized format, with sections for platform identification, memory layout, and device specifications.
Example Configuration (x86_64-qemu-q35)
classDiagram
class PlatformConfig {
arch: string
platform: string
plat: PlatformSettings
devices: DeviceSettings
}
class PlatformSettings {
family: string
phys-memory-base: usize
phys-memory-size: usize
kernel-base-paddr: usize
kernel-base-vaddr: usize
phys-virt-offset: usize
phys-bus-offset: usize
kernel-aspace-base: usize
kernel-aspace-size: usize
}
class DeviceSettings {
pci-ecam-base: usize
pci-bus-end: usize
timer-frequency: usize
mmio-regions: [(usize, usize) ]
virtio-mmio-regions: [(usize, usize) ]
pci-ranges: [(usize, usize) ]
}
PlatformConfig --> PlatformSettings
PlatformConfig --> DeviceSettings
Sources: configs/platforms/x86_64-qemu-q35.toml(L1 - L58)
Memory Layout Configuration
The memory layout configuration is one of the most critical aspects of platform-specific settings. It defines the physical and virtual memory address spaces, kernel loading addresses, and memory mapping offsets.
Memory Layout Parameters
| Parameter | Description | Example (x86_64) |
|---|---|---|
| phys-memory-base | Base address of physical memory | 0x0 |
| phys-memory-size | Size of physical memory | 0x8000000 (128MB) |
| kernel-base-paddr | Physical address where kernel is loaded | 0x200000 |
| kernel-base-vaddr | Virtual address of the kernel image | 0xffff800000200000 |
| phys-virt-offset | Offset for physical-to-virtual address translation | 0xffff800000000000 |
| kernel-aspace-base | Base of kernel address space | 0xffff800000000000 |
| kernel-aspace-size | Size of kernel address space | 0x7fffffffff000 |
Sources: configs/platforms/x86_64-qemu-q35.toml(L13 - L30) scripts/lds/linker.lds.S(L1 - L94)
Device Configuration
Device configuration defines the memory-mapped I/O (MMIO) regions, PCI configuration, and other hardware-specific settings for each platform.
flowchart TD
subgraph subGraph1["Platform-Specific Implementations"]
x86_impl["x86_64 ImplementationIO APIC, Local APIC, HPET"]
arm_impl["ARM ImplementationGICv2, ARM-specific timers"]
riscv_impl["RISC-V ImplementationSBI runtime, PLIC"]
end
subgraph subGraph0["Device Configuration Components"]
mmio["MMIO RegionsPhysical address regionsfor memory-mapped devices"]
virtio["VirtIO MMIO RegionsRegions for virtualized I/O devices"]
pci["PCI ConfigurationECAM base, bus numbers,memory ranges"]
timer["Timer SettingsFrequency and configuration"]
end
mmio --> arm_impl
mmio --> riscv_impl
mmio --> x86_impl
pci --> x86_impl
timer --> arm_impl
timer --> riscv_impl
timer --> x86_impl
virtio --> arm_impl
virtio --> riscv_impl
virtio --> x86_impl
Sources: configs/platforms/x86_64-qemu-q35.toml(L34 - L58) configs/vms/arceos-x86_64.toml(L40 - L77) configs/vms/nimbos-x86_64.toml(L40 - L77)
MMIO Regions
The mmio-regions parameter defines the memory ranges reserved for memory-mapped devices. For x86_64-qemu-q35, these include:
- PCI config space: 0xb0000000 - 0xc0000000
- PCI devices: 0xfe000000 - 0xfec00000
- IO APIC: 0xfec00000 - 0xfec01000
- HPET: 0xfed00000 - 0xfed01000
- Local APIC: 0xfee00000 - 0xfee01000
Sources: configs/platforms/x86_64-qemu-q35.toml(L36 - L45)
PCI Configuration
PCI configuration varies by platform. For x86_64-qemu-q35, it includes:
- PCI ECAM base: 0xb0000000
- PCI bus end: 0xff
Sources: configs/platforms/x86_64-qemu-q35.toml(L49 - L54)
Timer Configuration
Platform-specific timer configuration is essential for proper scheduling and timing in the hypervisor. The configuration specifies the timer frequency and related parameters.
flowchart TD
subgraph subGraph0["Timer Configuration"]
timer_config["Platform Timer Configtimer-frequency"]
periodic_interval["Periodic IntervalPERIODIC_INTERVAL_NANOS"]
scheduler_next["scheduler_next_event()"]
register_timer["register_timer()"]
check_events["check_events()"]
end
periodic_interval --> scheduler_next
register_timer --> check_events
scheduler_next --> check_events
timer_config --> periodic_interval
Sources: configs/platforms/x86_64-qemu-q35.toml(L57) src/vmm/timer.rs(L1 - L105)
Timer Implementation
The timer subsystem is implemented in src/vmm/timer.rs and uses platform-specific timers through the Hardware Abstraction Layer (HAL). For x86_64-qemu-q35, the timer frequency is set at 4 GHz.
The timer system uses the following key parameters:
timer-frequency: Base frequency of the platform timer (e.g., 4 GHz for x86_64-qemu-q35)PERIODIC_INTERVAL_NANOS: Derived fromaxconfig::TICKS_PER_SECandaxhal::time::NANOS_PER_SEC
Sources: src/vmm/timer.rs(L12) configs/platforms/x86_64-qemu-q35.toml(L57)
Relationship with VM Configuration
Platform-specific configurations form the foundation for VM configurations. VM configurations reference platform settings and may override or extend them for specific virtual machines.
flowchart TD
subgraph subGraph1["VM Config"]
vm_base["VM Base Infoid, name, cpu_num"]
vm_kernel["Kernel Configentry_point, image_location,kernel_path, kernel_load_addr"]
vm_memory["Memory Regionsbase_paddr, size, flags, map_type"]
vm_devices["Device Specsemu_devices, passthrough_devices"]
end
subgraph subGraph0["Platform Config"]
plat_arch["arch: x86_64"]
plat_platform["platform: x86_64-qemu-q35"]
plat_memory["Memory LayoutPhysical and virtualaddress spaces"]
plat_devices["Device ConfigurationMMIO regions, PCI, timers"]
end
plat_arch --> vm_base
plat_devices --> vm_devices
plat_memory --> vm_memory
plat_platform --> vm_base
Sources: configs/vms/arceos-x86_64.toml(L1 - L78) configs/vms/nimbos-x86_64.toml(L1 - L78) configs/platforms/x86_64-qemu-q35.toml(L1 - L58)
Memory Region Configuration in VMs
VM configurations specify memory regions that must be compatible with the platform's memory layout. For example, a VM configuration might include:
memory_regions = [
[0x0000_0000, 0x100_0000, 0x7, 0], # Low RAM 16M 0b111 R|W|EXECUTE
]
This defines a 16MB region starting at physical address 0, with read, write, and execute permissions (flags 0x7), using allocation mapping (map_type 0).
Sources: configs/vms/arceos-x86_64.toml(L42 - L44) configs/vms/nimbos-x86_64.toml(L42 - L44)
Device Passthrough in VMs
Platform-specific device configurations determine which devices can be passed through to VMs. The VM configuration specifies which devices to pass through and how to map them:
passthrough_devices = [
["IO APIC", 0xfec0_0000, 0xfec0_0000, 0x1000, 0x1],
["Local APIC", 0xfee0_0000, 0xfee0_0000, 0x1000, 0x1],
["HPET", 0xfed0_0000, 0xfed0_0000, 0x1000, 0x1],
]
Each entry specifies:
- Device name
- Base IPA (Intermediate Physical Address) in guest
- Base physical address in host
- Length of memory region
- Interrupt allocation flag
Sources: configs/vms/arceos-x86_64.toml(L55 - L77) configs/vms/nimbos-x86_64.toml(L55 - L77)
Architecture-Specific Considerations
Each supported architecture has specific configuration requirements and capabilities.
x86_64 Architecture
For x86_64 platforms, important considerations include:
- APIC configuration (IO APIC and Local APIC)
- HPET timer configuration
- PCI configuration space mapping
- Boot page table setup
Sources: configs/platforms/x86_64-qemu-q35.toml(L1 - L58) configs/vms/arceos-x86_64.toml(L55 - L77) configs/vms/nimbos-x86_64.toml(L55 - L77)
ARM/aarch64 Architecture
While not explicitly shown in the provided files, support for ARM architecture is indicated in the Cargo.toml file with features like "arm-el2". ARM platforms would have specific considerations for:
- GIC (Generic Interrupt Controller) configuration
- ARM-specific timer devices
- EL2 (Hypervisor) mode configuration
Sources: Cargo.toml(L43 - L45)
RISC-V Architecture
RISC-V support is mentioned in the system overview diagrams. RISC-V platforms would have specific considerations for:
- SBI (Supervisor Binary Interface) runtime configuration
- PLIC (Platform-Level Interrupt Controller) setup
- RISC-V privilege modes
Build Integration
The platform-specific configuration is integrated into the build process through the axconfig build dependency, which processes the configuration files at build time.
flowchart TD
subgraph subGraph1["Configuration Sources"]
platform_config["Platform Config Filesconfigs/platforms/*.toml"]
linker_script["Linker Scriptscripts/lds/linker.lds.S"]
end
subgraph subGraph0["Build Process"]
toml_parser["TOML Parser"]
axconfig["axconfig Tool"]
build_system["Build System"]
final_binary["Final Binary"]
end
axconfig --> build_system
axconfig --> linker_script
build_system --> final_binary
linker_script --> build_system
platform_config --> toml_parser
toml_parser --> axconfig
Sources: Cargo.toml(L47 - L52) scripts/lds/linker.lds.S(L1 - L94)
Building and Running
Relevant source files
This document provides detailed instructions on how to build AxVisor from source and run it with different guest virtual machines. For information about VM configuration options, see Configuration.
Prerequisites
Before building and running AxVisor, you need to set up your development environment with the following tools:
- Rust development environment
- cargo-binutils
- QEMU (for testing)
- Optional: musl-gcc (for building guest applications)
$ cargo install cargo-binutils
Sources: README.md(L64 - L68)
Setting up the Development Environment
Automated Setup
AxVisor provides a Python script that automates the process of cloning dependent repositories and configuring the development environment.
$ python tool/dev_env.py
This script will:
- Create a
cratesdirectory - Clone all required repositories
- Patch your Cargo.toml file
- Set up VSCode settings (if applicable)
You can specify a different repository URL using the --repo argument:
$ python tool/dev_env.py --repo https://github.com/your-fork/arceos-hypervisor
Sources: tool/dev_env.py(L1 - L106)
Build System Overview
Build Process Diagram
flowchart TD
subgraph subGraph2["Make Targets"]
make_build["make build"]
make_run["make run"]
make_debug["make debug"]
make_clean["make clean"]
end
subgraph subGraph1["Configuration Inputs"]
arch_opt["Architecture Options(x86_64, aarch64, riscv64)"]
plat_opt["Platform Options"]
feature_opt["Feature Options"]
vm_config["VM Configuration Files"]
end
subgraph subGraph0["Build Process"]
config["Configuration Generation"]
build["Build AxVisor"]
output["Output: ELF and Binary Files"]
end
binary["Binary file:APP_NAME_PLATFORM.bin"]
elf["ELF file:APP_NAME_PLATFORM.elf"]
asm["Assembly file:APP_NAME_PLATFORM.asm"]
arch_opt --> config
build --> output
config --> build
feature_opt --> config
make_build --> build
make_debug --> build
make_run --> build
output --> asm
output --> binary
output --> elf
plat_opt --> config
vm_config --> config
Sources: Makefile(L1 - L151) README.md(L57 - L59)
Building AxVisor
AxVisor uses a Makefile-based build system with numerous configuration options. The basic build command is:
$ make ARCH=<architecture> VM_CONFIGS=<config_path> build
Key Build Options
| Option | Description | Example Values |
|---|---|---|
| ARCH | Target architecture | x86_64, aarch64, riscv64 |
| PLATFORM | Target platform | aarch64-qemu-virt-hv |
| SMP | Number of CPUs | 1, 2, 4, etc. |
| MODE | Build mode | release, debug |
| LOG | Logging level | warn, error, info, debug, trace |
| VM_CONFIGS | Path to VM configuration files | configs/vms/arceos-aarch64.toml |
| FEATURES | Features to enable | fs |
| APP_FEATURES | App-specific features | fs |
| MEM | Memory size | 128M, 4G |
Sources: Makefile(L4 - L32) README.md(L57 - L59)
Example Build Commands
Build for aarch64 architecture with specific VM configurations:
$ make ARCH=aarch64 VM_CONFIGS=configs/vms/arceos-aarch64.toml build
Build with filesystem support:
$ make ARCH=aarch64 VM_CONFIGS=configs/vms/arceos-aarch64.toml APP_FEATURES=fs build
Sources: README.md(L99 - L112)
Running AxVisor
After building AxVisor, you can run it using QEMU. AxVisor supports two methods for loading guest VM images:
- Loading from a filesystem
- Loading from memory (statically compiled)
Runtime Configuration Diagram
flowchart TD
subgraph subGraph2["Memory Loading"]
buildImage["Build guest VM image"]
specifyPath["Specify kernel_path in config"]
end
subgraph subGraph1["Filesystem Loading"]
prepareDisk["Create disk.img with FAT32"]
mountDisk["Mount disk.img"]
copyImage["Copy guest VM image to disk"]
umountDisk["Unmount disk"]
end
subgraph subGraph0["AxVisor Runtime Configuration"]
startAxVisor["Start AxVisor"]
loadConfig["Load VM Configuration Files"]
checkImageLocation["Check image_location in config"]
loadFromFs["Load Guest from Filesystem"]
loadFromMem["Load Guest from Memory"]
setupVM["Setup VM"]
runVM["Run VM"]
end
buildImage --> specifyPath
checkImageLocation --> loadFromFs
checkImageLocation --> loadFromMem
copyImage --> umountDisk
loadConfig --> checkImageLocation
loadFromFs --> prepareDisk
loadFromFs --> setupVM
loadFromMem --> buildImage
loadFromMem --> setupVM
mountDisk --> copyImage
prepareDisk --> mountDisk
setupVM --> runVM
startAxVisor --> loadConfig
Sources: README.md(L59 - L112)
Loading and Running from Filesystem
- Build a guest VM image for your architecture
- Create a disk image and place the guest VM image into it:
$ make disk_img # Creates an empty FAT32 disk image
$ mkdir -p tmp
$ sudo mount disk.img tmp
$ sudo cp /PATH/TO/YOUR/GUEST/VM/IMAGE tmp/
$ sudo umount tmp
- Modify the VM configuration file in
configs/vms/<ARCH_CONFIG>.toml:
- Set
image_location="fs" - Set
kernel_pathto the path of the kernel image in the filesystem - Set
entry_pointto the entry address of the kernel image - Set
kernel_load_addrto the loading address of the kernel image
- Run AxVisor with the configured guest VM:
$ make ACCEL=n ARCH=aarch64 LOG=info VM_CONFIGS=configs/vms/arceos-aarch64.toml APP_FEATURES=fs run
Sources: README.md(L76 - L99)
Loading and Running from Memory
- Build a guest VM image for your architecture
- Modify the VM configuration file in
configs/vms/<ARCH_CONFIG>.toml:
- Set
image_location="memory" - Set
kernel_pathto the relative/absolute path of the kernel image in the workspace - Set
entry_pointto the entry address of the kernel image - Set
kernel_load_addrto the loading address of the kernel image
- Run AxVisor with the configured guest VM:
$ make ACCEL=n ARCH=aarch64 LOG=info VM_CONFIGS=configs/vms/arceos-aarch64.toml run
Sources: README.md(L101 - L112)
Guest VM Support
AxVisor has been verified to work with the following guest operating systems:
| Guest OS | Architecture | Features | Configuration Template |
|---|---|---|---|
| ArceOS | x86_64, aarch64, riscv64 | SMP support | configs/vms/arceos-*.toml |
| Starry-OS | x86_64, aarch64, riscv64 | - | - |
| NimbOS | x86_64, aarch64, riscv64 | Single-core only | configs/vms/nimbos-*.toml |
| Linux | aarch64 | Passthrough device | configs/vms/linux-qemu-aarch64.toml |
Sources: README.md(L45 - L56) doc/GuestVMs.md(L1 - L56)
Hardware Platform Support
AxVisor has been verified on the following platforms:
- QEMU ARM64 virt (qemu-max)
- Rockchip RK3568 / RK3588
- 黑芝麻华山 A1000 (Black Sesame A1000)
Sources: README.md(L37 - L44)
Debugging
AxVisor provides several targets for debugging:
- To build and run the system with debugging support:
$ make ARCH=<architecture> VM_CONFIGS=<config_path> debug
- To attach GDB to a running instance:
$ make gdb
This will connect GDB to the running QEMU instance and set a breakpoint at the rust_entry function.
Sources: Makefile(L175 - L182)
Troubleshooting and Development
Common Issues
- Make sure your QEMU version is up-to-date
- For hardware acceleration, ensure KVM is enabled on your system
- VM configuration paths must be correctly specified
CI/CD Environment
The project uses GitHub Actions for continuous integration and testing. The CI setup includes:
- Testing on multiple architectures (x86_64, aarch64, riscv64)
- Setting up QEMU with necessary configurations
- Remote testing for hardware-specific features
For more information about the remote CI setup, see .github/workflows/REMOTE-CI.md
Sources: .github/workflows/actions/setup-qemu/action.yml(L1 - L48) .github/workflows/REMOTE-CI.md(L1 - L50)
Advanced Configuration
For advanced configuration needs, including detailed VM settings, passthrough devices, and architecture-specific options, refer to VM Configuration and Platform-Specific Configuration.
Guest VMs
Relevant source files
This page documents the guest operating systems supported by AxVisor, how to configure them, and methods for loading and running guest VM images. For information about setting up the development environment for AxVisor itself, see Development Environment.
Overview of Supported Guest VMs
AxVisor currently supports running the following operating systems as guest VMs:
| Guest OS | Architecture Support | Features | Use Case |
|---|---|---|---|
| ArceOS | x86_64, aarch64, riscv64 | SMP, Hypercalls | SMP testing, General purpose |
| NimbOS | x86_64, aarch64, riscv64 | Simple RTOS | Single-core testing |
| Starry-OS | x86_64, aarch64 | Educational OS | Educational purposes |
| Linux | aarch64 | Passthrough devices | Production workloads |
Sources: README.md(L46 - L55) doc/GuestVMs.md(L1 - L13)
Guest VM Architecture
Relationship Between AxVisor and Guest VMs
flowchart TD
subgraph subGraph1["Guest Virtual Machines"]
vm1["VM: ArceOS"]
vm2["VM: NimbOS"]
vm3["VM: Linux"]
vm4["VM: Starry-OS"]
end
subgraph subGraph0["AxVisor Hypervisor Components"]
axvm["axvm - VM Management"]
axvcpu["axvcpu - Virtual CPU"]
axaddrspace["axaddrspace - Memory Management"]
axdevice["axdevice - Device Emulation"]
end
axaddrspace --> vm1
axaddrspace --> vm2
axaddrspace --> vm3
axaddrspace --> vm4
axdevice --> vm1
axdevice --> vm2
axdevice --> vm3
axdevice --> vm4
axvcpu --> vm1
axvcpu --> vm2
axvcpu --> vm3
axvcpu --> vm4
axvm --> vm1
axvm --> vm2
axvm --> vm3
axvm --> vm4
Sources: README.md(L30 - L36)
Guest VM Boot Process
sequenceDiagram
participant TOMLConfiguration as "TOML Configuration"
participant VMM as "VMM"
participant VMInstance as "VM Instance"
participant VCPU as "VCPU"
participant GuestOS as "Guest OS"
TOMLConfiguration ->> VMM: Load VM configuration
VMM ->> VMInstance: Create VM instance
VMM ->> VMInstance: Setup memory regions
alt Load from filesystem
VMM ->> VMInstance: Load kernel from FAT32 filesystem
else Load from memory
VMM ->> VMInstance: Load kernel from statically compiled image
end
VMM ->> VCPU: Create and setup VCPUs
VMM ->> VMInstance: Boot VM
VMInstance ->> VCPU: Notify primary VCPU
VCPU ->> GuestOS: Start guest execution
loop Until VM
loop shutdown
VCPU ->> VCPU: Execute guest code
VCPU ->> VCPU: Handle VM exits
end
end
Sources: README.md(L58 - L112)
Guest Operating Systems in Detail
ArceOS
ArceOS is a modular, arch-agnostic operating system focused on embedded systems, and it's also the foundation of AxVisor itself. As a guest VM, it's primarily used for SMP (Symmetric Multi-Processing) testing.
Key features when running as a guest:
- Supports x86_64, aarch64, and riscv64 architectures
- SMP support for testing multi-core scenarios
- Hypercall support for communication with the hypervisor
Configuration templates are available at:
configs/vms/arceos-aarch64.tomlconfigs/vms/arceos-x86_64.tomlconfigs/vms/arceos-riscv64.toml
ArceOS Testcases
- Hypercall Testing: A HelloWorld application is available that tests hypercall functionality.
- Virtio-PCI Device Testing: The PCI branch can be used for testing virtio-pci devices through port I/O.
Sources: doc/GuestVMs.md(L10 - L23)
NimbOS
NimbOS is a simple real-time operating system designed primarily for single-core testing scenarios. Its simplicity makes it ideal for testing basic hypervisor functionality.
Key features:
- Single-core operation
- Support for x86_64, aarch64, and riscv64 architectures
- Simple design makes it suitable for basic functionality testing
Configuration templates are available at:
configs/vms/nimbos-aarch64.tomlconfigs/vms/nimbos-x86_64.tomlconfigs/vms/nimbos-riscv64.toml
Pre-built kernel binary images are available from the [NimbOS releases page](https://github.com/arceos-hypervisor/axvisor/blob/0c9b89a5/NimbOS releases page)
Sources: doc/GuestVMs.md(L3 - L8)
Starry-OS
Starry-OS is an educational operating system that can be run as a guest VM on AxVisor. While less information is provided about its specific use cases compared to other guest OSes, it's included as a supported guest system.
Sources: README.md(L50)
Linux
Linux is supported as a guest OS, primarily on the aarch64 architecture with passthrough devices. This allows for running production workloads in a virtualized environment.
Currently, Linux with passthrough devices on aarch64 has been tested in:
- Single-core configuration: Configuration available at
configs/vms/linux-qemu-aarch64.tomlwith device tree atconfigs/vms/linux-qemu.dts - SMP configuration: Configuration available at
configs/vms/linux-qemu-aarch64-smp2.tomlwith device tree atconfigs/vms/linux-qemu-smp2.dts
Special instructions for running Linux on RK3588 boards are also available.
Sources: README.md(L52 - L55) doc/GuestVMs.md(L30 - L44)
axvm-bios (Bootloader)
For x86_64 guests, a simple BIOS called axvm-bios is available. This extremely simple BIOS acts as a bootloader for NimbOS and ArceOS on x86_64 platforms.
Pre-built binary is available from the [axvm-bios releases page](https://github.com/arceos-hypervisor/axvisor/blob/0c9b89a5/axvm-bios releases page)
Sources: doc/GuestVMs.md(L24 - L28)
Guest VM Configuration
Configuration File Structure
VM configurations in AxVisor are managed through TOML files, which specify parameters such as:
- VM ID and name
- Number of virtual CPUs
- Memory regions
- Virtual devices
- Passthrough devices
- Guest image location and loading information
flowchart TD
subgraph subGraph3["VM Configuration File"]
id["vm_id = 1"]
name["name = 'arceos'"]
vcpus["cpu_num = 1"]
subgraph subGraph1["Image Configuration"]
passthrough["passthrough_devices"]
emulated["emulated_devices"]
img_loc["image_location = 'fs'/'memory'"]
kernel_path["kernel_path = '/path/to/image'"]
entry["entry_point = 0x40080000"]
load_addr["kernel_load_addr = 0x40080000"]
dtb_path["dtb_path (optional)"]
mem1["memory_region_1"]
mem2["memory_region_2"]
subgraph Devices["Devices"]
subgraph subGraph0["Memory Regions"]
passthrough["passthrough_devices"]
emulated["emulated_devices"]
img_loc["image_location = 'fs'/'memory'"]
kernel_path["kernel_path = '/path/to/image'"]
mem1["memory_region_1"]
mem2["memory_region_2"]
end
end
end
end
Example configuration templates can be found in the configs/vms/ directory, with specific templates for each supported guest OS and architecture combination.
Sources: README.md(L70 - L74)
Loading and Running Guest VMs
AxVisor supports two primary methods for loading guest VM images:
Loading from a Filesystem
This method involves loading the guest image from a FAT32 filesystem:
- Build a client image file for your target architecture
- Create a disk image file and place the guest machine image into it:
- Generate an empty FAT32 disk image using
make disk_img - Mount the disk image and copy the guest image into it
- Configure the VM in the TOML file:
- Set
image_location = "fs"to indicate loading from the filesystem - Set
kernel_pathto the path of the kernel image in the filesystem - Configure
entry_pointandkernel_load_addrappropriately
- Build and run AxVisor with the appropriate configuration
make ACCEL=n ARCH=aarch64 LOG=info VM_CONFIGS=configs/vms/arceos-aarch64.toml APP_FEATURES=fs run
Sources: README.md(L76 - L99)
Loading from Memory
This method involves statically compiling the guest image into the hypervisor:
- Build a client image file for your target architecture
- Configure the VM in the TOML file:
- Set
image_location = "memory"to indicate loading from memory - Set
kernel_pathto the path of the kernel image in your workspace - Configure
entry_pointandkernel_load_addrappropriately
- Build and run AxVisor with the appropriate configuration
make ACCEL=n ARCH=aarch64 LOG=info VM_CONFIGS=configs/vms/arceos-aarch64.toml run
Sources: README.md(L101 - L112)
Platform-Specific Guest Support
RK3588 Board Support
AxVisor has been verified on the Rockchip RK3588 platform for running Linux guests. The process involves:
- Preparing the kernel file (
linux-rk3588-aarch64.bin) and DTB file (rk3588.dtb) - Configuring the paths in
configs/vms/linux-rk3588-aarch64.toml - Building the kernel image with
make A=(pwd) ARCH=aarch64 VM_CONFIGS=configs/vms/linux-rk3588-aarch64.toml kernel - Using RKDevTool to flash the image to the RK3588 board
Sources: doc/GuestVMs.md(L30 - L44) README.md(L37 - L44)
Guest VM Testing
For effective testing of guest VMs, several specific test configurations are available:
- Hypercall Tests: Using ArceOS HelloWorld application to test hypercall functionality
- PCI Device Tests: Using specific branches of ArceOS to test virtio-pci devices
- Single-core Tests: Using NimbOS for basic single-core functionality testing
- SMP Tests: Using ArceOS for multi-core functionality testing
Sources: doc/GuestVMs.md(L16 - L23)
Development Environment
Relevant source files
This document outlines how to set up a development environment for AxVisor, a unified modular hypervisor based on ArceOS. It covers the necessary tools, dependencies, repository setup, and IDE configuration to effectively contribute to the project. For information about building and running AxVisor with guest VMs, see Building and Running.
Prerequisites
Before setting up the development environment for AxVisor, you need to install the following dependencies:
- Rust Programming Language: The core language used to develop AxVisor
- cargo-binutils: Required for tools like
rust-objcopyandrust-objdump - musl-gcc (Optional): Needed when building certain guest applications
Installing Prerequisites
# Install Rust (if not already installed)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Install cargo-binutils
cargo install cargo-binutils
# Install musl-gcc (optional)
# Download from http://musl.cc/x86_64-linux-musl-cross.tgz if needed
Sources: README.md(L63 - L68)
Development Environment Setup Script
AxVisor provides a Python script (tool/dev_env.py) to simplify the development environment setup. This script:
- Clones required repositories
- Patches Cargo.toml to use local dependencies
- Creates IDE configuration for VS Code
Development Environment Structure
The diagram below illustrates the structure created by the development environment setup:
flowchart TD
subgraph subGraph0["AxVisor Development Environment"]
Root["AxVisor Repository"]
Crates["crates/ Directory"]
VSCode[".vscode/ Directory"]
CargoConfig["Cargo.toml"]
CargoBackup["Cargo.toml.bk"]
Repo1["arceos"]
Repo2["axvm"]
Repo3["axvcpu"]
Repo4["axaddrspace"]
Repo5["arm_vcpu"]
Repo6["axdevice"]
Repo7["arm_vgic"]
Repo8["arm_gicv2"]
Repo9["axdevice_crates"]
Settings["settings.json"]
end
Crates --> Repo1
Crates --> Repo2
Crates --> Repo3
Crates --> Repo4
Crates --> Repo5
Crates --> Repo6
Crates --> Repo7
Crates --> Repo8
Crates --> Repo9
Root --> CargoBackup
Root --> CargoConfig
Root --> Crates
Root --> VSCode
VSCode --> Settings
Sources: tool/dev_env.py(L1 - L106) .gitignore(L20 - L21)
Running the Setup Script
To set up the development environment, run:
python3 tool/dev_env.py
By default, the script clones repositories from git@github.com:arceos-hypervisor. You can specify a different repository base using the --repo parameter:
python3 tool/dev_env.py --repo https://github.com/arceos-hypervisor
Sources: tool/dev_env.py(L7 - L25)
Dependencies and Repository Structure
The following diagram shows the core dependencies that are cloned during the setup process and how they relate to the main AxVisor repository:
flowchart TD
subgraph Repositories["Repositories"]
axvisor["axvisor (main)"]
arceos["arceos(ArceOS unikernel framework)"]
axvm["axvm(Virtual Machine Management)"]
axvcpu["axvcpu(Virtual CPU Interface)"]
axaddrspace["axaddrspace(Address Space Management)"]
axdevice["axdevice(Device Emulation)"]
arm_vcpu["arm_vcpu(ARM VCPU Implementation)"]
arm_vgic["arm_vgic(ARM Virtual GIC)"]
arm_gicv2["arm_gicv2(ARM GICv2 Support)"]
axdevice_crates["axdevice_crates(Device Model Base)"]
end
arm_vcpu --> arm_vgic
arm_vgic --> arm_gicv2
axdevice --> axdevice_crates
axvcpu --> arm_vcpu
axvisor --> arceos
axvisor --> axaddrspace
axvisor --> axdevice
axvisor --> axvcpu
axvisor --> axvm
Sources: tool/dev_env.py(L30 - L48)
Cargo.toml Patching Process
The development environment script modifies the Cargo.toml file to use local paths for dependencies instead of fetching them from GitHub. This allows you to make changes to these dependencies locally and test them immediately.
The patching process works as follows:
flowchart TD
subgraph subGraph0["Patch Example"]
E["[patch.'Unsupported markdown: linkpath = 'crates/axvm'"]
end
A["Read original Cargo.toml"]
B["Backup to Cargo.toml.bk"]
C["Add patch sections"]
D["Write modified Cargo.toml"]
A --> B
B --> C
C --> D
The script adds patch sections for all dependencies, redirecting them to local paths in the crates/ directory.
Sources: tool/dev_env.py(L52 - L84)
VS Code Configuration
The development environment script creates a .vscode/settings.json file with configuration for Rust Analyzer:
{
"rust-analyzer.cargo.target": "aarch64-unknown-none-softfloat",
"rust-analyzer.check.allTargets": false,
"rust-analyzer.cargo.features": ["fs"],
"rust-analyzer.cargo.extraEnv": {
"AX_CONFIG_PATH": "${workspaceFolder}/.axconfig.toml"
}
}
This configuration:
- Sets the target architecture to
aarch64-unknown-none-softfloat - Enables the
fsfeature for Cargo - Sets the
AX_CONFIG_PATHenvironment variable for configuration
Sources: tool/dev_env.py(L86 - L100)
Development Workflow
Once your development environment is set up, you can start working on AxVisor:
- Make changes to the code in the main repository or any of the dependencies in the
crates/directory - Build and test using the Makefile commands as described in Building and Running
- Use guest VM configurations from the
configs/vms/directory for testing
Typical Development Cycle
flowchart TD A["Setup dev environmentwith dev_env.py"] B["Make code changes"] C["Build with make"] D["Test with QEMU"] A --> B B --> C C --> D D --> B
Editing Dependency Code
With the development environment set up, you can edit code in any of the cloned repositories. Changes to dependencies will be immediately reflected when you build the main project, without needing to publish new versions of those dependencies.
Key directories to work with:
crates/arceos/: The ArceOS unikernel frameworkcrates/axvm/: Virtual machine managementcrates/axvcpu/: Virtual CPU interfacecrates/axaddrspace/: Address space managementcrates/arm_vcpu/: ARM VCPU implementation (when working on ARM support)
Troubleshooting
Common Issues
- Git clone failures: Ensure you have proper SSH keys set up if using SSH URLs, or use HTTPS URLs with the
--repooption. - Rust compiler errors: Ensure you have the correct Rust toolchain installed. You may need to run:
rustup target add aarch64-unknown-none-softfloat
- Build errors after pulling updates: If updates to dependencies cause build errors, try running the setup script again to ensure all dependencies are in sync.
Cleaning the Environment
If you need to reset your development environment:
- Remove the
crates/directory:
rm -rf crates/
- Restore the original Cargo.toml:
mv Cargo.toml.bk Cargo.toml
- Run the setup script again:
python3 tool/dev_env.py
Sources: .gitignore(L20 - L21)
Next Steps
After setting up your development environment, you can proceed to:
- Learn more about the AxVisor Architecture
- Configure Guest VMs
- Contribute to the project by following the Contributing guidelines
Technical Reference
Relevant source files
This document provides detailed technical information about the internal implementation of AxVisor, the ArceOS hypervisor. It covers core components, execution models, and internal data structures used by the hypervisor. For information about building and running AxVisor, see Building and Running, and for configuration details, see Configuration.
1. VMM Implementation
The Virtual Machine Manager (VMM) is the core component of AxVisor responsible for managing virtual machines throughout their lifecycle. It handles VM initialization, booting, execution, and shutdown.
flowchart TD
subgraph subGraph1["VM States"]
vminit["VM::new()"]
vmboot["VM::boot()"]
vmrun["VM running"]
vmshutdown["VM::shutdown()"]
end
subgraph subGraph0["VMM Lifecycle"]
init["VMM::init()"]
config["config::init_guest_vms()"]
setup["vcpus::setup_vm_primary_vcpu()"]
start["VMM::start()"]
boot["vm.boot() for each VM"]
notify["vcpus::notify_primary_vcpu()"]
inc["Increment RUNNING_VM_COUNT"]
wait["Wait until all VMs stopped"]
dec["RUNNING_VM_COUNT == 0"]
end
boot --> inc
boot --> notify
config --> setup
init --> config
init --> vminit
notify --> vmrun
start --> boot
start --> vmboot
vmshutdown --> dec
wait --> dec
Sources: src/vmm/mod.rs(L28 - L65)
1.1 VM Initialization Process
The VMM initializes VMs through the following steps:
- Loads VM configuration from TOML files via
config::init_guest_vms() - Creates VM instances using
VM::new() - Loads VM images according to configuration
- Sets up the primary VCPU for each VM using
vcpus::setup_vm_primary_vcpu()
The RUNNING_VM_COUNT atomic counter tracks active VMs, allowing the VMM to determine when all VMs have stopped.
sequenceDiagram
participant VMM as VMM
participant Configuration as Configuration
participant VirtualMachine as Virtual Machine
participant VirtualCPU as Virtual CPU
VMM ->> Configuration: init_guest_vms()
Configuration ->> Configuration: static_vm_configs()
Configuration ->> Configuration: AxVMCrateConfig::from_toml()
Configuration ->> VirtualMachine: VM::new(vm_config)
Configuration ->> VirtualMachine: load_vm_images()
VMM ->> VirtualCPU: setup_vm_primary_vcpu()
VMM ->> VirtualMachine: vm.boot()
VMM ->> VirtualCPU: notify_primary_vcpu()
VMM ->> VMM: RUNNING_VM_COUNT.fetch_add(1)
Note over VMM: Wait for all VMs to stop
Sources: src/vmm/mod.rs(L28 - L65) src/vmm/config.rs(L25 - L43)
2. VCPU Management System
The VCPU management system handles the creation, execution, and synchronization of virtual CPUs. Each VCPU runs as a separate task in the ArceOS task scheduler.
2.1 VCPU Task Structure
Each VCPU is associated with a task that contains:
- Reference to the parent VM
- Reference to the VCPU itself
- Stack space for execution (256 KiB)
- Processor affinity settings (if configured)
classDiagram
class TaskExt {
VMRef vm
VCpuRef vcpu
+new(vm, vcpu)
}
class VMVCpus {
_vm_id: usize
wait_queue: WaitQueue
vcpu_task_list: Vec~AxTaskRef~
running_halting_vcpu_count: AtomicUsize
+new(vm)
+add_vcpu_task(vcpu_task)
+wait()
+wait_until(condition)
+notify_one()
+mark_vcpu_running()
+mark_vcpu_exiting() -~ bool
}
class VMRef {
}
class VCpuRef {
}
class AxTaskRef {
}
TaskExt "1" --> "1" VMRef : references
TaskExt "1" --> "1" VCpuRef : references
VMVCpus "1" --> "*" AxTaskRef : manages
Sources: src/task.rs(L1 - L19) src/vmm/vcpus.rs(L27 - L107)
2.2 VCPU Execution Flow
The VCPU execution flow follows these steps:
- Task is created for the VCPU with
alloc_vcpu_task() - VCPU task waits until the VM is in running state
- VCPU enters the execution loop via
vcpu_run() - VM executes guest code with
vm.run_vcpu(vcpu_id) - VCPU handles various exit reasons (hypercalls, interrupts, etc.)
- If the VM is shutting down, VCPU exits the loop
flowchart TD
subgraph subGraph1["Exit Reasons"]
hypercall["Hypercall"]
interrupt["External Interrupt"]
halt["Halt"]
cpuup["CPU Up"]
cpudown["CPU Down"]
sysdown["System Down"]
end
subgraph subGraph0["VCPU Task Lifecycle"]
alloc["alloc_vcpu_task()"]
task["Create task with vcpu_run() entry"]
wait["Wait for VM running state"]
mark["mark_vcpu_running()"]
loop["Enter execution loop"]
run["vm.run_vcpu()"]
handle["Handle exit reason"]
check["Check VM shutdown state"]
exit["Exit loop"]
dec["Decrement RUNNING_VM_COUNT if last VCPU"]
end
waitq["Wait on queue"]
create["Create new VCPU task"]
shutdown["vm.shutdown()"]
alloc --> task
check --> exit
check --> loop
cpuup --> create
create --> loop
exit --> dec
halt --> waitq
handle --> check
handle --> cpudown
handle --> cpuup
handle --> halt
handle --> hypercall
handle --> interrupt
handle --> sysdown
loop --> run
mark --> loop
run --> handle
shutdown --> check
sysdown --> shutdown
task --> wait
wait --> mark
waitq --> loop
Sources: src/vmm/vcpus.rs(L169 - L367)
2.3 Synchronization Mechanism
VCPUs are synchronized using a wait queue system:
- Each VM has a
VMVCpusstructure containing a wait queue - VCPUs can wait on the queue using
wait()orwait_until() - Other VCPUs can wake waiting VCPUs using
notify_one() - The primary VCPU is notified when the VM boots
The system also tracks running VCPUs with an atomic counter to determine when all VCPUs in a VM have exited.
sequenceDiagram
participant VMM as VMM
participant VirtualMachine as Virtual Machine
participant PrimaryVCPU as Primary VCPU
participant SecondaryVCPU as Secondary VCPU
participant WaitQueue as Wait Queue
VMM ->> VirtualMachine: boot()
VMM ->> WaitQueue: notify_primary_vcpu()
WaitQueue ->> PrimaryVCPU: wake up
PrimaryVCPU ->> VirtualMachine: run_vcpu()
PrimaryVCPU ->> SecondaryVCPU: vcpu_on() (CPU Up)
SecondaryVCPU ->> VirtualMachine: run_vcpu()
SecondaryVCPU ->> WaitQueue: wait() (Halt)
PrimaryVCPU ->> WaitQueue: notify_one()
WaitQueue ->> SecondaryVCPU: wake up
PrimaryVCPU ->> VirtualMachine: shutdown()
VirtualMachine ->> VirtualMachine: shutting_down = true
SecondaryVCPU ->> SecondaryVCPU: check VM state
SecondaryVCPU ->> VMM: mark_vcpu_exiting()
PrimaryVCPU ->> PrimaryVCPU: check VM state
PrimaryVCPU ->> VMM: mark_vcpu_exiting()
PrimaryVCPU ->> VMM: RUNNING_VM_COUNT.fetch_sub(1)
Sources: src/vmm/vcpus.rs(L110 - L167) src/vmm/mod.rs(L22 - L65)
3. Internal Data Structures
The hypervisor uses several key data structures to manage VMs and VCPUs.
3.1 VM and VCPU Types
VM = axvm::AxVM<AxVMHalImpl, AxVCpuHalImpl>
VMRef = axvm::AxVMRef<AxVMHalImpl, AxVCpuHalImpl>
VCpuRef = axvm::AxVCpuRef<AxVCpuHalImpl>
These types abstract the architecture-specific implementations behind common interfaces.
Sources: src/vmm/mod.rs(L16 - L20)
3.2 Global State Management
| Structure | Purpose | Implementation |
|---|---|---|
| RUNNING_VM_COUNT | Tracks number of running VMs | static AtomicUsize |
| VMM | Wait queue for VMM synchronization | static AxWaitQueueHandle |
| VM_VCPU_TASK_WAIT_QUEUE | Maps VM IDs to their VCPU wait queues | static mut BTreeMap<usize, VMVCpus> |
Sources: src/vmm/mod.rs(L22 - L25) src/vmm/vcpus.rs(L23)
3.3 Task Extension
The TaskExt structure associates an ArceOS task with VM and VCPU references:
pub struct TaskExt {
pub vm: VMRef,
pub vcpu: VCpuRef,
}
This allows the task scheduler to properly manage VCPU execution.
Sources: src/task.rs(L6 - L11)
4. VM Exit Handling
VM exits occur when the guest execution needs to be intercepted by the hypervisor. The hypervisor handles various exit reasons through the VCPU execution loop.
4.1 Exit Reason Processing
The following table shows the main exit reasons and their handling:
| Exit Reason | Description | Handling |
|---|---|---|
| Hypercall | Guest executed a hypercall | Log hypercall details and arguments |
| ExternalInterrupt | Interrupt received | Handle interrupt and continue execution |
| Halt | Guest halted CPU | VCPU waits on wait queue |
| CpuUp | Guest wants to start another CPU | Create new VCPU task for target CPU |
| CpuDown | Guest wants to stop a CPU | VCPU waits on wait queue |
| SystemDown | Guest wants to shutdown | Shutdown VM and exit VCPU task |
| FailEntry | Failed to enter VM | Log error and continue |
stateDiagram-v2 [*] --> Running Running --> Hypercall : Guest makes hypercall Hypercall --> Running : Process and continue Running --> Interrupt : External interrupt Interrupt --> Running : Handle interrupt Running --> Halted : Guest halts CPU Halted --> Running : Notified by other VCPU Running --> Creating : CPU Up request Creating --> Running : New VCPU created Running --> Waiting : CPU Down request Waiting --> Running : Notified Running --> Exiting : VM shutting down Exiting --> [*] : Last VCPU updates RUNNING_VM_COUNT
Sources: src/vmm/vcpus.rs(L290 - L363)
4.2 VCPU State Transitions
VCPUs transition through the following states during their lifecycle:
- Free: Initial state when VCPU is created
- Running: VCPU is executing guest code
- Halted: VCPU is waiting on a wait queue
- Exiting: VCPU is exiting due to VM shutdown
The mark_vcpu_running() and mark_vcpu_exiting() functions track these transitions.
Sources: src/vmm/vcpus.rs(L92 - L107) src/vmm/vcpus.rs(L155 - L167)
5. Architecture-Specific Components
AxVisor supports multiple CPU architectures through hardware abstraction layers.
5.1 HAL Implementation
The hypervisor uses architecture-specific implementations behind common interfaces:
classDiagram
class AxVMHalImpl {
<<HAL for VM operations>>
}
class AxVCpuHalImpl {
<<HAL for VCPU operations>>
}
class VM {
<<axvm::AxVM>>
+boot()
+shutdown()
+run_vcpu()
}
class VCpu {
<<axvm::AxVCpu>>
+run()
+set_entry()
+set_gpr()
}
VM *-- AxVMHalImpl
VCpu *-- AxVCpuHalImpl
The actual implementations vary by architecture (x86_64, ARM/aarch64, RISC-V) but present a unified interface.
Sources: src/vmm/mod.rs(L12 - L20)
5.2 Architecture-Specific Behaviors
Some behaviors vary by architecture:
#[cfg(target_arch = "riscv64")]
{
debug!(
"vcpu_on: vcpu[{}] entry={:x} opaque={:x}",
vcpu_id, entry_point, arg
);
vcpu.set_gpr(0, vcpu_id);
vcpu.set_gpr(1, arg);
}
This example shows RISC-V specific register setup for secondary VCPUs.
Sources: src/vmm/vcpus.rs(L193 - L201)
6. System Configuration
VM configurations are loaded from TOML files and used to initialize VMs.
6.1 Configuration Loading
Static VM configurations are included at compile time and parsed during VM initialization:
pub fn init_guest_vms() {
let gvm_raw_configs = config::static_vm_configs();
for raw_cfg_str in gvm_raw_configs {
let vm_create_config =
AxVMCrateConfig::from_toml(raw_cfg_str).expect("Failed to resolve VM config");
let vm_config = AxVMConfig::from(vm_create_config.clone());
// Create VM and load images
// ...
}
}
Architecture-specific configurations are selected based on the target architecture.
Sources: src/vmm/config.rs(L10 - L22) src/vmm/config.rs(L25 - L43)
6.2 Configuration Structure
VM configurations include:
- Basic properties (ID, name, CPU count)
- Memory regions
- Image location and loading parameters
- Device configurations
For more detailed information on configuration options, see VM Configuration.
VMM Implementation
Relevant source files
This document details the implementation of the Virtual Machine Manager (VMM) in AxVisor. The VMM is the core component responsible for creating, managing, and supervising virtual machines in the hypervisor. For information about specific VCPU management, see VCPU Management.
Overview
The VMM in AxVisor orchestrates the complete lifecycle of virtual machines, from initialization and booting to handling runtime events and shutdown. It provides a unified framework for managing VMs across multiple architectures (x86_64, aarch64, and RISC-V) while abstracting hardware-specific details.
flowchart TD
subgraph subGraph2["VM Tracking"]
running_vm_count["RUNNING_VM_COUNT"]
vmm_wait_queue["VMM Wait Queue"]
decrease_count["Decrease RUNNING_VM_COUNT"]
wake_vmm["Wake VMM Wait Queue"]
vmm_exit["VMM Exit"]
end
subgraph subGraph1["Runtime Execution"]
run_vcpu["vm.run_vcpu()"]
handle_exit["Handle Exit Reasons"]
hypercall["Hypercall"]
ext_interrupt["External Interrupt"]
halt["Halt"]
cpu_up["CPU Up"]
sys_down["System Down"]
end
subgraph subGraph0["VMM Implementation"]
vmm_init["VMM::init()"]
vmm_start["VMM::start()"]
config_init["config::init_guest_vms()"]
vcpu_setup["vcpus::setup_vm_primary_vcpu()"]
vcpu_notify["vcpus::notify_primary_vcpu()"]
vm_new["VM::new()"]
load_images["load_vm_images()"]
alloc_vcpu_task["alloc_vcpu_task()"]
vcpu_run["vcpu_run()"]
end
alloc_vcpu_task --> vcpu_run
config_init --> load_images
config_init --> vm_new
decrease_count --> wake_vmm
handle_exit --> cpu_up
handle_exit --> ext_interrupt
handle_exit --> halt
handle_exit --> hypercall
handle_exit --> sys_down
run_vcpu --> handle_exit
running_vm_count --> vmm_exit
sys_down --> decrease_count
vcpu_notify --> vcpu_run
vcpu_run --> run_vcpu
vcpu_setup --> alloc_vcpu_task
vmm_init --> config_init
vmm_init --> vcpu_setup
vmm_start --> vcpu_notify
Sources: src/vmm/mod.rs(L28 - L65) src/vmm/vcpus.rs(L268 - L367)
Key Components
The VMM implementation consists of several interconnected components that work together to manage virtual machines:
| Component | Description | Key Files |
|---|---|---|
| VMM Core | Initializes the VMM and starts VMs | src/vmm/mod.rs |
| VM Configuration | Loads and processes VM configurations | src/vmm/config.rs |
| VCPU Management | Manages VCPU tasks and execution | src/vmm/vcpus.rs |
| Task Extensions | Extends tasks to support VM and VCPU references | src/task.rs |
| VM List | Maintains a list of all VMs | src/vmm/vm_list.rs |
| Image Loading | Handles loading guest VM images | src/vmm/images.rs |
Sources: src/vmm/mod.rs(L1 - L7) src/task.rs(L1 - L19)
Initialization and Startup Flow
The VMM initialization and startup process follows a specific sequence to properly set up and boot virtual machines.
sequenceDiagram
participant MainApplication as "Main Application"
participant VMMModule as "VMM Module"
participant VMConfiguration as "VM Configuration"
participant VMInstance as "VM Instance"
participant VCPU as "VCPU"
participant TaskSystem as "Task System"
MainApplication ->> VMMModule: init()
VMMModule ->> VMConfiguration: init_guest_vms()
VMConfiguration ->> VMConfiguration: Load VM configs from TOML
loop For each VM config
VMConfiguration ->> VMInstance: VM::new(vm_config)
VMConfiguration ->> VMConfiguration: push_vm(vm)
VMConfiguration ->> VMConfiguration: load_vm_images(vm)
end
VMMModule ->> VMMModule: Setup VCPUs for each VM
loop For each VM
VMMModule ->> VCPU: setup_vm_primary_vcpu(vm)
VCPU ->> TaskSystem: alloc_vcpu_task(vm, vcpu)
TaskSystem -->> VCPU: Return task reference
end
MainApplication ->> VMMModule: start()
loop For each VM
VMMModule ->> VMInstance: boot()
VMMModule ->> VCPU: notify_primary_vcpu(vm.id())
VMMModule ->> VMMModule: Increment RUNNING_VM_COUNT
end
VMMModule ->> VMMModule: Wait for all VMs to stop
Note over VMMModule: Wait on VMM wait queue until RUNNING_VM_COUNT = 0
Sources: src/vmm/mod.rs(L28 - L65) src/vmm/config.rs(L25 - L43)
VMM Initialization
The VMM initialization process begins with the init() function, which performs two main tasks:
- VM Configuration Loading: The system loads VM configurations from TOML files and creates VM instances for each configuration.
- VCPU Setup: For each VM, the primary VCPU is set up, and a task is created to run it.
#![allow(unused)] fn main() { pub fn init() { // Initialize guest VM according to config file. config::init_guest_vms(); // Setup vcpus, spawn axtask for primary VCpu. info!("Setting up vcpus..."); for vm in vm_list::get_vm_list() { vcpus::setup_vm_primary_vcpu(vm); } } }
Sources: src/vmm/mod.rs(L28 - L39)
VMM Startup
After initialization, the start() function boots all VMs and waits for them to complete:
- VM Boot: Each VM is booted, and its primary VCPU is notified to start execution.
- VM Tracking: The system tracks the number of running VMs using the
RUNNING_VM_COUNTatomic counter. - Wait for Completion: The VMM waits on a wait queue until all VMs have stopped (
RUNNING_VM_COUNTreaches 0).
pub fn start() {
info!("VMM starting, booting VMs...");
for vm in vm_list::get_vm_list() {
match vm.boot() {
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);
info!("a VM exited, current running VM count: {}", vm_count);
vm_count == 0
},
None,
);
}
Sources: src/vmm/mod.rs(L42 - L65)
VCPU Task Management
The VCPU management system is a critical component of the VMM, responsible for setting up, scheduling, and managing VCPU tasks.
VCPU Task Structure
Each VCPU runs in its own task, managed by the task system. The task extensions (TaskExt) store references to both the VM and VCPU, allowing easy access to these components from within the task context.
flowchart TD
subgraph subGraph2["VM Access"]
vm["VM"]
vcpu["VCPU"]
end
subgraph subGraph1["VCPU Management"]
vm_vcpus["VMVCpus"]
wait_queue["WaitQueue"]
counter["AtomicUsize"]
end
subgraph subGraph0["Task System"]
task["AxTaskRef"]
task_ext["TaskExt"]
vm_ref["VMRef"]
vcpu_ref["VCpuRef"]
end
task --> task_ext
task_ext --> vcpu_ref
task_ext --> vm_ref
vcpu_ref --> vcpu
vm --> vcpu
vm_ref --> vm
vm_vcpus --> counter
vm_vcpus --> task
vm_vcpus --> wait_queue
Sources: src/task.rs(L1 - L19) src/vmm/vcpus.rs(L27 - L40)
VCPU Task Creation
The alloc_vcpu_task function creates a new task for a VCPU and initializes it with the appropriate VM and VCPU references:
- Create a new task with the
vcpu_runfunction as its entry point - Set the CPU mask if the VCPU has a dedicated physical CPU
- Initialize the task extension with VM and VCPU references
- Spawn the task on the scheduler
fn alloc_vcpu_task(vm: VMRef, vcpu: VCpuRef) -> AxTaskRef {
let mut vcpu_task = TaskInner::new(
vcpu_run,
format!("VM[{}]-VCpu[{}]", vm.id(), vcpu.id()),
KERNEL_STACK_SIZE,
);
if let Some(phys_cpu_set) = vcpu.phys_cpu_set() {
vcpu_task.set_cpumask(AxCpuMask::from_raw_bits(phys_cpu_set));
}
vcpu_task.init_task_ext(TaskExt::new(vm, vcpu));
axtask::spawn_task(vcpu_task)
}
Sources: src/vmm/vcpus.rs(L249 - L268)
VCPU Execution Flow
The execution of a VCPU is managed by the vcpu_run function, which runs in the context of the VCPU task.
flowchart TD start["vcpu_run() Start"] wait["Wait for VM Running State"] mark["Mark VCPU as Running"] run_loop["Enter VCPU Run Loop"] run_vcpu["vm.run_vcpu(vcpu_id)"] exit_handler["Handle Exit Reason"] handle_hypercall["Process Hypercall"] handle_interrupt["Process Interrupt"] handle_halt["Wait on VM Wait Queue"] handle_cpu_up["Boot Secondary VCPU"] handle_system_down["Shutdown VM"] check_shutdown["Check VM Shutting Down"] mark_exiting["Mark VCPU as Exiting"] decrease_count["Decrease RUNNING_VM_COUNT"] exit["Exit VCPU Task"] check_shutdown --> mark_exiting check_shutdown --> run_loop exit_handler --> handle_cpu_up exit_handler --> handle_halt exit_handler --> handle_hypercall exit_handler --> handle_interrupt exit_handler --> handle_system_down handle_cpu_up --> check_shutdown handle_halt --> check_shutdown handle_hypercall --> check_shutdown handle_interrupt --> check_shutdown handle_system_down --> check_shutdown mark --> run_loop mark_exiting --> decrease_count mark_exiting --> exit run_loop --> run_vcpu run_vcpu --> exit_handler start --> wait wait --> mark
Sources: src/vmm/vcpus.rs(L275 - L367)
VCPU Execution Loop
The VCPU execution loop performs the following steps:
- Wait for the VM to be in a running state
- Mark the VCPU as running
- Enter the main execution loop:
- Run the VCPU
- Handle various exit reasons
- Check if the VM is shutting down
- If the VM is shutting down:
- Mark the VCPU as exiting
- If this is the last VCPU to exit, decrement the running VM count
- Exit the VCPU task
fn vcpu_run() {
let curr = axtask::current();
let vm = curr.task_ext().vm.clone();
let vcpu = curr.task_ext().vcpu.clone();
// ... [Initialization code]
// Wait for VM to be in running state
wait_for(vm_id, || vm.running());
// Mark VCPU as running
mark_vcpu_running(vm_id);
// Main execution loop
loop {
match vm.run_vcpu(vcpu_id) {
Ok(exit_reason) => match exit_reason {
// ... [Handle various exit reasons]
},
Err(err) => {
// ... [Handle error]
}
}
// Check if VM is shutting down
if vm.shutting_down() {
// ... [Handle shutdown]
if mark_vcpu_exiting(vm_id) {
// Last VCPU exiting
super::RUNNING_VM_COUNT.fetch_sub(1, Ordering::Release);
ax_wait_queue_wake(&super::VMM, 1);
}
break;
}
}
}
Sources: src/vmm/vcpus.rs(L275 - L367)
VM and VCPU State Management
The VMM maintains the state of VMs and VCPUs through a combination of atomic counters and wait queues.
VM State Tracking
The VMM tracks running VMs using the RUNNING_VM_COUNT atomic counter. When all VMs have stopped (RUNNING_VM_COUNT reaches 0), the VMM is signaled to exit.
static RUNNING_VM_COUNT: AtomicUsize = AtomicUsize::new(0);
static VMM: AxWaitQueueHandle = AxWaitQueueHandle::new();
Sources: src/vmm/mod.rs(L22 - L25)
VCPU State Management
The VCPU state management involves:
- Wait Queues: Each VM has a wait queue to manage its VCPUs
- VCPU Task List: Each VM maintains a list of VCPU tasks
- Running/Halting Count: The system tracks the number of running or halting VCPUs for each VM
pub struct VMVCpus {
_vm_id: usize,
wait_queue: WaitQueue,
vcpu_task_list: Vec<AxTaskRef>,
running_halting_vcpu_count: AtomicUsize,
}
Sources: src/vmm/vcpus.rs(L27 - L40)
Secondary VCPU Management
While primary VCPUs are set up during VMM initialization, secondary VCPUs can be dynamically started by the guest OS through the CPU Up exit reason.
When a running VCPU requests to start a secondary VCPU, the system:
- Sets up the entry point and arguments for the new VCPU
- Creates a new VCPU task
- Adds the task to the VM's VCPU task list
fn vcpu_on(vm: VMRef, vcpu_id: usize, entry_point: GuestPhysAddr, arg: usize) {
let vcpu = vm.vcpu_list()[vcpu_id].clone();
// Setup VCPU entry point and arguments
vcpu.set_entry(entry_point).expect("vcpu_on: set_entry failed");
vcpu.set_gpr(0, arg);
// Create and add VCPU task
let vcpu_task = alloc_vcpu_task(vm.clone(), vcpu);
unsafe { VM_VCPU_TASK_WAIT_QUEUE.get_mut(&vm.id()) }
.unwrap()
.add_vcpu_task(vcpu_task);
}
Sources: src/vmm/vcpus.rs(L179 - L208)
VM Shutdown Process
When a VM shuts down, the following steps occur:
- The VM is marked as shutting down
- Each VCPU detects the shutdown state in its execution loop
- The last VCPU to exit decrements the
RUNNING_VM_COUNT - When
RUNNING_VM_COUNTreaches 0, the VMM wait queue is signaled - The VMM exits its wait loop and the hypervisor can terminate
This coordinated shutdown ensures proper cleanup of resources and allows the hypervisor to exit cleanly.
sequenceDiagram
participant GuestOS as "Guest OS"
participant VCPUTask as "VCPU Task"
participant VMInstance as "VM Instance"
participant VMMModule as "VMM Module"
GuestOS ->> VCPUTask: System Down Exit
VCPUTask ->> VMInstance: shutdown()
VMInstance -->> VCPUTask: VM marked as shutting down
loop For each VCPU
VCPUTask ->> VCPUTask: Detect VM shutting down
VCPUTask ->> VCPUTask: Mark VCPU as exiting
VCPUTask ->> VCPUTask: Check if last VCPU
end
VCPUTask ->> VMMModule: Decrement RUNNING_VM_COUNT
VCPUTask ->> VMMModule: Wake VMM wait queue
VCPUTask ->> VCPUTask: Exit VCPU task
VMMModule ->> VMMModule: Check if RUNNING_VM_COUNT = 0
VMMModule ->> VMMModule: Exit VMM
Sources: src/vmm/vcpus.rs(L345 - L363) src/vmm/mod.rs(L55 - L64)
Summary
The VMM implementation in AxVisor provides a robust foundation for managing virtual machines across multiple architectures. Key aspects of this implementation include:
- Centralized VM Management: The VMM initializes, boots, and tracks all VMs in the system.
- VCPU Task System: Each VCPU runs in its own task, managed by the task scheduler.
- Coordinated Shutdown: The system ensures proper cleanup and coordination during VM shutdown.
- State Tracking: The VMM maintains clear state tracking for VMs and VCPUs.
- Secondary VCPU Support: The system supports dynamically starting secondary VCPUs.
This implementation enables AxVisor to efficiently manage multiple guest VMs while providing a clean architecture for future extensions.
VCPU Management
Relevant source files
This page documents the VCPU (Virtual CPU) management system in AxVisor, focusing on how virtual CPUs are implemented, initialized, scheduled, and monitored throughout their lifecycle. For information about the overall VM management, see VM Management. For timer-related aspects of VCPUs, see Timer Subsystem.
Overview
AxVisor implements a task-based VCPU execution model, where each VCPU runs as a separate task in the ArceOS task scheduler. This design allows multiple VCPUs to be efficiently managed across physical CPUs, with mechanisms for coordination, notification, and state tracking.
flowchart TD
subgraph subGraph1["External Components"]
axvm["axvm - VM Management"]
axtask["axtask - Task Scheduler"]
arch_impl["Architecture-Specific Implementations"]
end
subgraph subGraph0["VCPU Management System"]
vcpu_management["VCPU Management"]
vcpu_task_alloc["VCPU Task Allocation"]
vcpu_initialization["VCPU Initialization"]
vcpu_execution["VCPU Execution Loop"]
vcpu_exit["VCPU Exit Handling"]
vcpu_notification["VCPU Notification System"]
vm_shutdown["VM Shutdown Coordination"]
end
arch_impl --> vcpu_execution
axtask --> vcpu_execution
axtask --> vcpu_task_alloc
axvm --> vcpu_management
vcpu_management --> vcpu_execution
vcpu_management --> vcpu_exit
vcpu_management --> vcpu_initialization
vcpu_management --> vcpu_notification
vcpu_management --> vcpu_task_alloc
vcpu_management --> vm_shutdown
Sources: src/vmm/vcpus.rs(L1 - L368) src/vmm/mod.rs(L1 - L66) src/task.rs(L1 - L19)
VCPU Architecture and Implementation
AxVisor's VCPU management is based on a cross-architecture design that abstracts hardware-specific details. The system includes platform-specific VCPU implementations for x86_64, ARM, and RISC-V architectures, all conforming to a common interface defined by axvcpu.
VCPU Type Hierarchy
Sources: src/vmm/mod.rs(L15 - L20) src/vmm/vcpus.rs(L175 - L201)
VCPU and Task Relationship
Each VCPU in AxVisor is associated with an ArceOS task, which is scheduled by the underlying task scheduler. This relationship is established through the TaskExt structure that extends ArceOS tasks with VCPU-specific information.
| Component | Purpose |
|---|---|
| TaskExt | Extension to ArceOS tasks that holds references to VM and VCPU |
| VCpuRef | Reference to a VCPU instance, architecture-specific implementation |
| VMRef | Reference to the VM that owns the VCPU |
Source: src/task.rs(L1 - L19)
VCPU Lifecycle
VCPU Initialization
VCPUs are initialized during the VMM startup process. The primary VCPU (VCPU 0) for each VM is set up first, while secondary VCPUs can be started on demand.
sequenceDiagram
participant VMMinit as "VMM::init"
participant VM as "VM"
participant VMVCpus as "VMVCpus"
participant PrimaryVCPU as "Primary VCPU"
participant VCPUTask as "VCPU Task"
VMMinit ->> VM: init_guest_vms()
VMMinit ->> VM: setup_vm_primary_vcpu(vm)
VM ->> VMVCpus: new(vm)
VM ->> PrimaryVCPU: Get primary VCPU (id=0)
VM ->> VCPUTask: alloc_vcpu_task(vm, primary_vcpu)
VCPUTask ->> VMVCpus: add_vcpu_task(primary_vcpu_task)
VMMinit ->> VM: start()
VMMinit ->> VM: boot()
VMMinit ->> PrimaryVCPU: notify_primary_vcpu(vm_id)
Sources: src/vmm/mod.rs(L27 - L39) src/vmm/vcpus.rs(L211 - L231)
VCPU Execution Loop
The main VCPU execution loop is implemented in the vcpu_run function, which is the entry point for all VCPU tasks. The VCPU waits for the VM to be in a running state, then enters a loop where it runs the VCPU and handles various exit reasons.
flowchart TD start["VCPU Task Start"] wait_vm["Wait for VM running"] mark_running["Mark VCPU running"] run_vcpu["Run VCPU (vm.run_vcpu)"] handle_exit["Handle exit reason"] check_shutdown["VM shutting down?"] last_vcpu["Last VCPU exiting?"] dec_count["Decrease running VM count"] wake_vmm["Wake VMM wait queue"] exit["Exit VCPU task"] wait_wakeup["Wait for notification"] cpu_up["Start target VCPU"] vm_shutdown["Shutdown VM"] check_shutdown --> last_vcpu check_shutdown --> run_vcpu cpu_up --> run_vcpu dec_count --> wake_vmm handle_exit --> check_shutdown handle_exit --> cpu_up handle_exit --> run_vcpu handle_exit --> vm_shutdown handle_exit --> wait_wakeup last_vcpu --> dec_count last_vcpu --> exit mark_running --> run_vcpu run_vcpu --> handle_exit start --> wait_vm vm_shutdown --> check_shutdown wait_vm --> mark_running wait_wakeup --> run_vcpu wake_vmm --> exit
Sources: src/vmm/vcpus.rs(L270 - L367)
VCPU Task Management
Task Allocation
Each VCPU runs as a separate ArceOS task, allocated with a dedicated kernel stack. The alloc_vcpu_task function creates a task for a VCPU, initializes it with VCPU-specific information, and configures its CPU affinity if specified.
flowchart TD
subgraph subGraph1["Task Properties"]
task_name["Task name: VM[id]-VCpu[id]"]
entry_fn["Entry function: vcpu_run"]
stack_size["Stack size: 256 KiB"]
cpu_mask["Optional CPU mask"]
end
subgraph subGraph0["VCPU Task Allocation"]
alloc_vcpu_task["alloc_vcpu_task()"]
new_task["TaskInner::new()"]
set_cpumask["Set CPU affinity mask"]
init_task_ext["Initialize TaskExt"]
spawn_task["Spawn task in scheduler"]
end
alloc_vcpu_task --> new_task
init_task_ext --> spawn_task
new_task --> entry_fn
new_task --> set_cpumask
new_task --> stack_size
new_task --> task_name
set_cpumask --> cpu_mask
set_cpumask --> init_task_ext
Sources: src/vmm/vcpus.rs(L232 - L268)
Task Structure: VMVCpus
The VMVCpus structure maintains the state of all VCPUs for a specific VM, including:
| Field | Purpose |
|---|---|
| _vm_id | ID of the VM to which these VCPUs belong |
| wait_queue | Wait queue for VCPU task scheduling |
| vcpu_task_list | List of tasks associated with the VCPUs |
| running_halting_vcpu_count | Counter for running/halting VCPUs to track VM shutdown |
Sources: src/vmm/vcpus.rs(L25 - L40)
VCPU Exit Handling
When a VCPU exits from guest mode, the exit reason is handled in the vcpu_run function. The system supports various exit reasons, including:
| Exit Reason | Description | Handling Behavior |
|---|---|---|
| Hypercall | Guest OS making a call to the hypervisor | Process hypercall and continue |
| ExternalInterrupt | Hardware interrupt delivered to host | Handle interrupt and continue |
| Halt | VCPU in halted state | Wait for notification to continue |
| CpuUp | Request to start another VCPU | Boot target VCPU and continue |
| CpuDown | VCPU being stopped | Wait for notification to continue |
| SystemDown | Guest OS shutting down | Shut down the VM |
| FailEntry | Failed to enter guest mode | Report error and continue |
Sources: src/vmm/vcpus.rs(L290 - L343)
Multi-VCPU Coordination
Primary and Secondary VCPUs
AxVisor distinguishes between the primary VCPU (typically VCPU 0) and secondary VCPUs. The primary VCPU is initialized during VM setup, while secondary VCPUs can be started on demand through the CpuUp exit reason.
sequenceDiagram
participant PrimaryVCPU as "Primary VCPU"
participant VMVCpus as "VMVCpus"
participant SecondaryVCPU as "Secondary VCPU"
PrimaryVCPU ->> PrimaryVCPU: Executes guest code
Note over PrimaryVCPU: Guest OS executes CPU_UP operation
PrimaryVCPU ->> PrimaryVCPU: Exits with CpuUp reason
PrimaryVCPU ->> VMVCpus: vcpu_on(vm, target_cpu, entry, arg)
VMVCpus ->> SecondaryVCPU: Create secondary VCPU task
VMVCpus ->> SecondaryVCPU: Set entry point and arguments
VMVCpus ->> VMVCpus: add_vcpu_task(secondary_task)
SecondaryVCPU ->> SecondaryVCPU: Start execution (vcpu_run)
SecondaryVCPU ->> SecondaryVCPU: Wait for VM running
PrimaryVCPU ->> PrimaryVCPU: Continue execution
Sources: src/vmm/vcpus.rs(L179 - L208) src/vmm/vcpus.rs(L319 - L330)
VM Shutdown Coordination
When a VM is shutting down, all VCPUs must exit properly. The last VCPU to exit is responsible for notifying the VMM that the VM has fully shut down.
flowchart TD vcpu_exit["VCPU detects VM is shutting down"] mark_exiting["Mark VCPU as exiting"] check_last["Is this the last VCPU?"] dec_count["Decrease running VM count"] wake_vmm["Wake VMM wait queue"] exit_vcpu["Exit VCPU task"] check_last --> dec_count check_last --> exit_vcpu dec_count --> wake_vmm mark_exiting --> check_last vcpu_exit --> mark_exiting wake_vmm --> exit_vcpu
Sources: src/vmm/vcpus.rs(L345 - L366) src/vmm/mod.rs(L55 - L64)
Wait Queue System
AxVisor uses a wait queue system to coordinate VCPU execution. The WaitQueue in VMVCpus allows VCPUs to block when they are halted and to be woken up when needed.
| Wait Queue Operation | Purpose |
|---|---|
| wait() | Block the current VCPU task unconditionally |
| wait_until(condition) | Block until a condition is met |
| notify_one() | Wake up one waiting VCPU task |
Sources: src/vmm/vcpus.rs(L72 - L89) src/vmm/vcpus.rs(L117 - L138)
Cross-Architecture VCPU Implementation
AxVisor supports multiple architectures through architecture-specific VCPU implementations. Each platform has its own VCPU implementation that conforms to the axvcpu interface.
| Architecture | Implementation | Special Considerations |
|---|---|---|
| ARM/aarch64 | arm_vcpu | Uses ARM-specific registers and virtualization extensions |
| x86_64 | x86_vcpu | Leverages VT-x/VMX for virtualization |
| RISC-V | riscv_vcpu | Uses RISC-V virtualization extensions and SBI |
Sources: src/vmm/mod.rs(L15 - L20) src/vmm/vcpus.rs(L193 - L201)
Timer Subsystem
Relevant source files
The Timer Subsystem in AxVisor provides time-based event scheduling and management for the hypervisor environment. This document describes the internal design and implementation of the timer framework that enables scheduling and execution of time-bound operations across the hypervisor.
For information about VCPU Management and how it interacts with timers, see VCPU Management.
Overview
The Timer Subsystem enables the hypervisor to schedule operations to be executed at specific times or after specific intervals. It manages a list of pending timer events per virtual CPU and provides an interface for registering, canceling, and processing timer events.
flowchart TD
subgraph subGraph2["Guest VMs"]
VMTimer["VM Timer Devices"]
end
subgraph subGraph1["Hardware Layer"]
HPET["HPET"]
LAPIC["Local APIC Timer"]
end
subgraph subGraph0["Timer Subsystem Components"]
Register["register_timer()"]
Cancel["cancel_timer()"]
CheckEvents["check_events()"]
ScheduleNext["scheduler_next_event()"]
InitPercpu["init_percpu()"]
TimerList["TIMER_LIST(Per-CPU TimerList)"]
VmmEvent["VmmTimerEvent(Token + Callback)"]
HWTimer["Hardware Timer"]
end
Cancel --> TimerList
CheckEvents --> TimerList
HWTimer --> HPET
HWTimer --> LAPIC
InitPercpu --> TimerList
Register --> TimerList
ScheduleNext --> HWTimer
TimerList --> VmmEvent
VMTimer --> HPET
Sources: src/vmm/timer.rs(L1 - L105) configs/vms/arceos-x86_64.toml(L57 - L77) configs/platforms/x86_64-qemu-q35.toml(L56 - L57)
Core Components
VmmTimerEvent Structure
The Timer Subsystem is built around the VmmTimerEvent struct, which encapsulates a timer event with:
- A unique token identifier
- A callback function to be executed when the timer expires
classDiagram
class VmmTimerEvent {
+usize token
+BoxUnsupported markdown: del timer_callback
+new(token: usize, f: F) VmmTimerEvent
}
class TimerEvent {
<<trait>>
+callback(self, now: TimeValue)
}
VmmTimerEvent ..|> TimerEvent : implements
Sources: src/vmm/timer.rs(L15 - L41)
The VmmTimerEvent implements the TimerEvent trait, which defines a callback method that will be invoked when the timer expires. Each timer event is uniquely identified by a token, which allows for cancellation or modification of pending timers.
Per-CPU Timer Lists
The Timer Subsystem maintains a separate timer list for each virtual CPU using the percpu crate. This approach:
- Avoids contention between CPUs for timer management
- Allows each CPU to handle its local timer events efficiently
- Ensures scalability as the number of CPUs increases
#[percpu::def_percpu]
static TIMER_LIST: LazyInit<SpinNoIrq<TimerList<VmmTimerEvent>>> = LazyInit::new();
Sources: src/vmm/timer.rs(L43 - L44)
The TIMER_LIST is protected by a spinlock (SpinNoIrq) to prevent concurrent modifications from different contexts on the same CPU. The LazyInit wrapper ensures the timer list is initialized only when first accessed.
Timer Operations
Timer Registration
Timers are registered using the register_timer function, which:
- Accepts a deadline timestamp in nanoseconds
- Takes a callback function to execute when the deadline is reached
- Returns a unique token that can be used to cancel the timer
#![allow(unused)] fn main() { pub fn register_timer<F>(deadline: u64, handler: F) -> usize where F: FnOnce(TimeValue) + Send + 'static }
Sources: src/vmm/timer.rs(L54 - L64)
Timer Cancellation
A previously registered timer can be canceled before its expiration using the cancel_timer function:
#![allow(unused)] fn main() { pub fn cancel_timer(token: usize) }
The function removes the timer with the matching token from the timer list.
Sources: src/vmm/timer.rs(L70 - L74)
Event Processing
The Timer Subsystem periodically checks for expired timers using the check_events function. This function:
- Obtains the current time
- Retrieves and executes all expired timer events
- Continues until no more expired events remain
Timer event processing happens in the context of the caller, which means callback functions are executed synchronously.
Sources: src/vmm/timer.rs(L77 - L89)
Scheduling the Next Timer
The scheduler_next_event function sets up the next hardware timer interrupt based on a periodic interval:
pub fn scheduler_next_event() {
let now_ns = axhal::time::monotonic_time_nanos();
let deadline = now_ns + PERIODIC_INTERVAL_NANOS;
axhal::time::set_oneshot_timer(deadline);
}
The interval is calculated from the system configuration (axconfig::TICKS_PER_SEC).
Sources: src/vmm/timer.rs(L92 - L97) src/vmm/timer.rs(L12)
Hardware Integration
The Timer Subsystem abstracts the underlying hardware timer mechanisms through the ArceOS hardware abstraction layer (axhal).
Timer Hardware
AxVisor supports several hardware timer mechanisms:
| Timer Type | Description | Usage |
|---|---|---|
| HPET | High Precision Event Timer | Passed through to guest VMs for high-precision timing |
| Local APIC Timer | Per-CPU timer in the Advanced Programmable Interrupt Controller | Used for scheduling timer events on individual CPUs |
Sources: configs/vms/arceos-x86_64.toml(L72 - L77) configs/vms/nimbos-x86_64.toml(L72 - L77)
Timer Frequency Configuration
The platform configuration specifies the timer frequency, which affects the precision and granularity of timer events:
# Timer interrupt frequency in Hz. (4.0GHz)
timer-frequency = 4_000_000_000 # uint
This high frequency enables precise timing for hypervisor operations.
Sources: configs/platforms/x86_64-qemu-q35.toml(L56 - L57)
Initialization and Setup
The Timer Subsystem requires initialization on each CPU using the init_percpu function:
pub fn init_percpu() {
info!("Initing HV Timer...");
let timer_list = unsafe { TIMER_LIST.current_ref_mut_raw() };
timer_list.init_once(SpinNoIrq::new(TimerList::new()));
}
This function initializes the per-CPU timer list and prepares it for use.
Sources: src/vmm/timer.rs(L99 - L104)
Usage in AxVisor
The Timer Subsystem is used throughout AxVisor for various purposes:
flowchart TD
subgraph subGraph1["Timer Subsystem"]
TimerReg["register_timer()"]
TimerCancel["cancel_timer()"]
TimerCheck["check_events()"]
end
subgraph subGraph0["Timer Use Cases"]
VCPU["VCPU Scheduling"]
VMEvents["VM Event Processing"]
Watchdog["Watchdog Timers"]
Preemption["Preemption Points"]
end
Preemption --> TimerReg
VCPU --> TimerCheck
VCPU --> TimerReg
VMEvents --> TimerReg
Watchdog --> TimerCancel
Watchdog --> TimerReg
Sources: src/vmm/timer.rs(L1 - L105)
Examples
Below is a simplified example of how a timer might be used in the hypervisor:
- Register a timer to check VM progress after 100ms:
let deadline = current_time_ns() + 100_000_000; // 100ms in the future
let token = register_timer(deadline, |_| {
// Check VM progress and take action if needed
check_vm_progress();
});
- Cancel the timer if the VM makes progress before the deadline:
if vm_made_progress {
cancel_timer(token);
}
- Process any expired timers:
// Called periodically from the hypervisor main loop
check_events();
Summary
The Timer Subsystem provides a fundamental service for the AxVisor hypervisor, enabling time-based operations and scheduling. Its per-CPU design ensures scalability, while the simple API makes it easy to use throughout the hypervisor codebase.
The timer framework successfully abstracts the underlying hardware timer mechanisms, allowing AxVisor to provide consistent timing services across different hardware platforms and architectures.
Contributing
Relevant source files
This document provides information and guidelines for contributing to the AxVisor project. It covers setting up a development environment, building and testing your changes, and understanding the CI/CD pipeline. For information about specific technical implementations, please refer to the Technical Reference section.
Development Environment Setup
To begin contributing to AxVisor, you'll need to set up a proper development environment with the necessary tools and dependencies.
Prerequisites
- Rust toolchain (nightly version, specified in
rust-toolchain.toml) - QEMU (version 8.2.0 or later recommended)
- Required system packages (for Linux-based systems)
- Git
Setting Up Your Environment
flowchart TD A["Install Rust"] B["Install Dependencies"] C["Clone Repository"] D["Setup QEMU"] E["Prepare Test Images"] F["Ready for Development"] A --> B B --> C C --> D D --> E E --> F
- Install Rust:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
- Install Dependencies:
sudo apt-get update && sudo apt-get install -y ninja-build libslirp-dev glib-2.0
- Clone Repository:
git clone https://github.com/arceos-hypervisor/axvisor.git
cd axvisor
- Setup QEMU (if not using the system version):
# Download and build QEMU
wget https://download.qemu.org/qemu-8.2.0.tar.xz
tar -xJf qemu-8.2.0.tar.xz
cd qemu-8.2.0
./configure --target-list=x86_64-softmmu,riscv64-softmmu,aarch64-softmmu --enable-slirp
make -j
make install
- Prepare Test Images:
# Create disk image for testing
make DISK_IMG=disk.img disk_img
Sources: Makefile(L14 - L34) .github/workflows/actions/setup-qemu/action.yml(L1 - L48)
Building and Running
AxVisor uses a Makefile system adapted from ArceOS to manage the build process, with a variety of options for different architectures and configurations.
Build Options
Here are the key parameters you can configure when building AxVisor:
| Parameter | Description | Possible Values |
|---|---|---|
| ARCH | Target architecture | x86_64,riscv64,aarch64 |
| SMP | Number of CPUs | Any integer (default: 1) |
| MODE | Build mode | release,debug |
| LOG | Logging level | warn,error,info,debug,trace |
| FEATURES | Features to enable | Space-separated list of features |
| APP_FEATURES | App-specific features | Space-separated list of features |
| BLK | Enable storage devices | y,n |
| NET | Enable network devices | y,n |
| ACCEL | Enable hardware acceleration | y,n |
Basic Build Commands
flowchart TD A["make defconfig"] B["make build"] C["make run"] D["make debug"] E["make test"] A --> B B --> C B --> D B --> E
- Generate default configuration:
make ARCH=x86_64 defconfig
- Build the project:
make ARCH=x86_64 build
- Run in QEMU:
make ARCH=x86_64 DISK_IMG=disk.img BLK=y ACCEL=y run
- Debug with GDB:
make ARCH=x86_64 debug
# In another terminal
make gdb
Sources: Makefile(L34 - L117) Makefile(L164 - L183)
Testing Your Changes
AxVisor has a comprehensive testing infrastructure to verify that your changes work correctly across different architectures and configurations.
Local Testing
Local testing can be performed on your development machine using the following commands:
- Run unit tests:
make unittest
- Run with a guest VM:
# Create disk image if not created yet
make DISK_IMG=disk.img disk_img
# Run with NimbOS as guest
make ARCH=x86_64 DISK_IMG=disk.img LOG=info BLK=y ACCEL=y \
VM_CONFIGS=configs/vms/nimbos-x86_64.toml APP_FEATURES=fs run
Testing Infrastructure
The testing infrastructure in AxVisor supports multiple architectures (x86_64, riscv64, aarch64) and can run both locally and remotely.
flowchart TD A["Developer Change"] B["Local Testing"] C["Push to GitHub"] D["GitHub Actions"] E["Local Runner Tests"] F["Remote Runner Tests"] G["Test on Ubuntu VMriscv64/aarch64"] H["Test on Remote Serversx86_64 with KVM"] I["All Tests Pass?"] J["Merge Change"] K["Fix Issues"] A --> B A --> C C --> D D --> E D --> F E --> G F --> H G --> I H --> I I --> J I --> K K --> A
Sources: .github/workflows/test.yml(L1 - L146) Makefile(L203 - L210)
CI/CD Pipeline
AxVisor employs a comprehensive CI/CD pipeline implemented with GitHub Actions to ensure code quality and compatibility across different architectures.
flowchart TD A["Push/PR"] B["GitHub Actions"] C["test_local Job"] D["test_remote Job"] C1["Matrix: riscv64, aarch64"] C2["Matrix: nightly-2024-12-25, nightly"] D1["Matrix: x86_64"] D2["Matrix: nightly-2024-12-25, nightly"] D3["Matrix: remote_aarkegz, remote_x10dri"] E["Setup Rust Toolchain"] F["Setup QEMU"] G["Setup NimbOS Guest Image"] H["Run Guest Tests"] I["Compress Source Code"] J["Setup NimbOS Guest Image"] K["Upload to Remote Server"] L["Run Tests Remotely"] M["Check for Panics"] A --> B B --> C B --> D C --> C1 C --> C2 C --> E D --> D1 D --> D2 D --> D3 D --> I E --> F F --> G G --> H I --> J J --> K K --> L L --> M
Local Runner Tests
The CI pipeline runs tests on GitHub-hosted runners for riscv64 and aarch64 architectures. These tests verify that AxVisor compiles and runs correctly on these architectures.
Remote Runner Tests
For x86_64 architecture, which requires specific hardware features (Intel VT-x), the CI pipeline uses remote runners. These are custom servers with the necessary hardware that allows running AxVisor with hardware acceleration.
Sources: .github/workflows/test.yml(L9 - L146) .github/workflows/REMOTE-CI.md(L1 - L50)
Contribution Guidelines
When contributing to AxVisor, please follow these guidelines to ensure your changes can be smoothly integrated.
Code Style and Formatting
- Rust Code Formatting:
cargo fmt --all
- Linting:
make clippy
- Documentation:
make doc
Git Workflow
flowchart TD A["Fork Repository"] B["Create Feature Branch"] C["Make Changes"] D["Run Tests"] E["Create Pull Request"] F["Address Review Feedback"] G["Changes Merged"] A --> B B --> C C --> D D --> E E --> F F --> G
- Fork the Repository: Create your own fork of the repository.
- Create a Feature Branch: Create a branch for your feature or bugfix.
- Make Changes: Implement your feature or fix.
- Run Tests: Ensure all tests pass locally.
- Create a Pull Request: Submit your changes for review.
- Address Review Feedback: Make any requested changes.
- Changes Merged: Once approved, your changes will be merged.
Sources: Makefile(L184 - L202)
Testing Different Guest Operating Systems
AxVisor supports multiple guest operating systems, including NimbOS, ArceOS, Linux, and others. When testing your changes, it's important to verify that they work with the relevant guest OS.
flowchart TD A["AxVisor"] B["Guest VM Testing"] C["NimbOS Guest"] D["ArceOS Guest"] E["Linux Guest"] F["Setup NimbOS Image"] G["Run with NimbOS Config"] H["Setup ArceOS Image"] I["Run with ArceOS Config"] J["Setup Linux Image"] K["Run with Linux Config"] A --> B B --> C B --> D B --> E C --> F D --> H E --> J F --> G H --> I J --> K
Setting Up Guest Images
- NimbOS Guest:
# The CI/CD pipeline uses this action to set up NimbOS
# You can download releases from the NimbOS repository
- Ubuntu Guest (for Linux testing):
make DISK_IMG=ubuntu.img ubuntu_img
Sources: .github/workflows/actions/setup-nimbos-guest-image/action.yml(L1 - L71) Makefile(L219 - L224)
Remote Testing Infrastructure
If you need to test changes that require specific hardware features (like Intel VT-x for x86_64), you can use the remote testing infrastructure. This is particularly important for hypervisor development where architectural differences can impact functionality.
Why Remote Testing is Needed
Remote testing allows testing on specific hardware configurations that may not be available in GitHub's runners. In particular:
- Testing Intel VT-x features
- Testing with high-end hardware
- Testing with specific hardware configurations
Sources: .github/workflows/REMOTE-CI.md(L1 - L50)
This document provides an overview of how to contribute to AxVisor. For more specific information about components, please refer to other sections of the wiki.
Testing Infrastructure
Relevant source files
This page documents the testing infrastructure for AxVisor, explaining how testing is automated, configured, and executed. It covers both local and remote testing setups, guest image preparation, and how to run and interpret tests. For CI/CD pipeline details beyond testing, see CI/CD Pipeline.
Overview
AxVisor employs a comprehensive testing infrastructure to ensure that changes don't break functionality across supported architectures (x86_64, aarch64, and riscv64). The testing system runs virtual machines with guest operating systems to verify that virtualization works correctly.
flowchart TD
subgraph subGraph1["Testing Infrastructure"]
test["Testing System"]
local["Local Testing"]
remote["Remote Testing"]
localSetup["Setup Environment"]
localRun["Run Tests"]
localCheck["Check Results"]
remoteSetup["Setup Environment"]
remoteTransfer["Transfer Files"]
remoteRun["Run Tests"]
remoteCheck["Check Results"]
subgraph subGraph0["Environment Setup"]
setupToolchain["Set Rust Toolchain"]
setupDeps["Install Dependencies"]
setupQEMU["Set up QEMU"]
setupGuestImage["Prepare Guest Images"]
end
end
local --> localCheck
local --> localRun
local --> localSetup
localSetup --> setupDeps
localSetup --> setupGuestImage
localSetup --> setupQEMU
localSetup --> setupToolchain
remote --> remoteCheck
remote --> remoteRun
remote --> remoteSetup
remote --> remoteTransfer
remoteSetup --> setupToolchain
test --> local
test --> remote
Sources: .github/workflows/test.yml(L1 - L150)
Testing Strategy
The AxVisor testing infrastructure is built around running actual guest virtual machines to validate functionality. Tests are executed both in local GitHub Actions runners and on specialized remote hardware for architecture-specific testing.
Test Matrix
Tests are conducted across a matrix of configurations:
| Test Type | Architectures | Rust Toolchains | Hardware Acceleration |
|---|---|---|---|
| Local | riscv64, aarch64 | nightly-2024-12-25, nightly | No (QEMU) |
| Remote | x86_64 | nightly-2024-12-25, nightly | Yes (KVM) |
Sources: .github/workflows/test.yml(L10 - L59)
Local Testing Workflow
Local testing runs on GitHub Actions runners and focuses on riscv64 and aarch64 architectures using QEMU emulation.
sequenceDiagram
participant GitHubActions as "GitHub Actions"
participant checkoutv4 as "checkout@v4"
participant rusttoolchainstable as "rust-toolchain@stable"
participant setupqemuv01 as "setup-qemu@v0.1"
participant setupnimbosguestimage as "setup-nimbos-guest-image"
participant TestRunner as "Test Runner"
GitHubActions ->> checkoutv4: Checkout repository
GitHubActions ->> rusttoolchainstable: Setup Rust toolchain
Note over rusttoolchainstable: nightly-2024-12-25 or latest nightly
GitHubActions ->> setupqemuv01: Setup QEMU v8.2.0
GitHubActions ->> setupnimbosguestimage: Prepare NimbOS guest image
Note over setupnimbosguestimage: Download NimbOS v0.7, create disk image
GitHubActions ->> TestRunner: Configure KVM permissions
GitHubActions ->> TestRunner: Update rust-toolchain.toml
GitHubActions ->> TestRunner: Run tests with make
Note over TestRunner: ARCH=(riscv64/aarch64), ACCEL=n
Sources: .github/workflows/test.yml(L10 - L50) .github/workflows/actions/setup-nimbos-guest-image/action.yml(L1 - L70)
Local Test Execution Steps
- Check out the repository
- Set up the specified Rust toolchain with rust-src component
- Install cargo-binutils
- Set up QEMU for the target architecture
- Download and prepare NimbOS guest image
- Configure KVM permissions
- Update rust-toolchain.toml with the specified toolchain
- Run the test using make with:
- Target architecture
- Disk image path
- Verbose logging
- Block device enabled
- Hardware acceleration disabled
- VM configuration file path
- Filesystem support
Sources: .github/workflows/test.yml(L19 - L50)
Remote Testing Workflow
Remote testing targets x86_64 architecture on specialized hardware with KVM acceleration enabled.
sequenceDiagram
participant GitHubActions as "GitHub Actions"
participant checkoutv4 as "checkout@v4"
participant setupnimbosguestimage as "setup-nimbos-guest-image"
participant scpactionv017 as "scp-action@v0.1.7"
participant sshactionv122 as "ssh-action@v1.2.2"
participant RemoteRunner as "Remote Runner"
GitHubActions ->> checkoutv4: Checkout repository
GitHubActions ->> GitHubActions: Update rust-toolchain.toml
GitHubActions ->> GitHubActions: Compress source code
GitHubActions ->> setupnimbosguestimage: Prepare NimbOS guest image
GitHubActions ->> scpactionv017: Copy files to remote runner
Note over scpactionv017: Transfer disk image and source code
GitHubActions ->> sshactionv122: Execute test on remote runner
sshactionv122 ->> RemoteRunner: Extract source code
sshactionv122 ->> RemoteRunner: Run make defconfig
sshactionv122 ->> RemoteRunner: Run make with VM configs
Note over RemoteRunner: ARCH=x86_64, ACCEL=y
sshactionv122 ->> RemoteRunner: Check for panic in output
sshactionv122 ->> RemoteRunner: Clean up
Sources: .github/workflows/test.yml(L52 - L149)
Remote Test Execution Steps
- Check out the repository
- Update rust-toolchain.toml with the specified toolchain
- Compress the source code
- Prepare NimbOS guest image locally
- Transfer files to the remote runner:
- Compressed source code
- Disk image
- On the remote runner:
- Extract the source code
- Run
make defconfigfor x86_64 - Execute test with hardware acceleration enabled
- Check output for panic messages
- Clean up temporary files
Sources: .github/workflows/test.yml(L72 - L149)
Guest VM Image Preparation
The testing infrastructure uses a custom action to prepare guest VM images (specifically NimbOS) for testing.
flowchart TD
subgraph subGraph0["setup-nimbos-guest-image Action"]
setupStart["Start Setup"]
downloadNimbOS["Download NimbOS Release"]
unzipNimbOS["Unzip NimbOS Image"]
renameNimbOS["Rename to nimbos-{arch}.bin"]
isX86["Is x86_64?"]
downloadBIOS["Download AXVM BIOS"]
createDiskImage["Create Disk Image"]
mountDisk["Mount Disk Image"]
copyFiles["Copy Files to Disk"]
unmountDisk["Unmount Disk"]
cleanup["Cleanup Temporary Files"]
end
copyFiles --> unmountDisk
createDiskImage --> mountDisk
downloadBIOS --> createDiskImage
downloadNimbOS --> unzipNimbOS
isX86 --> createDiskImage
isX86 --> downloadBIOS
mountDisk --> copyFiles
renameNimbOS --> isX86
setupStart --> downloadNimbOS
unmountDisk --> cleanup
unzipNimbOS --> renameNimbOS
Sources: .github/workflows/actions/setup-nimbos-guest-image/action.yml(L1 - L70)
Guest Image Setup Process
The setup process:
- Creates a temporary directory
- Downloads the appropriate NimbOS release for the target architecture
- Unzips and renames the NimbOS binary
- For x86_64, also downloads the AXVM BIOS
- Creates a disk image using the makefile
- Mounts the disk image
- Copies the NimbOS binary and BIOS (if applicable) to the disk image
- Unmounts the disk image and cleans up temporary files
Sources: .github/workflows/actions/setup-nimbos-guest-image/action.yml(L32 - L70)
Running Tests Manually
To run tests manually in a local development environment, follow these steps:
- Set up a disk image with a guest OS (like NimbOS)
- Configure the test environment:
# For local testing (without hardware acceleration)
make ARCH=<arch> defconfig
make ARCH=<arch> DISK_IMG=<path_to_disk_img> LOG=info BLK=y ACCEL=n VM_CONFIGS=<path_to_vm_config> APP_FEATURES=fs run
# For testing with hardware acceleration (KVM)
make ARCH=<arch> defconfig
make ARCH=<arch> DISK_IMG=<path_to_disk_img> LOG=info BLK=y ACCEL=y VM_CONFIGS=<path_to_vm_config> APP_FEATURES=fs run
Replace <arch> with the target architecture (x86_64, aarch64, or riscv64), <path_to_disk_img> with the path to your guest disk image, and <path_to_vm_config> with the path to your VM configuration file.
Sources: .github/workflows/test.yml(L45 - L50) .github/workflows/test.yml(L133 - L134)
Interpreting Test Results
Tests are considered successful if they complete without generating "panic" messages in the output. The CI system checks for these messages automatically:
if grep -q "panic" make_output.log; then
# Test failed
exit 1
else
# Test passed
fi
For debugging failed tests, examine the log output for:
- Panic messages
- Errors during VM boot
- Issues with guest OS execution
- Problems with virtual device emulation
Sources: .github/workflows/test.yml(L139 - L146)
Architecture-Specific Considerations
Each supported architecture has specific testing requirements:
x86_64
- Tested on remote runners with KVM acceleration
- Requires AXVM BIOS for guest OS boot
- Uses hardware virtualization extensions (VT-x)
aarch64
- Tested on local GitHub Actions runners
- Uses QEMU for emulation
- Leverages virtualization extensions (EL2 mode)
riscv64
- Tested on local GitHub Actions runners
- Uses QEMU for emulation
- Relies on SBI runtime for hypervisor operations
Sources: .github/workflows/test.yml(L16) .github/workflows/test.yml(L57) .github/workflows/actions/setup-nimbos-guest-image/action.yml(L52 - L58)
CI/CD Pipeline
Relevant source files
This page documents the Continuous Integration and Continuous Deployment (CI/CD) pipeline used by the AxVisor hypervisor project. The pipeline handles automated building, testing, and verification across multiple architectures (x86_64, aarch64, riscv64) and environments to ensure the hypervisor operates correctly on various platforms.
For information about the testing infrastructure and methodology, see Testing Infrastructure.
Overview of the CI/CD Pipeline
The AxVisor CI/CD pipeline is implemented using GitHub Actions and consists of two main testing strategies:
- Local Testing: Tests run directly on GitHub-hosted runners
- Remote Testing: Tests run on remote machines for hardware-specific testing (particularly for Intel VT-x)
The pipeline automatically triggers on both push events and pull requests, ensuring all code changes are verified before merging.
flowchart TD
subgraph subGraph2["CI/CD Pipeline"]
trigger["Code Push or PR"]
gh_actions["GitHub Actions Workflow"]
subgraph subGraph1["Remote Testing (Custom Servers)"]
remote_test["test_remote Job"]
compress["Compress Source"]
copy["Copy to Remote Server"]
extract["Extract Source"]
run_remote["Run Tests on Remote"]
end
subgraph subGraph0["Local Testing (GitHub Runners)"]
local_test["test_local Job"]
setup_qemu["Setup QEMU Action"]
setup_img["Setup NimbOS Guest Image"]
run_test["Run Tests"]
end
end
compress --> copy
copy --> extract
extract --> run_remote
gh_actions --> local_test
gh_actions --> remote_test
local_test --> setup_qemu
remote_test --> compress
setup_img --> run_test
setup_qemu --> setup_img
trigger --> gh_actions
Sources: .github/workflows/test.yml(L1 - L150)
Local Testing Process
Local testing runs on GitHub-hosted runners with multiple architecture and Rust toolchain configurations. This testing strategy covers non-hardware-specific features and ensures compatibility across architectures.
Configuration Matrix
The local testing strategy uses a configuration matrix to test multiple combinations:
| Architecture | Rust Toolchain |
|---|---|
| riscv64 | nightly-2024-12-25 |
| riscv64 | nightly |
| aarch64 | nightly-2024-12-25 |
| aarch64 | nightly |
Local Testing Workflow
sequenceDiagram
participant GitHubActions as "GitHub Actions"
participant GitHubRunner as "GitHub Runner"
participant QEMUVM as "QEMU VM"
participant NimbOSGuest as "NimbOS Guest"
GitHubActions ->> GitHubRunner: Start test_local job
GitHubRunner ->> GitHubRunner: Install Rust toolchain
GitHubRunner ->> GitHubRunner: Install cargo-binutils
GitHubRunner ->> GitHubRunner: Setup QEMU
GitHubRunner ->> GitHubRunner: Prepare NimbOS guest image
GitHubRunner ->> GitHubRunner: Configure KVM permissions
GitHubRunner ->> GitHubRunner: Update rust-toolchain.toml
GitHubRunner ->> QEMUVM: Launch QEMU with NimbOS guest
QEMUVM ->> NimbOSGuest: Boot NimbOS
NimbOSGuest ->> GitHubRunner: Test results
GitHubRunner ->> GitHubActions: Report test status
Sources: .github/workflows/test.yml(L10 - L50) .github/workflows/actions/setup-qemu/action.yml(L1 - L48) .github/workflows/actions/setup-nimbos-guest-image/action.yml(L1 - L71)
Remote Testing Process
Remote testing is used specifically for hardware-dependent features (such as Intel VT-x virtualization) that cannot be properly tested on GitHub's AMD-based runners. This is essential for AxVisor as a hypervisor project that requires specific CPU virtualization features.
Remote Testing Architecture
flowchart TD
subgraph subGraph1["Remote Server"]
remote_dir["~/runner/remote_test-{run_id}"]
extract["Extract Source"]
build["Build AxVisor"]
run["Run With NimbOS Guest"]
verify["Verify No Panics"]
cleanup["Clean Up"]
end
subgraph subGraph0["GitHub Actions"]
gh_action["test_remote Job"]
prepare["Prepare Source & Images"]
scp["SCP Action"]
ssh["SSH Action"]
end
build --> run
extract --> build
gh_action --> prepare
prepare --> scp
run --> verify
scp --> remote_dir
scp --> ssh
ssh --> extract
verify --> cleanup
Sources: .github/workflows/test.yml(L52 - L150) .github/workflows/REMOTE-CI.md(L1 - L50)
Remote Server Configuration
Remote testing currently uses two server configurations:
| Server ID | Purpose |
|---|---|
| remote_aarkegz | Intel CPU for VT-x testing |
| remote_x10dri | Additional Intel platform |
Credentials and connection details for these servers are stored as GitHub secrets and accessed securely during the workflow execution.
Sources: .github/workflows/test.yml(L59 - L70)
Guest VM Testing System
AxVisor is tested with real guest VMs to verify its functionality as a hypervisor. The primary guest OS used for testing is NimbOS, which is lightweight and designed specifically for testing purposes.
Guest Image Preparation Process
flowchart TD
subgraph subGraph0["Guest Image Setup"]
action["setup-nimbos-guest-image Action"]
download["Download NimbOS Release"]
extract["Extract Image"]
bios["Download BIOS (x86_64 only)"]
create["Create Disk Image"]
mount["Mount Image"]
copy["Copy Files to Image"]
unmount["Unmount Image"]
end
action --> download
bios --> create
copy --> unmount
create --> mount
download --> extract
extract --> bios
mount --> copy
Sources: .github/workflows/actions/setup-nimbos-guest-image/action.yml(L1 - L71)
Guest Testing Configuration
The testing configuration for guest VMs includes:
- Disk Image: A FAT32 filesystem image containing the guest OS
- Virtual Hardware: Configured through QEMU with appropriate architecture-specific settings
- VM Configuration: Loaded from
configs/vms/nimbos-{arch}.tomlfiles
Sources: .github/workflows/test.yml(L47 - L50) .github/workflows/test.yml(L134)
QEMU Setup and Integration
QEMU is the virtualization platform used for testing AxVisor. A custom GitHub Action is used to set up QEMU with the required configurations.
QEMU Installation Process
flowchart TD
subgraph subGraph0["QEMU Setup Action"]
setup["setup-qemu Action"]
cache_check["Check Cache"]
download["Download QEMU Source"]
build["Build QEMU"]
install["Install QEMU"]
verify["Verify Installation"]
end
build --> install
cache_check --> download
cache_check --> install
download --> build
install --> verify
setup --> cache_check
Sources: .github/workflows/actions/setup-qemu/action.yml(L1 - L48)
QEMU Configuration
The QEMU setup configures several architecture-specific system emulators:
qemu-system-x86_64for x86_64 architectureqemu-system-aarch64for ARM/aarch64 architectureqemu-system-riscv64for RISC-V architecture
Each emulator is built with slirp networking support and appropriate target configurations.
Sources: .github/workflows/actions/setup-qemu/action.yml(L28 - L31) .github/workflows/actions/setup-qemu/action.yml(L42 - L47)
Integration with Makefile Test Targets
The CI/CD pipeline integrates with the project's Makefile test targets to execute tests within both local and remote environments.
Makefile Testing Commands
flowchart TD
subgraph subGraph0["Makefile Testing Targets"]
make_test["make test"]
app_test["App-level Tests"]
make_unittest["make unittest"]
unit_test["Unit Tests"]
make_unit_no_fail["make unittest_no_fail_fast"]
unit_test_nff["Unit Tests (No Fail Fast)"]
end
make_test --> app_test
make_unit_no_fail --> unit_test_nff
make_unittest --> unit_test
Sources: Makefile(L203 - L210)
CI Testing Execution
In the CI/CD pipeline, tests are executed using a specific command that includes several parameters:
make ARCH={arch} DISK_IMG={disk_img} LOG=info BLK=y ACCEL={accel} VM_CONFIGS={vm_configs} APP_FEATURES=fs run
This command configures:
- The target architecture
- The disk image path
- Logging level
- Block device support
- Hardware acceleration (KVM on x86_64)
- VM configuration files
- Additional features
Sources: .github/workflows/test.yml(L49 - L50) .github/workflows/test.yml(L134)
Development Workflow Integration
The CI/CD pipeline is integrated into the development workflow to provide automated testing and verification for all code changes.
Pull Request Workflow
sequenceDiagram
participant Developer as Developer
participant GitHub as GitHub
participant CICDPipeline as "CI/CD Pipeline"
participant Reviewers as Reviewers
Developer ->> GitHub: Create Pull Request
GitHub ->> CICDPipeline: Trigger CI Workflow
CICDPipeline ->> CICDPipeline: Run Local Tests
CICDPipeline ->> CICDPipeline: Run Remote Tests
CICDPipeline ->> GitHub: Report Test Results
Reviewers ->> GitHub: Review Code
GitHub ->> Developer: Feedback
Developer ->> GitHub: Address Feedback
GitHub ->> CICDPipeline: Trigger CI Again
CICDPipeline ->> GitHub: Updated Results
Reviewers ->> GitHub: Approve PR
GitHub ->> GitHub: Merge PR
Multi-Architecture Support
The CI/CD pipeline is designed to test AxVisor across multiple CPU architectures to ensure broad compatibility.
Architecture Matrix
The pipeline supports testing on three key architectures:
| Architecture | Local Testing | Remote Testing | Virtualization Technology |
|---|---|---|---|
| x86_64 | No | Yes | Intel VT-x |
| aarch64 | Yes | No | ARM Virtualization Extensions |
| riscv64 | Yes | No | RISC-V H-Extension |
Sources: .github/workflows/test.yml(L16) .github/workflows/test.yml(L57)
Future Improvements
Based on the documentation, several potential improvements to the CI/CD pipeline are planned:
- Better Tool Integration: Using the
acttool to run CI tests more consistently - Improved SSH Tooling: Finding better alternatives to the current SSH action tools
- Enhanced Caching: Implementing more efficient caching strategies for QEMU and other environments
- Automated Environment Setup: Creating automated setup for remote testing environments
- Additional Hardware Resources: Acquiring more stable and powerful servers for remote testing
Sources: .github/workflows/REMOTE-CI.md(L40 - L49)
Overview
Relevant source files
This document provides an overview of the axdevice_crates repository, which contains foundational crates for building emulated device subsystems within the ArceOS hypervisor ecosystem. The repository specifically targets no_std embedded environments and provides the core abstractions needed for device emulation in virtualized guest systems.
For detailed information about the core device abstraction layer, see Core Architecture. For implementation guidance, see Development Guide. For specifics about external dependencies and ArceOS integration, see Dependencies and Integration.
Purpose and Scope
The axdevice_crates repository serves as the foundation for device emulation in the ArceOS hypervisor. It provides:
- Core traits and abstractions for emulated devices (
BaseDeviceOps) - A comprehensive device type taxonomy (
EmuDeviceType) - Integration points with ArceOS hypervisor address space management
- Support for multiple architectures and virtualization approaches
The repository is designed for no_std environments, making it suitable for embedded systems and hypervisor contexts where the standard library is not available.
Sources: README.md(L1 - L5) Cargo.toml(L1 - L16)
Repository Architecture
System Architecture Overview
The repository follows a workspace-based structure with axdevice_base as the foundational crate:
flowchart TD
subgraph subGraph3["no_std Environment"]
EMB["Embedded RuntimeConstraints"]
end
subgraph subGraph2["External Dependencies"]
AE["axerrno"]
AA["axaddrspace"]
MA["memory_addr"]
SE["serde"]
end
subgraph subGraph1["ArceOS Hypervisor Ecosystem"]
AH["ArceOS Hypervisor"]
AS["Address Space Manager"]
VM["Virtual Machines"]
end
subgraph subGraph0["axdevice_crates Repository"]
WS["Workspace Root(Cargo.toml)"]
AB["axdevice_base Crate"]
README["README.md"]
end
AB --> AA
AB --> AE
AB --> AH
AB --> EMB
AB --> MA
AB --> SE
AH --> AS
AH --> VM
WS --> AB
Sources: Cargo.toml(L1 - L16) README.md(L3)
Core Component Relationships
The following diagram shows how the main code entities relate to each other:
classDiagram
class BaseDeviceOps {
<<trait>>
+emu_type() EmuDeviceType
+address_range() AddrRange~GuestPhysAddr~
+handle_read(addr, width) AxResult~usize~
+handle_write(addr, width, val) AxResult~() ~
}
class EmuDeviceType {
<<enum>>
EmuDeviceTConsole
EmuDeviceTGicdV2
EmuDeviceTGPPT
EmuDeviceTVirtioBlk
EmuDeviceTVirtioNet
EmuDeviceTVirtioConsole
EmuDeviceTIOMMU
EmuDeviceTICCSRE
EmuDeviceTSGIR
EmuDeviceTGICR
EmuDeviceTMeta
+removable() bool
+from_usize(value) Option~EmuDeviceType~
}
class AddrRange~GuestPhysAddr~ {
<<from axaddrspace>>
}
class AxResult~T~ {
<<from axerrno>>
}
class GuestPhysAddr {
<<from axaddrspace>>
}
BaseDeviceOps --> EmuDeviceType : "returns"
BaseDeviceOps --> AddrRange : "uses"
BaseDeviceOps --> AxResult : "returns"
BaseDeviceOps --> GuestPhysAddr : "uses"
Sources: Based on architectural analysis from provided diagrams
Device Type Categories
The EmuDeviceType enum categorizes emulated devices into several functional groups:
| Category | Device Types | Removable | Purpose |
|---|---|---|---|
| Console & I/O | EmuDeviceTConsole,EmuDeviceTVirtioConsole | Mixed | Guest system I/O |
| ARM Interrupt Controllers | EmuDeviceTGicdV2,EmuDeviceTGPPT,EmuDeviceTICCSRE,EmuDeviceTSGIR,EmuDeviceTGICR | Yes | ARM-specific interrupt handling |
| Virtio Paravirtualization | EmuDeviceTVirtioBlk,EmuDeviceTVirtioNet | Yes | High-performance paravirtualized devices |
| System Infrastructure | EmuDeviceTIOMMU,EmuDeviceTMeta | No | Core system components |
The majority of device types (8 out of 11) are removable, indicating support for dynamic device configuration in virtualized environments.
Sources: Based on EmuDeviceType analysis from architectural diagrams
Integration with ArceOS Ecosystem
The axdevice_crates repository integrates tightly with the ArceOS hypervisor ecosystem through several key dependencies:
flowchart TD
subgraph subGraph3["ArceOS Hypervisor"]
HV["Hypervisor Core"]
ASM["Address SpaceManager"]
end
subgraph subGraph2["Standard Dependencies"]
SE["serdeSerialization"]
CF["cfg-ifConditionalCompilation"]
end
subgraph subGraph1["ArceOS Core Dependencies"]
AE["axerrnoError Handling"]
AA["axaddrspaceGuest PhysicalAddress Management"]
MA["memory_addrMemory Utilities"]
end
subgraph axdevice_base["axdevice_base"]
BDO["BaseDeviceOps"]
EDT["EmuDeviceType"]
end
AA --> ASM
AE --> HV
BDO --> AA
BDO --> AE
BDO --> HV
BDO --> MA
EDT --> CF
EDT --> SE
Sources: Cargo.toml(L15 - L16) architectural analysis
Development Environment
The repository targets multiple architectures and maintains compatibility with no_std environments:
- Architecture Support: x86_64, RISC-V (riscv64gc), ARM64 (aarch64)
- Environment:
no_stdcompatible for embedded systems - License: Triple-licensed under GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0
- Workspace Structure: Single workspace with
axdevice_baseas the foundational member
The no_std constraint ensures the crates can be used in hypervisor and embedded contexts where the standard library is not available, making them suitable for system-level virtualization components.
Sources: Cargo.toml(L8 - L13) README.md(L3)
Next Steps
This overview establishes the foundational understanding of the axdevice_crates repository. For implementation details:
- See Project Structure for detailed workspace organization
- See BaseDeviceOps Trait for the core device abstraction API
- See Device Type System for comprehensive device categorization
- See Implementing New Devices for development guidance
Project Structure
Relevant source files
This document covers the workspace organization and architectural layout of the axdevice_crates repository. It explains how the axdevice_base foundation crate is structured and integrates with the ArceOS hypervisor ecosystem. For detailed information about the core traits and device abstractions, see Core Architecture. For build system specifics and development workflows, see Build System and CI.
Workspace Organization
The axdevice_crates repository follows a Cargo workspace pattern with a single foundational crate that serves as the basis for device emulation abstractions.
Repository Structure
flowchart TD
subgraph subGraph2["Future Extensions"]
FUTURE["Additional device crates..."]
end
subgraph subGraph1["axdevice_base Crate"]
CBASE["Cargo.toml"]
SRCBASE["src/"]
LIBRS["src/lib.rs"]
EMUTY["src/emu_type.rs"]
end
subgraph subGraph0["Repository Root"]
CWS["Cargo.toml (workspace)"]
README["README.md"]
GH[".github/workflows/"]
end
CWS --> CBASE
CWS --> FUTURE
SRCBASE --> EMUTY
SRCBASE --> LIBRS
Sources: Cargo.toml(L1 - L16) axdevice_base/Cargo.toml(L1 - L17)
The workspace is configured with resolver version 2 and includes the axdevice_base crate as its primary member. The workspace-level configuration defines common package metadata including version 0.1.0, authorship, and licensing under GPL-3.0-or-later, Apache-2.0, or MulanPSL-2.0.
Workspace Dependencies
| Dependency | Version | Purpose |
|---|---|---|
| axdevice_base | 0.1 | Foundation crate for device abstractions |
Sources: Cargo.toml(L15 - L16)
axdevice_base Crate Architecture
The axdevice_base crate serves as the foundational layer providing core traits and types for device emulation in the ArceOS hypervisor environment.
Crate Configuration
flowchart TD
subgraph subGraph2["Standard Dependencies"]
SERDE["serde: 1.0.204"]
CFGIF["cfg-if: 1.0"]
end
subgraph subGraph1["ArceOS Dependencies"]
AXERRNO["axerrno: 0.1.0"]
AXADDR["axaddrspace: git"]
MEMADDR["memory_addr: 0.3"]
end
subgraph subGraph0["Package Metadata"]
NAME["name: axdevice_base"]
EDITION["edition: 2024"]
VERSION["version: workspace"]
end
NAME --> AXADDR
NAME --> AXERRNO
NAME --> CFGIF
NAME --> MEMADDR
NAME --> SERDE
Sources: axdevice_base/Cargo.toml(L1 - L9) axdevice_base/Cargo.toml(L10 - L16)
The crate uses Rust edition 2024 and inherits workspace-level version and metadata. Key configuration aspects include:
- No-std compatibility: Uses
allocfeature for collections without requiring standard library - Serialization support: Serde with
deriveandallocfeatures enabled - Conditional compilation:
cfg-iffor platform-specific code paths
Dependency Architecture
The crate's dependencies are strategically chosen to support hypervisor device emulation while maintaining compatibility with embedded environments.
flowchart TD
subgraph subGraph3["Dependency Purposes"]
ERRHAND["Error handling and result types"]
GUESTMEM["Guest physical address management"]
MEMUTIL["Memory address utilities"]
SERIAL["Device type serialization"]
CONDITIONAL["Platform-specific compilation"]
end
subgraph subGraph2["axdevice_base Dependencies"]
subgraph subGraph1["External Crates"]
SERDE["serde"]
CFGIF["cfg-if"]
end
subgraph subGraph0["ArceOS Ecosystem"]
ERRNO["axerrno"]
ADDRSPACE["axaddrspace"]
MEMADDR["memory_addr"]
end
end
ADDRSPACE --> GUESTMEM
CFGIF --> CONDITIONAL
ERRNO --> ERRHAND
MEMADDR --> MEMUTIL
SERDE --> SERIAL
Sources: axdevice_base/Cargo.toml(L11 - L16)
ArceOS-Specific Dependencies
axerrno: ProvidesAxResult<T>type for consistent error handling across the ArceOS ecosystemaxaddrspace: SuppliesGuestPhysAddrand address range management for hypervisor memory operationsmemory_addr: Low-level memory address manipulation utilities
External Dependencies
serde: Enables serialization of device types withderivemacros andallocfeature for no-std environmentscfg-if: Facilitates conditional compilation for different target architectures
Integration Points
ArceOS Hypervisor Integration
The axdevice_base crate integrates with the broader ArceOS hypervisor ecosystem through well-defined interfaces:
flowchart TD
subgraph subGraph2["Device Implementations"]
DEVS["Future Device Crates"]
end
subgraph subGraph1["axdevice_base Integration"]
BDO["BaseDeviceOps trait"]
EDT["EmuDeviceType enum"]
AR["AddrRange"]
end
subgraph subGraph0["ArceOS Hypervisor Core"]
HV["Hypervisor Engine"]
AS["Address Space Manager"]
VM["Virtual Machine Instance"]
end
AS --> AR
BDO --> EDT
DEVS --> BDO
DEVS --> EDT
HV --> BDO
VM --> HV
Sources: axdevice_base/Cargo.toml(L14 - L15)
Target Architecture Support
The crate is designed to support multiple target architectures commonly used in embedded hypervisor environments:
| Architecture | Target Triple | Purpose |
|---|---|---|
| x86_64 | x86_64-unknown-linux-gnu | Development and testing |
| x86_64 bare-metal | x86_64-unknown-none | Hypervisor deployment |
| RISC-V 64-bit | riscv64gc-unknown-none-elf | Embedded RISC-V systems |
| ARM64 | aarch64-unknown-none-softfloat | ARM-based hypervisors |
The architecture support reflects the project's focus on ARM virtualization (evidenced by the prevalence of ARM interrupt controller device types) while maintaining cross-platform compatibility.
Extensibility Design
The workspace structure anticipates future expansion with additional device implementation crates:
flowchart TD
subgraph subGraph1["Future Device Crates"]
CONSOLE["axdevice_console"]
VIRTIO["axdevice_virtio"]
ARM["axdevice_arm_gic"]
IOMMU["axdevice_iommu"]
end
subgraph subGraph0["Current Structure"]
BASE["axdevice_base"]
end
BASE --> ARM
BASE --> CONSOLE
BASE --> IOMMU
BASE --> VIRTIO
Sources: Cargo.toml(L4 - L6)
The workspace member list currently contains only axdevice_base, but the structure supports adding device-specific implementation crates that would depend on the foundation crate and implement the BaseDeviceOps trait for specific hardware categories.
Core Architecture
Relevant source files
This document outlines the fundamental design patterns, traits, and abstractions that form the foundation of the axdevice_crates device emulation system. It covers the high-level architectural decisions, the trait-based abstraction model, and how the core components interact within the ArceOS hypervisor ecosystem.
For detailed documentation of the BaseDeviceOps trait implementation, see BaseDeviceOps Trait. For comprehensive coverage of device type classifications, see Device Type System. For specifics on memory management and guest physical addressing, see Address Space Management.
Architectural Overview
The axdevice_crates system implements a trait-based device emulation architecture designed specifically for the ArceOS hypervisor. The architecture centers around a unified abstraction layer that enables consistent device emulation across different hardware types while maintaining the performance characteristics required for hypervisor environments.
Core Architectural Components
flowchart TD
subgraph subGraph3["External Dependencies"]
AE["axerrno::AxResult"]
AA["axaddrspace::GuestPhysAddr"]
MA["memory_addr::AddrRange"]
end
subgraph subGraph2["Device Implementation Layer"]
DEV1["Console Device"]
DEV2["Interrupt Controller"]
DEV3["Virtio Device"]
DEVN["... Other Devices"]
end
subgraph subGraph1["Device Abstraction Layer"]
BDO["BaseDeviceOps Trait"]
EDT["EmuDeviceType Enum"]
AR["AddrRange"]
end
subgraph subGraph0["Hypervisor Integration Layer"]
HV["ArceOS Hypervisor Core"]
AS["Address Space Manager"]
end
AR --> AA
AR --> MA
AS --> AA
BDO --> AE
BDO --> AR
BDO --> EDT
DEV1 --> BDO
DEV1 --> EDT
DEV2 --> BDO
DEV2 --> EDT
DEV3 --> BDO
DEV3 --> EDT
DEVN --> BDO
DEVN --> EDT
HV --> BDO
Sources: axdevice_base/src/lib.rs(L1 - L31)
Trait-Based Device Abstraction
The system employs a trait-based abstraction model where all emulated devices implement the BaseDeviceOps trait. This design provides a uniform interface for device operations while allowing each device type to implement specific emulation logic.
BaseDeviceOps Contract
classDiagram
class BaseDeviceOps {
<<trait>>
+emu_type() EmuDeviceType
+address_range() AddrRange~GuestPhysAddr~
+handle_read(addr: GuestPhysAddr, width: usize) AxResult~usize~
+handle_write(addr: GuestPhysAddr, width: usize, val: usize)
}
class EmuDeviceType {
<<enum>>
EmuDeviceTConsole
EmuDeviceTGicdV2
EmuDeviceTVirtioBlk
+removable() bool
}
class AddrRange~GuestPhysAddr~ {
+start GuestPhysAddr
+end GuestPhysAddr
+contains(addr: GuestPhysAddr) bool
}
class GuestPhysAddr {
<<from axaddrspace>>
}
class AxResult~T~ {
<<from axerrno>>
Ok(T)
Err(AxError)
}
BaseDeviceOps --> EmuDeviceType : "returns"
BaseDeviceOps --> AddrRange : "returns"
BaseDeviceOps --> AxResult : "returns"
AddrRange --> GuestPhysAddr : "contains"
Sources: axdevice_base/src/lib.rs(L20 - L30)
The trait defines four essential operations that every emulated device must support:
| Method | Purpose | Return Type |
|---|---|---|
| emu_type() | Device type identification | EmuDeviceType |
| address_range() | Memory mapping boundaries | AddrRange |
| handle_read() | Read operation emulation | AxResult |
| handle_write() | Write operation emulation | () |
Memory-Mapped Device Emulation Model
The architecture implements a memory-mapped I/O (MMIO) model where devices are accessed through specific guest physical address ranges. This design mirrors how real hardware devices are typically accessed and provides natural integration with existing hypervisor memory management systems.
Device Access Flow
sequenceDiagram
participant GuestVM as "Guest VM"
participant ArceOSHypervisor as "ArceOS Hypervisor"
participant BaseDeviceOpsImplementation as "BaseDeviceOps Implementation"
participant EmuDeviceType as "EmuDeviceType"
GuestVM ->> ArceOSHypervisor: "Memory Access (GuestPhysAddr)"
ArceOSHypervisor ->> BaseDeviceOpsImplementation: "address_range()"
BaseDeviceOpsImplementation ->> ArceOSHypervisor: "AddrRange<GuestPhysAddr>"
alt "Address in device range"
ArceOSHypervisor ->> BaseDeviceOpsImplementation: "emu_type()"
BaseDeviceOpsImplementation ->> EmuDeviceType: "Get device classification"
EmuDeviceType ->> BaseDeviceOpsImplementation: "Device type metadata"
BaseDeviceOpsImplementation ->> ArceOSHypervisor: "EmuDeviceType"
alt "Read Operation"
ArceOSHypervisor ->> BaseDeviceOpsImplementation: "handle_read(addr, width)"
BaseDeviceOpsImplementation ->> BaseDeviceOpsImplementation: "Perform device-specific read logic"
BaseDeviceOpsImplementation ->> ArceOSHypervisor: "AxResult<usize>"
ArceOSHypervisor ->> GuestVM: "Return read data"
else "Write Operation"
ArceOSHypervisor ->> BaseDeviceOpsImplementation: "handle_write(addr, width, val)"
BaseDeviceOpsImplementation ->> BaseDeviceOpsImplementation: "Perform device-specific write logic"
ArceOSHypervisor ->> GuestVM: "Complete write operation"
end
else "Address not in device range"
ArceOSHypervisor ->> GuestVM: "Handle as regular memory access"
end
Sources: axdevice_base/src/lib.rs(L24 - L29)
Integration with Hypervisor Core
The device emulation system integrates tightly with the ArceOS hypervisor through several key interfaces and design decisions:
Dependency Architecture
| Component | Purpose | Integration Point |
|---|---|---|
| axerrno::AxResult | Error handling consistency | All fallible operations returnAxResult |
| axaddrspace::GuestPhysAddr | Guest memory addressing | Device address ranges and access parameters |
| memory_addr::AddrRange | Address range management | Device memory mapping boundaries |
No Standard Library Constraint
The entire system operates under #![no_std] constraints, reflecting its target deployment in embedded hypervisor environments. This architectural decision influences several design choices:
- Use of
alloccrate for heap-allocated collections when needed - Reliance on external crates for core functionality (error handling, address management)
- Emphasis on zero-cost abstractions and compile-time optimizations
Sources: axdevice_base/src/lib.rs(L1)
Design Principles
The architecture embodies several key design principles that guide the system's development and usage:
Uniform Interface: All devices implement identical trait methods, enabling generic device management code in the hypervisor.
Type Safety: Strong typing through EmuDeviceType enum and GuestPhysAddr wrapper prevents common addressing errors.
Performance: Direct trait dispatch and no_std environment minimize runtime overhead.
Extensibility: New device types can be added by implementing BaseDeviceOps and extending EmuDeviceType.
ArceOS Integration: Deep integration with ArceOS ecosystem through shared address space management and error handling patterns.
Sources: axdevice_base/src/lib.rs(L3 - L18)
BaseDeviceOps Trait
Relevant source files
This document covers the BaseDeviceOps trait, which defines the core interface that all emulated devices must implement in the ArceOS hypervisor device emulation system. The trait provides a uniform contract for device identification, address mapping, and memory access handling across different types of virtualized hardware.
For information about specific device types that implement this trait, see Device Type System. For details about address space management and memory mapping, see Address Space Management.
Trait Overview
The BaseDeviceOps trait serves as the fundamental abstraction layer for device emulation in the ArceOS hypervisor. It defines four essential operations that enable the hypervisor to uniformly interact with different types of emulated devices, from simple console devices to complex interrupt controllers.
flowchart TD
subgraph Parameters["Parameters"]
GPA["GuestPhysAddr"]
WIDTH["usize (width)"]
VAL["usize (val)"]
end
subgraph subGraph1["Return Types"]
EDT["EmuDeviceType"]
AR["AddrRange"]
AXR["AxResult"]
UNIT["()"]
end
subgraph subGraph0["BaseDeviceOps Contract"]
BDO["BaseDeviceOps trait"]
EMU["emu_type()"]
ADDR["address_range()"]
READ["handle_read()"]
WRITE["handle_write()"]
end
ADDR --> AR
BDO --> ADDR
BDO --> EMU
BDO --> READ
BDO --> WRITE
EMU --> EDT
READ --> AXR
READ --> GPA
READ --> WIDTH
WRITE --> GPA
WRITE --> UNIT
WRITE --> VAL
WRITE --> WIDTH
The trait is designed for no_std environments and integrates with the ArceOS ecosystem through typed address spaces and standardized error handling.
Sources: axdevice_base/src/lib.rs(L20 - L30)
Method Specifications
Device Type Identification
flowchart TD
subgraph subGraph0["Device Categories"]
CONSOLE["EmuDeviceTConsole"]
VIRTIO["EmuDeviceTVirtioBlk"]
GIC["EmuDeviceTGicdV2"]
OTHER["Unsupported markdown: list"]
end
DEV["Device Implementation"]
EMU_TYPE["emu_type()"]
EDT["EmuDeviceType"]
DEV --> EMU_TYPE
EDT --> CONSOLE
EDT --> GIC
EDT --> OTHER
EDT --> VIRTIO
EMU_TYPE --> EDT
The emu_type() method returns an EmuDeviceType enum value that identifies the specific type of device being emulated. This enables the hypervisor to apply device-specific logic and optimizations.
| Method | Signature | Purpose |
|---|---|---|
| emu_type | fn emu_type(&self) -> EmuDeviceType | Device type identification for hypervisor routing |
Sources: axdevice_base/src/lib.rs(L22 - L23)
Address Range Management
flowchart TD
subgraph subGraph1["Hypervisor Usage"]
LOOKUP["Address lookup"]
ROUTING["Device routing"]
VALIDATION["Access validation"]
end
subgraph subGraph0["Address Space Components"]
GPA_START["start: GuestPhysAddr"]
GPA_END["end: GuestPhysAddr"]
SIZE["size calculation"]
end
ADDR_RANGE["address_range()"]
AR["AddrRange"]
ADDR_RANGE --> AR
AR --> GPA_END
AR --> GPA_START
AR --> LOOKUP
AR --> ROUTING
AR --> SIZE
AR --> VALIDATION
The address_range() method defines the guest physical address space region that the device occupies. The hypervisor uses this information to route memory accesses to the appropriate device implementation.
| Method | Signature | Purpose |
|---|---|---|
| address_range | fn address_range(&self) -> AddrRange | Memory-mapped I/O address space definition |
Sources: axdevice_base/src/lib.rs(L24 - L25)
Memory Access Handlers
flowchart TD
subgraph subGraph2["Parameters and Results"]
GPA_PARAM["GuestPhysAddr addr"]
WIDTH_PARAM["usize width"]
VAL_PARAM["usize val"]
RESULT["AxResult"]
VOID_RESULT["()"]
end
subgraph subGraph1["BaseDeviceOps Methods"]
HANDLE_READ["handle_read(addr, width)"]
HANDLE_WRITE["handle_write(addr, width, val)"]
end
subgraph subGraph0["Memory Access Flow"]
GUEST["Guest VM Memory Access"]
HV_DECODE["Hypervisor Address Decode"]
DEVICE_CHECK["Device Range Check"]
end
DEVICE_CHECK --> HANDLE_READ
DEVICE_CHECK --> HANDLE_WRITE
GUEST --> HV_DECODE
HANDLE_READ --> GPA_PARAM
HANDLE_READ --> RESULT
HANDLE_READ --> WIDTH_PARAM
HANDLE_WRITE --> GPA_PARAM
HANDLE_WRITE --> VAL_PARAM
HANDLE_WRITE --> VOID_RESULT
HANDLE_WRITE --> WIDTH_PARAM
HV_DECODE --> DEVICE_CHECK
The memory access handlers implement the core device emulation logic. The handle_read() method processes guest read operations and returns emulated device state, while handle_write() processes guest write operations that may modify device behavior.
| Method | Signature | Return | Purpose |
|---|---|---|---|
| handle_read | fn handle_read(&self, addr: GuestPhysAddr, width: usize) -> AxResult | AxResult | Process guest memory read operations |
| handle_write | fn handle_write(&self, addr: GuestPhysAddr, width: usize, val: usize) | () | Process guest memory write operations |
Sources: axdevice_base/src/lib.rs(L26 - L29)
Implementation Requirements
Error Handling
Read operations return AxResult<usize> to handle potential emulation errors such as invalid register addresses or unsupported access widths. Write operations use a void return type, with error conditions typically handled through device-specific state changes or logging.
Access Width Support
The width parameter specifies the memory access size in bytes (typically 1, 2, 4, or 8). Device implementations must handle the access widths supported by their emulated hardware, potentially returning errors for unsupported widths.
Address Validation
Device implementations should validate that the provided addr parameter falls within their declared address range and corresponds to a valid device register or memory location.
Integration Patterns
flowchart TD
subgraph subGraph2["External Dependencies"]
AXADDRSPACE["axaddrspace::GuestPhysAddr"]
AXERRNO["axerrno::AxResult"]
MEMORY_ADDR["memory_addr::AddrRange"]
end
subgraph subGraph1["Device Implementation"]
DEVICE["Device Struct"]
BDO_IMPL["BaseDeviceOps impl"]
DEV_STATE["Device State"]
end
subgraph subGraph0["ArceOS Hypervisor"]
HV_CORE["Hypervisor Core"]
ADDR_MGR["Address Space Manager"]
VM_EXIT["VM Exit Handler"]
end
ADDR_MGR --> BDO_IMPL
BDO_IMPL --> AXADDRSPACE
BDO_IMPL --> AXERRNO
BDO_IMPL --> DEVICE
BDO_IMPL --> MEMORY_ADDR
DEVICE --> DEV_STATE
HV_CORE --> ADDR_MGR
VM_EXIT --> HV_CORE
The trait integrates with the ArceOS hypervisor through standardized address space management and error handling. Device implementations typically maintain internal state and use the trait methods as the primary interface for hypervisor interaction.
Sources: axdevice_base/src/lib.rs(L11 - L18)
Device Type System
Relevant source files
This document covers the EmuDeviceType enumeration and associated type system that categorizes and manages different classes of emulated devices in the ArceOS hypervisor. The device type system provides unified identification, classification, and removability management for all supported emulated hardware devices.
For information about how devices implement behavior through the trait system, see BaseDeviceOps Trait. For details about memory mapping and address ranges, see Address Space Management.
Overview
The device type system is built around the EmuDeviceType enum defined in axdevice_base/src/emu_type.rs(L3 - L28) This enumeration serves as the central registry of all supported emulated device types in the ArceOS hypervisor ecosystem, providing type identification, categorization, and operational characteristics for each device class.
flowchart TD
subgraph subGraph1["Integration Points"]
BDO["BaseDeviceOps trait"]
HV["ArceOS Hypervisor"]
end
subgraph subGraph0["Device Type System Core"]
EDT["EmuDeviceType enum"]
REM["removable() method"]
CONV["from_usize() method"]
DISP["Display implementation"]
end
BDO --> EDT
EDT --> CONV
EDT --> DISP
EDT --> HV
EDT --> REM
HV --> BDO
Sources: axdevice_base/src/emu_type.rs(L3 - L28) axdevice_base/src/lib.rs(L20 - L30)
Device Type Enumeration
The EmuDeviceType enum defines 11 distinct device types, each assigned a unique numeric identifier from 0 to 10. The enumeration uses explicit discriminant values to ensure stable serialization and conversion.
| Device Type | Value | Description |
|---|---|---|
| EmuDeviceTConsole | 0 | Console device |
| EmuDeviceTGicdV2 | 1 | ARM interrupt controller V2 device |
| EmuDeviceTGPPT | 2 | Partial passthrough interrupt controller device |
| EmuDeviceTVirtioBlk | 3 | Virtio block device |
| EmuDeviceTVirtioNet | 4 | Virtio net device |
| EmuDeviceTVirtioConsole | 5 | Virtio console device |
| EmuDeviceTIOMMU | 6 | IOMMU device |
| EmuDeviceTICCSRE | 7 | Interrupt ICC SRE device |
| EmuDeviceTSGIR | 8 | Interrupt ICC SGIR device |
| EmuDeviceTGICR | 9 | Interrupt controller GICR device |
| EmuDeviceTMeta | 10 | Meta device |
Sources: axdevice_base/src/emu_type.rs(L5 - L28)
Device Categories
Device Type Taxonomy by Functional Category
flowchart TD
subgraph subGraph2["Virtio Paravirtualization"]
IOMMU["EmuDeviceTIOMMU"]
META["EmuDeviceTMeta"]
VBLK["EmuDeviceTVirtioBlk"]
VNET["EmuDeviceTVirtioNet"]
VCONS2["EmuDeviceTVirtioConsole"]
GICD["EmuDeviceTGicdV2"]
GPPT["EmuDeviceTGPPT"]
ICCSRE["EmuDeviceTICCSRE"]
CONS["EmuDeviceTConsole"]
VCONS["EmuDeviceTVirtioConsole"]
subgraph subGraph0["Console and I/O Devices"]
subgraph subGraph3["System Infrastructure"]
IOMMU["EmuDeviceTIOMMU"]
META["EmuDeviceTMeta"]
VBLK["EmuDeviceTVirtioBlk"]
VNET["EmuDeviceTVirtioNet"]
GICD["EmuDeviceTGicdV2"]
GPPT["EmuDeviceTGPPT"]
CONS["EmuDeviceTConsole"]
VCONS["EmuDeviceTVirtioConsole"]
end
end
end
subgraph subGraph1["ARM Interrupt Controllers"]
IOMMU["EmuDeviceTIOMMU"]
META["EmuDeviceTMeta"]
VBLK["EmuDeviceTVirtioBlk"]
VNET["EmuDeviceTVirtioNet"]
VCONS2["EmuDeviceTVirtioConsole"]
GICD["EmuDeviceTGicdV2"]
GPPT["EmuDeviceTGPPT"]
ICCSRE["EmuDeviceTICCSRE"]
SGIR["EmuDeviceTSGIR"]
GICR["EmuDeviceTGICR"]
CONS["EmuDeviceTConsole"]
VCONS["EmuDeviceTVirtioConsole"]
end
Sources: axdevice_base/src/emu_type.rs(L5 - L28) axdevice_base/src/emu_type.rs(L34 - L45)
ARM Interrupt Controller Focus
The device type system shows a strong focus on ARM architecture virtualization, with 5 out of 11 device types (45%) dedicated to ARM Generic Interrupt Controller (GIC) components:
EmuDeviceTGicdV2: ARM GICv2 distributor interfaceEmuDeviceTGPPT: Partial passthrough interrupt controllerEmuDeviceTICCSRE: ICC System Register Enable interfaceEmuDeviceTSGIR: ICC Software Generated Interrupt RegisterEmuDeviceTGICR: GIC redistributor interface
Virtio Paravirtualization Support
Three device types implement Virtio paravirtualization standards:
EmuDeviceTVirtioBlk: Block storage deviceEmuDeviceTVirtioNet: Network interface deviceEmuDeviceTVirtioConsole: Console device
Sources: axdevice_base/src/emu_type.rs(L5 - L28)
Removability Classification
The device type system categorizes devices by removability using the removable() method defined in axdevice_base/src/emu_type.rs(L52 - L64) This classification determines whether devices can be dynamically added or removed from a running virtual machine.
Device Removability Classification
flowchart TD
subgraph subGraph0["Removable Devices (8 of 11)"]
GPPT_R["EmuDeviceTGPPT"]
VBLK_R["EmuDeviceTVirtioBlk"]
VNET_R["EmuDeviceTVirtioNet"]
GICR_R["EmuDeviceTGICR"]
VCONS_R["EmuDeviceTVirtioConsole"]
subgraph subGraph1["Non-Removable Devices (3 of 11)"]
CONS_NR["EmuDeviceTConsole"]
IOMMU_NR["EmuDeviceTIOMMU"]
META_NR["EmuDeviceTMeta"]
GICD_R["EmuDeviceTGicdV2"]
SGIR_R["EmuDeviceTSGIR"]
ICCSRE_R["EmuDeviceTICCSRE"]
end
end
Sources: axdevice_base/src/emu_type.rs(L52 - L64)
Removable Devices
The following 8 device types are classified as removable:
- All ARM interrupt controller devices
- All Virtio paravirtualization devices
Non-Removable Devices
The following 3 device types are classified as non-removable:
- Console device: Essential for basic VM operation
- IOMMU device: Critical system infrastructure
- Meta device: Core hypervisor functionality
Sources: axdevice_base/src/emu_type.rs(L53 - L63)
Type Conversion and Identification
Numeric Conversion
The from_usize() method provides conversion from numeric identifiers to device types, supporting serialization and external configuration systems.
// Example conversion pattern from axdevice_base/src/emu_type.rs:67-82
pub fn from_usize(value: usize) -> EmuDeviceType {
match value {
0 => EmuDeviceType::EmuDeviceTConsole,
1 => EmuDeviceType::EmuDeviceTGicdV2,
// ... additional mappings
_ => panic!("Unknown EmuDeviceType value: {}", value),
}
}
String Representation
The Display trait implementation in axdevice_base/src/emu_type.rs(L30 - L47) provides human-readable names for debugging and logging:
EmuDeviceTConsole→ "console"EmuDeviceTGicdV2→ "Arm interrupt controller V2"EmuDeviceTVirtioBlk→ "virtio block"
Sources: axdevice_base/src/emu_type.rs(L30 - L47) axdevice_base/src/emu_type.rs(L67 - L82)
Integration with BaseDeviceOps
The device type system integrates with the BaseDeviceOps trait through the emu_type() method, enabling runtime device identification:
Device Type Integration Flow
sequenceDiagram
participant ArceOSHypervisor as "ArceOS Hypervisor"
participant DeviceBaseDeviceOps as "Device (BaseDeviceOps)"
participant EmuDeviceType as "EmuDeviceType"
ArceOSHypervisor ->> DeviceBaseDeviceOps: "emu_type()"
DeviceBaseDeviceOps ->> EmuDeviceType: "return device type"
EmuDeviceType ->> DeviceBaseDeviceOps: "EmuDeviceType variant"
DeviceBaseDeviceOps ->> ArceOSHypervisor: "device type identifier"
ArceOSHypervisor ->> EmuDeviceType: "removable()"
EmuDeviceType ->> ArceOSHypervisor: "bool result"
ArceOSHypervisor ->> EmuDeviceType: "Display format"
EmuDeviceType ->> ArceOSHypervisor: "human readable name"
Sources: axdevice_base/src/lib.rs(L22 - L23) axdevice_base/src/emu_type.rs(L50 - L83)
This integration enables the hypervisor to:
- Identify device types at runtime
- Determine removability characteristics
- Apply type-specific handling logic
- Generate debugging output with descriptive names
Sources: axdevice_base/src/lib.rs(L20 - L30) axdevice_base/src/emu_type.rs(L1 - L84)
Address Space Management
Relevant source files
This document covers the address space management system used by emulated devices in the ArceOS hypervisor. It explains how guest physical addresses (GuestPhysAddr) are mapped to device handlers, how address ranges (AddrRange<GuestPhysAddr>) define device memory regions, and the integration with the axaddrspace crate for hypervisor address space management.
For information about device type classification and the BaseDeviceOps trait interface, see Device Type System and BaseDeviceOps Trait.
Core Address Space Concepts
The address space management system is built around two fundamental types from external crates:
GuestPhysAddrfrom theaxaddrspacecrate represents individual guest physical addressesAddrRange<GuestPhysAddr>from thememory_addrcrate represents contiguous address ranges
Every emulated device must define its memory-mapped region through the address_range() method in the BaseDeviceOps trait, which returns an AddrRange<GuestPhysAddr> defining the device's location in guest physical memory space.
Address Space Type Hierarchy
flowchart TD
subgraph subGraph0["External Crates"]
AS["axaddrspace crate"]
MA["memory_addr crate"]
end
GPA["GuestPhysAddr"]
AR["AddrRange<GuestPhysAddr>"]
BDO["BaseDeviceOps::address_range()"]
HR["BaseDeviceOps::handle_read(addr: GuestPhysAddr)"]
HW["BaseDeviceOps::handle_write(addr: GuestPhysAddr)"]
AR --> BDO
AS --> GPA
GPA --> AR
GPA --> HR
GPA --> HW
MA --> AR
Sources: axdevice_base/src/lib.rs(L11 - L14) axdevice_base/src/lib.rs(L25) axdevice_base/src/lib.rs(L27 - L29)
Guest Physical Address Mapping
Guest physical addresses represent the memory layout as seen by the virtual machine. Each emulated device occupies a specific range within this address space, allowing the hypervisor to route memory accesses to the appropriate device handler.
The BaseDeviceOps trait defines the contract for address space integration:
| Method | Parameter Type | Return Type | Purpose |
|---|---|---|---|
| address_range() | None | AddrRange | Define device memory region |
| handle_read() | addr: GuestPhysAddr, width: usize | AxResult | Handle guest read access |
| handle_write() | addr: GuestPhysAddr, width: usize, val: usize | None | Handle guest write access |
The width parameter specifies the access size (1, 2, 4, or 8 bytes), allowing devices to handle different data types appropriately.
Sources: axdevice_base/src/lib.rs(L25 - L29)
Device Address Range Management
Each device implementation must return a valid AddrRange<GuestPhysAddr> that defines its memory-mapped region. This range is used by the hypervisor's address space manager to route memory accesses to the correct device handler.
Device Address Range Resolution Flow
The address space manager maintains a mapping between guest physical address ranges and device handlers, enabling efficient routing of memory operations without requiring linear searches through all devices.
Sources: axdevice_base/src/lib.rs(L25)
Memory Access Routing Flow
When a guest virtual machine accesses memory, the hypervisor must determine whether the access targets an emulated device or regular memory. This determination is made using the address ranges returned by each device's address_range() method.
Memory Access Decision Tree
flowchart TD
subgraph subGraph1["Hypervisor Memory Manager"]
MEM["Handle as regular memory"]
end
subgraph subGraph0["Device Handler Methods"]
READ["handle_read(addr, width)"]
WRITE["handle_write(addr, width, val)"]
end
MA["Guest Memory Access at GuestPhysAddr"]
AR["Check address_range() for all devices"]
HIT["Address matches device range"]
MISS["Address does not match any device"]
AR --> HIT
AR --> MISS
HIT --> READ
HIT --> WRITE
MA --> AR
MISS --> MEM
The routing decision is based on whether the guest physical address falls within any device's declared address range. If multiple devices claim overlapping ranges, the behavior is implementation-defined by the hypervisor's address space manager.
Sources: axdevice_base/src/lib.rs(L27 - L29)
Integration with axaddrspace
The axaddrspace crate provides the GuestPhysAddr type that represents addresses in the guest's physical memory space. This integration ensures type safety and prevents confusion between different address spaces (host physical, guest physical, guest virtual).
The choice of GuestPhysAddr as the address type in BaseDeviceOps methods indicates that device emulation operates at the guest physical memory level, after any guest virtual-to-physical translation has occurred within the guest VM.
Address Space Integration Architecture
flowchart TD
subgraph subGraph2["Host System"]
HPA["Host Physical Address"]
end
subgraph subGraph1["ArceOS Hypervisor"]
ASPCM["axaddrspace::GuestPhysAddr"]
AR["memory_addr::AddrRange"]
BDO["BaseDeviceOps methods"]
end
subgraph subGraph0["Guest VM"]
GVA["Guest Virtual Address"]
GPA["Guest Physical Address"]
end
ASPCM --> AR
ASPCM --> BDO
BDO --> HPA
GPA --> ASPCM
GPA --> HPA
GVA --> GPA
This architecture ensures that emulated devices receive addresses in a consistent format regardless of the guest's internal memory management configuration.
Sources: axdevice_base/src/lib.rs(L13) axdevice_base/src/lib.rs(L25) axdevice_base/src/lib.rs(L27 - L29)
Dependencies and Integration
Relevant source files
This document covers the external crate dependencies used by axdevice_base and explains how they integrate to support device emulation within the ArceOS hypervisor ecosystem. The focus is on dependency selection rationale, integration patterns, and the no_std compatibility requirements.
For detailed coverage of how ArceOS-specific dependencies (axerrno, axaddrspace, memory_addr) enable hypervisor functionality, see ArceOS Integration. For information about how dependencies support the core device abstraction, see BaseDeviceOps Trait.
Dependency Architecture Overview
The axdevice_base crate maintains a minimal dependency footprint while supporting essential hypervisor device emulation capabilities. All dependencies are carefully selected for no_std compatibility and embedded system constraints.
Dependency Categories and Integration
flowchart TD
subgraph subGraph3["Core Allocator"]
AL["alloc::collections"]
end
subgraph subGraph2["Standard Utilities"]
SE["serde (derive, alloc)"]
CF["cfg-if"]
end
subgraph subGraph1["ArceOS Ecosystem Dependencies"]
AE["axerrno::AxResult"]
AA["axaddrspace::AddrRange"]
MA["memory_addr::GuestPhysAddr"]
end
subgraph axdevice_base["axdevice_base"]
BDO["BaseDeviceOps"]
EMU["EmuDeviceType"]
end
AA --> MA
BDO --> AA
BDO --> AE
BDO --> MA
EMU --> CF
EMU --> SE
SE --> AL
Sources: axdevice_base/Cargo.toml(L10 - L16)
Dependency Analysis
ArceOS Hypervisor Dependencies
The three ArceOS-specific dependencies form the core integration layer that enables device emulation within the hypervisor:
| Dependency | Version | Purpose | Integration Point |
|---|---|---|---|
| axerrno | 0.1.0 | Error handling for hypervisor operations | BaseDeviceOpsreturn types |
| axaddrspace | git latest | Guest physical address management | Device address range mapping |
| memory_addr | 0.3 | Memory address utilities | Address range boundaries |
ArceOS Integration Pattern
sequenceDiagram
participant DeviceImplementation as "Device Implementation"
participant BaseDeviceOps as "BaseDeviceOps"
participant axerrnoAxResult as "axerrno::AxResult"
participant axaddrspaceAddrRange as "axaddrspace::AddrRange"
participant memory_addrGuestPhysAddr as "memory_addr::GuestPhysAddr"
DeviceImplementation ->> BaseDeviceOps: "implement trait"
BaseDeviceOps ->> axaddrspaceAddrRange: "address_range()"
axaddrspaceAddrRange ->> memory_addrGuestPhysAddr: "AddrRange<GuestPhysAddr>"
memory_addrGuestPhysAddr ->> axaddrspaceAddrRange: "physical address boundaries"
axaddrspaceAddrRange ->> BaseDeviceOps: "device memory mapping"
BaseDeviceOps ->> axerrnoAxResult: "handle_read() -> AxResult<usize>"
axerrnoAxResult ->> DeviceImplementation: "hypervisor-compatible result"
Sources: axdevice_base/Cargo.toml(L14 - L16)
Standard Library Dependencies
serde Configuration
The serde dependency uses a specific feature configuration optimized for no_std environments:
serde = { version = "1.0.204", default-features = false, features = ["derive", "alloc"] }
default-features = false: Removesstddependencyderivefeature: Enables#[derive(Serialize, Deserialize)]macros forEmuDeviceTypeallocfeature: Provides collections support without requiring full standard library
Conditional Compilation Support
The cfg-if crate enables clean conditional compilation patterns:
cfg-if = "1.0"
This dependency supports platform-specific device behavior while maintaining code clarity across different target architectures (x86_64, RISC-V, ARM64).
Sources: axdevice_base/Cargo.toml(L11 - L12)
Integration Patterns
Error Propagation Chain
The error handling integration follows a consistent pattern from hypervisor operations through device emulation:
Error Flow Through Dependencies
flowchart TD
subgraph subGraph3["Address Resolution"]
AA["AddrRange"]
MA["GuestPhysAddr"]
end
subgraph subGraph2["Error Types"]
AR["AxResult"]
AE["AxError"]
end
subgraph subGraph1["Device Layer"]
BD["BaseDeviceOps::handle_read()"]
BDW["BaseDeviceOps::handle_write()"]
end
subgraph subGraph0["Hypervisor Layer"]
HV["ArceOS Hypervisor"]
end
AA --> MA
AR --> AE
BD --> AA
BD --> AR
BDW --> AA
BDW --> AR
HV --> BD
HV --> BDW
Sources: axdevice_base/Cargo.toml(L14 - L16)
Memory Address Integration
The address management dependencies create a unified addressing model:
- memory_addr::GuestPhysAddr: Provides type-safe guest physical addresses
- axaddrspace::AddrRange: Wraps
GuestPhysAddrinto address ranges - BaseDeviceOps::address_range(): Returns
AddrRange<GuestPhysAddr>for device mapping
This integration ensures that device emulation operates within the hypervisor's guest address space management without requiring device implementations to handle low-level address translation.
Serialization Integration
The serde integration with EmuDeviceType enables device type persistence and configuration management:
- Device type serialization for hypervisor state saving
- Configuration file parsing for device topology
- Inter-component communication with serialized device descriptors
The alloc feature dependency provides the necessary collection types for handling serialized device configurations without requiring full standard library support.
Sources: axdevice_base/Cargo.toml(L12)
Dependency Version Strategy
ArceOS Ecosystem Tracking
axerrno: Uses semantic versioning (0.1.0) for stable error handling interfaceaxaddrspace: Tracks git repository HEAD for active development coordinationmemory_addr: Uses stable crate version (0.3) for mature address utilities
External Dependencies
serde: Pinned to specific version (1.0.204) for build reproducibilitycfg-if: Uses caret range (1.0) allowing compatible updates
This strategy balances stability for core functionality with active development tracking for hypervisor-specific components.
Sources: axdevice_base/Cargo.toml(L11 - L16)
ArceOS Integration
Relevant source files
This page documents how axdevice_base integrates with the ArceOS hypervisor ecosystem through its core dependencies: axerrno, axaddrspace, and memory_addr. These dependencies enable standardized error handling, guest physical address management, and memory range abstractions essential for device emulation in the hypervisor context.
For information about the core device abstraction patterns, see Core Architecture. For implementation details of the traits and types, see BaseDeviceOps Trait.
ArceOS Dependency Overview
The axdevice_base crate relies on three key ArceOS ecosystem dependencies that provide fundamental hypervisor capabilities. These dependencies are tightly integrated into the BaseDeviceOps trait interface to ensure consistent device emulation across the ArceOS hypervisor.
Dependency Architecture
flowchart TD
subgraph subGraph2["External Dependencies"]
SE["serde"]
CF["cfg-if"]
AL["alloc"]
end
subgraph subGraph1["ArceOS Core Dependencies"]
AXE["axerrno::AxResult<T>"]
AXA["axaddrspace::GuestPhysAddr"]
MA["memory_addr::AddrRange<T>"]
end
subgraph axdevice_base["axdevice_base"]
BDO["BaseDeviceOps trait"]
AR["address_range()"]
HR["handle_read()"]
HW["handle_write()"]
ET["emu_type()"]
end
AR --> AXA
AR --> MA
BDO --> AL
BDO --> AR
BDO --> CF
BDO --> ET
BDO --> HR
BDO --> HW
BDO --> SE
HR --> AXA
HR --> AXE
HW --> AXA
Sources: axdevice_base/src/lib.rs(L11 - L14) axdevice_base/Cargo.toml(L10 - L16)
Dependency Declaration
The dependencies are declared in the crate's Cargo.toml with specific version requirements:
| Dependency | Version | Source | Purpose |
|---|---|---|---|
| axerrno | 0.1.0 | crates.io | Standardized error handling |
| axaddrspace | git main | GitHub repository | Guest physical address types |
| memory_addr | 0.3 | crates.io | Memory address range abstractions |
Sources: axdevice_base/Cargo.toml(L14 - L16)
Error Handling Integration:axerrno
The axerrno crate provides standardized error handling across the ArceOS ecosystem. In axdevice_base, it's used exclusively in the handle_read method of the BaseDeviceOps trait to indicate success or failure of read operations.
AxResult Usage Pattern
flowchart TD
subgraph subGraph1["AxResult Variants"]
OK["Ok(data: usize)"]
ERR["Err(AxError)"]
end
subgraph subGraph0["Device Read Operation Flow"]
GVM["Guest VM Memory Access"]
HV["ArceOS Hypervisor"]
DEV["Device Implementation"]
RES["AxResult<usize>"]
end
DEV --> RES
GVM --> HV
HV --> DEV
HV --> GVM
RES --> ERR
RES --> HV
RES --> OK
The handle_read method signature demonstrates this integration:
#![allow(unused)] fn main() { fn handle_read(&self, addr: GuestPhysAddr, width: usize) -> AxResult<usize> }
This design allows device implementations to return either:
Ok(data)- successful read with the actual data valueErr(error)- standardized error indicating why the read failed
Sources: axdevice_base/src/lib.rs(L27)
Address Space Integration:axaddrspace
The axaddrspace crate provides hypervisor-specific address space management types, particularly GuestPhysAddr which represents guest physical addresses in the virtualized environment.
GuestPhysAddr in BaseDeviceOps
The GuestPhysAddr type appears in three critical methods of the BaseDeviceOps trait:
flowchart TD
subgraph subGraph2["Hypervisor Context"]
GAS["Guest Address Space"]
EMU["Device Emulation"]
MMU["Memory Management"]
end
subgraph subGraph1["GuestPhysAddr Usage"]
ART["AddrRange<GuestPhysAddr>"]
RPA["Read Parameter Address"]
WPA["Write Parameter Address"]
end
subgraph subGraph0["BaseDeviceOps Methods Using GuestPhysAddr"]
AR["address_range()"]
HR["handle_read(addr: GuestPhysAddr, ...)"]
HW["handle_write(addr: GuestPhysAddr, ...)"]
end
AR --> ART
ART --> GAS
EMU --> MMU
GAS --> MMU
HR --> RPA
HW --> WPA
RPA --> EMU
WPA --> EMU
The integration enables:
- Address Range Definition:
address_range()returnsAddrRange<GuestPhysAddr>to define the guest physical memory region this device occupies - Read Access:
handle_read()receives aGuestPhysAddrparameter indicating which guest physical address is being read - Write Access:
handle_write()receives aGuestPhysAddrparameter indicating which guest physical address is being written
Sources: axdevice_base/src/lib.rs(L25 - L29)
Memory Range Integration:memory_addr
The memory_addr crate provides the AddrRange<T> generic type used to represent contiguous memory address ranges. In axdevice_base, it's specialized with GuestPhysAddr to define device memory regions.
AddrRange Specialization
flowchart TD
subgraph subGraph2["Device Memory Mapping"]
DEV1["Console Device Range"]
DEV2["GICD Device Range"]
DEV3["Virtio Device Range"]
DEVN["Other Device Ranges"]
end
subgraph subGraph1["axdevice_base Specialization"]
GPAR["AddrRange<GuestPhysAddr>"]
BDO["BaseDeviceOps::address_range()"]
end
subgraph subGraph0["memory_addr Crate"]
AR["AddrRange<T>"]
GEN["Generic Address Range"]
end
AR --> GPAR
BDO --> DEV1
BDO --> DEV2
BDO --> DEV3
BDO --> DEVN
GPAR --> BDO
This specialization allows each device implementation to precisely define:
- Start Address: Beginning of the device's guest physical address range
- Size/End Address: Extent of the device's memory-mapped region
- Bounds Checking: Automatic validation that access addresses fall within the device's range
The address_range() method is fundamental to the hypervisor's memory management, enabling it to route guest memory accesses to the appropriate emulated device based on the target address.
Sources: axdevice_base/src/lib.rs(L11 - L25)
Integration Patterns in BaseDeviceOps
The BaseDeviceOps trait demonstrates a cohesive integration pattern where each ArceOS dependency serves a specific role in the device emulation interface:
Method Integration Matrix
| Method | axerrno | axaddrspace | memory_addr | Purpose |
|---|---|---|---|---|
| emu_type() | ❌ | ❌ | ❌ | Device type identification |
| address_range() | ❌ | ✅ | ✅ | Memory region definition |
| handle_read() | ✅ | ✅ | ❌ | Read operation with error handling |
| handle_write() | ❌ | ✅ | ❌ | Write operation (no error return) |
Trait Method Signatures
The complete trait definition shows the integration points:
#![allow(unused)] fn main() { pub trait BaseDeviceOps { fn emu_type(&self) -> EmuDeviceType; fn address_range(&self) -> AddrRange<GuestPhysAddr>; // memory_addr + axaddrspace fn handle_read(&self, addr: GuestPhysAddr, width: usize) -> AxResult<usize>; // axaddrspace + axerrno fn handle_write(&self, addr: GuestPhysAddr, width: usize, val: usize); // axaddrspace } }
This design ensures that device implementations can seamlessly integrate with the ArceOS hypervisor's memory management and error handling systems.
Sources: axdevice_base/src/lib.rs(L21 - L30)
Hypervisor Ecosystem Context
The ArceOS integration dependencies position axdevice_base as a bridge between the hypervisor core and device-specific implementations. This integration enables:
- Unified Address Space: All devices operate within the same
GuestPhysAddraddress space managed by the hypervisor - Consistent Error Handling: Device operations return standardized
AxResulttypes that the hypervisor can process uniformly - Memory Region Management: Device address ranges integrate with the hypervisor's memory management unit for efficient routing
The dependency choices reflect ArceOS's design philosophy of providing specialized, composable components for hypervisor functionality while maintaining no_std compatibility for embedded and constrained environments.
Sources: axdevice_base/src/lib.rs(L1 - L14) axdevice_base/Cargo.toml(L14 - L16)
Development Guide
Relevant source files
This document provides practical information for developers working with or extending the axdevice_crates system. It covers the development environment setup, build processes, code quality standards, and contribution workflows specific to this hypervisor device emulation framework.
For detailed information about implementing specific device types, see Implementing New Devices. For comprehensive build system documentation, see Build System and CI.
Development Environment Requirements
The axdevice_crates project requires a specific development environment configuration to support its no_std embedded target and multi-architecture requirements.
Rust Toolchain Configuration
The project exclusively uses the nightly Rust toolchain to access cutting-edge features required for hypervisor development and no_std environments. The required toolchain components are:
| Component | Purpose |
|---|---|
| rust-src | Source code for cross-compilation to embedded targets |
| clippy | Advanced linting for code quality enforcement |
| rustfmt | Code formatting standardization |
Supported Target Architectures
The codebase maintains compatibility across four distinct target architectures, reflecting the diverse embedded and hypervisor deployment scenarios:
flowchart TD
subgraph subGraph3["Development Capabilities"]
UT["Unit Testing"]
DOC["Documentation Generation"]
BE["Bare Metal Execution"]
HV["Hypervisor Integration"]
end
subgraph subGraph2["Target Architecture Matrix"]
subgraph subGraph1["Bare Metal Targets"]
T2["x86_64-unknown-none"]
T3["riscv64gc-unknown-none-elf"]
T4["aarch64-unknown-none-softfloat"]
end
subgraph subGraph0["Hosted Environment"]
T1["x86_64-unknown-linux-gnu"]
end
end
T1 --> DOC
T1 --> UT
T2 --> BE
T2 --> HV
T3 --> BE
T3 --> HV
T4 --> BE
T4 --> HV
Sources: .github/workflows/ci.yml(L12) .github/workflows/ci.yml(L19)
Development Workflow Pipeline
The development process follows a strict automated pipeline that enforces code quality and multi-architecture compatibility:
flowchart TD
subgraph subGraph1["Documentation Workflow"]
DOC["Documentation Pipeline"]
DOCBUILD["cargo doc --no-deps --all-features"]
DOCCHECK["RUSTDOCFLAGS Validation"]
DEPLOY["GitHub Pages Deployment"]
end
subgraph subGraph0["Parallel Architecture Builds"]
BUILD["cargo build --target TARGET --all-features"]
B1["Build x86_64-unknown-linux-gnu"]
B2["Build x86_64-unknown-none"]
B3["Build riscv64gc-unknown-none-elf"]
B4["Build aarch64-unknown-none-softfloat"]
end
PR["Pull Request / Push"]
CI["CI Pipeline Trigger"]
FMT["cargo fmt --all --check"]
CLIPPY["cargo clippy --target TARGET --all-features"]
TEST["cargo test --target x86_64-unknown-linux-gnu"]
SUCCESS["Pipeline Success"]
B1 --> SUCCESS
B2 --> SUCCESS
B3 --> SUCCESS
B4 --> SUCCESS
BUILD --> B1
BUILD --> B2
BUILD --> B3
BUILD --> B4
BUILD --> TEST
CI --> FMT
CLIPPY --> BUILD
DEPLOY --> SUCCESS
DOC --> DOCBUILD
DOCBUILD --> DOCCHECK
DOCCHECK --> DEPLOY
FMT --> CLIPPY
PR --> CI
TEST --> DOC
Sources: .github/workflows/ci.yml(L22 - L30) .github/workflows/ci.yml(L44 - L48)
Code Quality Standards
Formatting Requirements
All code must pass cargo fmt --all --check without modifications. The project uses the standard Rust formatting conventions with no custom overrides.
Linting Configuration
The Clippy linting process runs with --all-features enabled and includes one specific allowance:
clippy::new_without_defaultis explicitly allowed via-A clippy::new_without_default
This exception acknowledges that hypervisor device emulation patterns may require new() methods without corresponding Default implementations due to the specialized initialization requirements of emulated hardware.
Documentation Standards
Documentation generation enforces strict quality standards through RUSTDOCFLAGS:
RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs
These flags ensure:
- Broken intra-doc links: All internal documentation links must resolve correctly
- Missing docs: All public APIs must include documentation
Sources: .github/workflows/ci.yml(L25) .github/workflows/ci.yml(L40)
Testing Strategy
Unit Testing Scope
Unit tests execute exclusively on the x86_64-unknown-linux-gnu target, which provides the hosted environment necessary for test infrastructure. The testing command includes --nocapture to ensure full test output visibility.
Multi-Architecture Validation
While unit tests run only on the hosted target, build verification occurs across all four supported architectures. This approach ensures:
- Code compiles correctly for all deployment targets
- no_std constraints are properly maintained
- Architecture-specific conditional compilation works correctly
Test Execution Conditions
Tests run conditionally based on the target architecture:
if: ${{ matrix.targets == 'x86_64-unknown-linux-gnu' }}
This constraint reflects the practical limitation that bare-metal targets cannot execute standard Rust test infrastructure.
Sources: .github/workflows/ci.yml(L28 - L30)
Documentation Workflow
Automated Documentation Generation
The documentation pipeline operates independently from the main CI pipeline and includes sophisticated deployment automation:
flowchart TD
subgraph subGraph2["Deployment Conditions"]
DC1["Default Branch Only"]
DC2["Single Commit Strategy"]
DC3["gh-pages Branch Target"]
end
subgraph subGraph1["Quality Controls"]
BIL["Broken Intra-doc Links Check"]
MD["Missing Docs Check"]
end
subgraph subGraph0["Documentation Generation Process"]
SRC["Source Code"]
DOCGEN["cargo doc --no-deps --all-features"]
VALIDATE["RUSTDOCFLAGS Validation"]
INDEX["Auto-generated index.html"]
DEPLOY["GitHub Pages Deployment"]
end
DEPLOY --> DC1
DEPLOY --> DC2
DEPLOY --> DC3
DOCGEN --> VALIDATE
INDEX --> DEPLOY
SRC --> DOCGEN
VALIDATE --> BIL
VALIDATE --> INDEX
VALIDATE --> MD
Index Generation Logic
The documentation system automatically generates a redirect index using shell commands:
printf '<meta http-equiv="refresh" content="0;url=%s/index.html">' $(cargo tree | head -1 | cut -d' ' -f1) > target/doc/index.html
This creates an automatic redirect from the documentation root to the primary crate's documentation.
Deployment Permissions
The documentation job requires contents: write permissions to deploy to GitHub Pages, reflecting the automated nature of the documentation publication process.
Sources: .github/workflows/ci.yml(L44 - L48) .github/workflows/ci.yml(L50 - L55)
Development Environment Setup
Repository Setup
# Clone the repository
git clone https://github.com/arceos-hypervisor/axdevice_crates
cd axdevice_crates
# Install nightly toolchain with required components
rustup toolchain install nightly
rustup default nightly
rustup component add rust-src clippy rustfmt
# Add required targets
rustup target add x86_64-unknown-none
rustup target add riscv64gc-unknown-none-elf
rustup target add aarch64-unknown-none-softfloat
Verification Commands
Verify your development environment matches CI requirements:
# Check formatting
cargo fmt --all -- --check
# Run linting
cargo clippy --all-features -- -A clippy::new_without_default
# Test build for all targets
cargo build --target x86_64-unknown-linux-gnu --all-features
cargo build --target x86_64-unknown-none --all-features
cargo build --target riscv64gc-unknown-none-elf --all-features
cargo build --target aarch64-unknown-none-softfloat --all-features
# Run tests
cargo test --target x86_64-unknown-linux-gnu -- --nocapture
# Generate documentation
cargo doc --no-deps --all-features
Sources: .github/workflows/ci.yml(L15 - L19) .github/workflows/ci.yml(L22 - L30)
Build System and CI
Relevant source files
This page documents the automated build system and continuous integration (CI) pipeline for the axdevice_crates repository. It covers the multi-architecture build strategy, code quality enforcement, testing procedures, and documentation generation workflow that ensures the codebase maintains compatibility across different target platforms in the no_std embedded environment.
For information about implementing new devices within this build system, see Implementing New Devices. For details about the core architecture that this build system validates, see Core Architecture.
CI Pipeline Overview
The repository uses GitHub Actions to implement a comprehensive CI pipeline that validates code across multiple architectures and maintains documentation quality. The pipeline consists of two primary jobs: ci for code validation and doc for documentation generation.
CI Job Architecture
flowchart TD
subgraph subGraph2["Build Steps"]
CHECKOUT["actions/checkout@v4"]
TOOLCHAIN["dtolnay/rust-toolchain@nightly"]
VERSION["rustc --version --verbose"]
FMT["cargo fmt --all -- --check"]
CLIPPY["cargo clippy --target TARGET --all-features"]
BUILD["cargo build --target TARGET --all-features"]
TEST["cargo test --target x86_64-unknown-linux-gnu"]
end
subgraph subGraph1["CI Job Matrix"]
RUST["rust-toolchain: nightly"]
T1["x86_64-unknown-linux-gnu"]
T2["x86_64-unknown-none"]
T3["riscv64gc-unknown-none-elf"]
T4["aarch64-unknown-none-softfloat"]
end
subgraph subGraph0["GitHub Actions Triggers"]
PUSH["push events"]
PR["pull_request events"]
end
BUILD --> TEST
CHECKOUT --> TOOLCHAIN
CLIPPY --> BUILD
FMT --> CLIPPY
PR --> RUST
PUSH --> RUST
RUST --> T1
RUST --> T2
RUST --> T3
RUST --> T4
T1 --> CHECKOUT
T2 --> CHECKOUT
T3 --> CHECKOUT
T4 --> CHECKOUT
TOOLCHAIN --> VERSION
VERSION --> FMT
Sources: .github/workflows/ci.yml(L1 - L31)
Documentation Job Pipeline
flowchart TD
subgraph subGraph2["Deployment Conditions"]
MAIN_BRANCH["github.ref == default-branch"]
GH_PAGES["branch: gh-pages"]
DOC_FOLDER["folder: target/doc"]
end
subgraph subGraph1["Environment Configuration"]
RUSTDOCFLAGS["-D rustdoc::broken_intra_doc_links -D missing-docs"]
DEFAULT_BRANCH["refs/heads/default_branch"]
PERMISSIONS["contents: write"]
end
subgraph subGraph0["Documentation Generation"]
DOC_CHECKOUT["actions/checkout@v4"]
DOC_TOOLCHAIN["dtolnay/rust-toolchain@nightly"]
DOC_BUILD["cargo doc --no-deps --all-features"]
INDEX_GEN["Generate target/doc/index.html redirect"]
PAGES_DEPLOY["JamesIves/github-pages-deploy-action@v4"]
end
DEFAULT_BRANCH --> MAIN_BRANCH
DOC_BUILD --> INDEX_GEN
DOC_CHECKOUT --> DOC_TOOLCHAIN
DOC_TOOLCHAIN --> DOC_BUILD
INDEX_GEN --> MAIN_BRANCH
MAIN_BRANCH --> PAGES_DEPLOY
PAGES_DEPLOY --> DOC_FOLDER
PAGES_DEPLOY --> GH_PAGES
PERMISSIONS --> PAGES_DEPLOY
RUSTDOCFLAGS --> DOC_BUILD
Sources: .github/workflows/ci.yml(L32 - L56)
Multi-Architecture Build Strategy
The CI pipeline validates compatibility across four distinct target architectures, each serving different use cases in the hypervisor ecosystem:
| Target | Purpose | Test Coverage |
|---|---|---|
| x86_64-unknown-linux-gnu | Standard Linux development and unit testing | Full (includingcargo test) |
| x86_64-unknown-none | Bare metal x86_64 hypervisor environments | Build and lint only |
| riscv64gc-unknown-none-elf | RISC-V hypervisor platforms | Build and lint only |
| aarch64-unknown-none-softfloat | ARM64 embedded hypervisor systems | Build and lint only |
Build Matrix Configuration
The build matrix uses a fail-fast: false strategy to ensure all target architectures are tested independently, preventing early termination when one target fails.
flowchart TD
subgraph subGraph1["Build Validation Steps"]
VERSION_CHECK["rustc --version --verbose"]
FORMAT_CHECK["cargo fmt --all -- --check"]
LINT_CHECK["cargo clippy --target TARGET --all-features"]
BUILD_CHECK["cargo build --target TARGET --all-features"]
UNIT_TEST["cargo test (x86_64-linux only)"]
end
subgraph subGraph0["Rust Toolchain Setup"]
NIGHTLY["nightly toolchain"]
COMPONENTS["rust-src, clippy, rustfmt"]
TARGETS["matrix.targets"]
end
BUILD_CHECK --> UNIT_TEST
COMPONENTS --> FORMAT_CHECK
COMPONENTS --> LINT_CHECK
FORMAT_CHECK --> LINT_CHECK
LINT_CHECK --> BUILD_CHECK
NIGHTLY --> VERSION_CHECK
TARGETS --> BUILD_CHECK
TARGETS --> LINT_CHECK
VERSION_CHECK --> FORMAT_CHECK
Sources: .github/workflows/ci.yml(L8 - L19) .github/workflows/ci.yml(L25 - L30)
Code Quality Enforcement
The CI pipeline enforces multiple layers of code quality validation before any changes can be merged.
Formatting and Linting
| Check Type | Command | Purpose |
|---|---|---|
| Code Formatting | cargo fmt --all -- --check | Ensures consistent code style across the codebase |
| Clippy Linting | cargo clippy --target $TARGET --all-features -- -A clippy::new_without_default | Catches common mistakes and enforces Rust best practices |
| Documentation | RUSTDOCFLAGS="-D rustdoc::broken_intra_doc_links -D missing-docs" | Enforces complete documentation with valid links |
The Clippy configuration specifically allows the clippy::new_without_default lint, indicating that the codebase may contain new() methods without corresponding Default implementations, which is common in no_std environments.
Sources: .github/workflows/ci.yml(L22 - L25) .github/workflows/ci.yml(L40)
Testing Strategy
The testing approach is pragmatic, recognizing the constraints of no_std embedded development:
Unit Testing Limitations
flowchart TD
subgraph subGraph2["Quality Assurance"]
CLIPPY_ALL["Clippy runs on all targets"]
FORMAT_ALL["Format check runs on all targets"]
COMPILE_ALL["Compilation check on all targets"]
end
subgraph subGraph1["Test Execution"]
LINUX_ONLY["Unit tests run only on x86_64-unknown-linux-gnu"]
TEST_CMD["cargo test --target x86_64-unknown-linux-gnu -- --nocapture"]
BUILD_VALIDATION["Build validation on all targets"]
end
subgraph subGraph0["Testing Constraints"]
NOSTD["no_std environment"]
EMBEDDED["Embedded targets"]
LIMITED["Limited std library access"]
end
BUILD_VALIDATION --> COMPILE_ALL
CLIPPY_ALL --> FORMAT_ALL
COMPILE_ALL --> CLIPPY_ALL
EMBEDDED --> BUILD_VALIDATION
LIMITED --> LINUX_ONLY
LINUX_ONLY --> TEST_CMD
NOSTD --> LINUX_ONLY
Unit tests execute only on x86_64-unknown-linux-gnu because the other targets are bare metal environments that lack the standard library infrastructure required for Rust's test framework. However, the build validation ensures that the code compiles correctly for all target architectures.
Sources: .github/workflows/ci.yml(L28 - L30)
Documentation Generation and Deployment
The documentation system automatically generates and deploys API documentation using a dedicated job that runs in parallel with the main CI validation.
Documentation Build Process
The documentation generation uses strict flags to ensure high-quality documentation:
flowchart TD
subgraph Deployment["Deployment"]
BRANCH_CHECK["github.ref == default-branch"]
DEPLOY_ACTION["JamesIves/github-pages-deploy-action@v4"]
SINGLE_COMMIT["single-commit: true"]
GH_PAGES_BRANCH["branch: gh-pages"]
DOC_FOLDER["folder: target/doc"]
end
subgraph subGraph1["Build Process"]
DOC_CMD["cargo doc --no-deps --all-features"]
INDEX_CREATE["printf redirect to generated docs"]
TREE_PARSE["cargo tree | head -1 | cut -d' ' -f1"]
end
subgraph subGraph0["Documentation Flags"]
BROKEN_LINKS["-D rustdoc::broken_intra_doc_links"]
MISSING_DOCS["-D missing-docs"]
RUSTDOCFLAGS["RUSTDOCFLAGS environment"]
end
BRANCH_CHECK --> DEPLOY_ACTION
BROKEN_LINKS --> RUSTDOCFLAGS
DEPLOY_ACTION --> DOC_FOLDER
DEPLOY_ACTION --> GH_PAGES_BRANCH
DEPLOY_ACTION --> SINGLE_COMMIT
DOC_CMD --> TREE_PARSE
INDEX_CREATE --> BRANCH_CHECK
MISSING_DOCS --> RUSTDOCFLAGS
RUSTDOCFLAGS --> DOC_CMD
TREE_PARSE --> INDEX_CREATE
The --no-deps flag ensures that only the crate's own documentation is generated, not its dependencies. The automatic index.html generation creates a redirect to the main crate documentation.
Sources: .github/workflows/ci.yml(L40) .github/workflows/ci.yml(L44 - L48) .github/workflows/ci.yml(L49 - L55)
Development Workflow Integration
The CI system is designed to support the typical development workflow for embedded hypervisor development:
Branch Protection and Quality Gates
| Stage | Validation | Blocking |
|---|---|---|
| Pull Request | All CI checks must pass | Yes |
| Format Check | cargo fmtvalidation | Yes |
| Lint Check | Clippy on all targets | Yes |
| Build Check | Compilation on all targets | Yes |
| Unit Tests | Tests on Linux target only | Yes |
| Documentation | Doc generation and link validation | Yes for main branch |
Error Handling Strategy
The documentation job uses conditional error handling with continue-on-error set based on branch and event type, allowing documentation builds to fail on non-main branches and non-pull-request events without blocking the overall workflow.
flowchart TD
subgraph subGraph1["Build Outcomes"]
MAIN_BRANCH_STRICT["Main branch: strict documentation"]
PR_STRICT["Pull requests: strict documentation"]
OTHER_LENIENT["Other contexts: lenient documentation"]
end
subgraph subGraph0["Error Handling Logic"]
BRANCH_CHECK["github.ref != default-branch"]
EVENT_CHECK["github.event_name != pull_request"]
CONTINUE_ERROR["continue-on-error: true"]
STRICT_MODE["continue-on-error: false"]
end
BRANCH_CHECK --> CONTINUE_ERROR
CONTINUE_ERROR --> OTHER_LENIENT
EVENT_CHECK --> CONTINUE_ERROR
STRICT_MODE --> MAIN_BRANCH_STRICT
STRICT_MODE --> PR_STRICT
This approach ensures that documentation quality is enforced where it matters most (main branch and pull requests) while allowing experimental branches to have temporary documentation issues.
Sources: .github/workflows/ci.yml(L45) .github/workflows/ci.yml(L39)
Implementing New Devices
Relevant source files
This page provides a comprehensive guide for implementing new emulated devices within the ArceOS hypervisor ecosystem using the BaseDeviceOps trait foundation. It covers the step-by-step process of creating device implementations, configuring address ranges, implementing read/write handlers, and integrating with the device type system.
For information about the core architecture and trait definitions, see Core Architecture. For details about the device type enumeration system, see Device Type System.
Implementation Overview
Creating a new emulated device requires implementing the BaseDeviceOps trait and integrating with the EmuDeviceType system. The implementation process follows a standardized pattern that ensures compatibility with the ArceOS hypervisor's device emulation framework.
Device Implementation Architecture
flowchart TD
subgraph subGraph2["Core Types"]
T1["EmuDeviceType"]
T2["AddrRange"]
T3["AxResult"]
T4["GuestPhysAddr"]
end
subgraph subGraph1["BaseDeviceOps Methods"]
M1["emu_type()"]
M2["address_range()"]
M3["handle_read()"]
M4["handle_write()"]
end
subgraph subGraph0["Implementation Steps"]
S1["Unsupported markdown: list"]
S2["Unsupported markdown: list"]
S3["Unsupported markdown: list"]
S4["Unsupported markdown: list"]
S5["Unsupported markdown: list"]
S6["Unsupported markdown: list"]
end
M1 --> T1
M2 --> T2
M3 --> T3
M4 --> T4
S1 --> S2
S2 --> M1
S2 --> M2
S2 --> M3
S2 --> M4
S2 --> S3
S3 --> S4
S4 --> S5
S5 --> S6
Sources: axdevice_base/src/lib.rs(L20 - L30) axdevice_base/src/emu_type.rs(L3 - L28)
Device Structure Definition
The first step in implementing a new device is defining the device structure that will hold the device's state and configuration. The structure should contain all necessary data for device emulation, including memory mappings, configuration registers, and operational state.
Device Implementation Pattern
classDiagram
class BaseDeviceOps {
<<trait>>
+emu_type() EmuDeviceType
+address_range() AddrRange~GuestPhysAddr~
+handle_read(addr, width) AxResult~usize~
+handle_write(addr, width, val)
}
class YourNewDevice {
-base_address: GuestPhysAddr
-size: usize
-registers: DeviceRegisters
-state: DeviceState
+new(base_addr, size) Self
+reset()
+configure()
}
class DeviceRegisters {
-control_reg: u32
-status_reg: u32
-data_reg: u32
-interrupt_reg: u32
}
class DeviceState {
-enabled: bool
-interrupt_pending: bool
-operation_mode: OperationMode
}
class EmuDeviceType {
<<enum>>
EmuDeviceTConsole
EmuDeviceTGicdV2
EmuDeviceTVirtioBlk
"... other types"
YourNewDeviceType
}
YourNewDevice ..|> BaseDeviceOps : implements
YourNewDevice --> DeviceRegisters : contains
YourNewDevice --> DeviceState : contains
YourNewDevice --> EmuDeviceType : returns via emu_type()
Sources: axdevice_base/src/lib.rs(L21 - L30) axdevice_base/src/emu_type.rs(L5 - L28)
BaseDeviceOps Implementation
The core of device implementation involves implementing the four required methods of the BaseDeviceOps trait. Each method serves a specific purpose in the device emulation lifecycle.
Required Method Implementation
| Method | Purpose | Return Type | Parameters |
|---|---|---|---|
| emu_type() | Device type identification | EmuDeviceType | &self |
| address_range() | Memory mapping definition | AddrRange | &self |
| handle_read() | Read operation processing | AxResult | &self, addr, width |
| handle_write() | Write operation processing | () | &self, addr, width, val |
The emu_type() method returns the device's type identifier from the EmuDeviceType enumeration. This method enables the hypervisor to identify and categorize the device for management purposes.
The address_range() method defines the guest physical address range that the device occupies in the virtual machine's memory space. This range determines which memory accesses will be routed to the device's read and write handlers.
Sources: axdevice_base/src/lib.rs(L22 - L29)
Address Range Configuration
Device address range configuration requires careful consideration of the guest physical address space layout and potential conflicts with other devices or memory regions. The address range must be properly aligned and sized according to the device's requirements.
Address Range Management Flow
sequenceDiagram
participant ArceOSHypervisor as "ArceOS Hypervisor"
participant AddressSpaceManager as "Address Space Manager"
participant YourNewDevice as "YourNewDevice"
participant AddrRangeGuestPhysAddr as "AddrRange<GuestPhysAddr>"
Note over ArceOSHypervisor,AddrRangeGuestPhysAddr: Device Registration Flow
ArceOSHypervisor ->> YourNewDevice: address_range()
YourNewDevice ->> AddrRangeGuestPhysAddr: create range
Note over AddrRangeGuestPhysAddr: base_address: GuestPhysAddr<br>size: usize
AddrRangeGuestPhysAddr ->> YourNewDevice: AddrRange instance
YourNewDevice ->> ArceOSHypervisor: return address range
ArceOSHypervisor ->> AddressSpaceManager: register_device_range()
AddressSpaceManager ->> AddressSpaceManager: validate no conflicts
AddressSpaceManager ->> ArceOSHypervisor: registration result
Note over ArceOSHypervisor,AddrRangeGuestPhysAddr: Memory Access Flow
ArceOSHypervisor ->> AddressSpaceManager: guest_memory_access(gpa)
AddressSpaceManager ->> AddressSpaceManager: check address ranges
alt Address in device range
AddressSpaceManager ->> ArceOSHypervisor: route to device
ArceOSHypervisor ->> YourNewDevice: handle_read/write()
else Address not in range
AddressSpaceManager ->> ArceOSHypervisor: handle as memory
end
Sources: axdevice_base/src/lib.rs(L24 - L25)
The address range should be configured during device initialization and remain constant throughout the device's lifetime. Dynamic address range changes are not supported by the current architecture.
Read Handler Implementation
The handle_read() method processes guest read operations targeting the device's address range. The implementation must decode the address offset, validate the access width, and return appropriate data based on the device's current state.
Read Handler Patterns
flowchart TD
subgraph subGraph2["Error Handling"]
E1["Invalid Address"]
E2["Unsupported Width"]
E3["Device Not Ready"]
E4["Permission Denied"]
end
subgraph subGraph1["Common Read Patterns"]
P1["Register Bank Access"]
P2["FIFO/Buffer Reading"]
P3["Status Register Polling"]
P4["Interrupt Status Check"]
end
subgraph subGraph0["handle_read() Implementation Flow"]
A["Guest Read Access"]
B["Validate Address Range"]
C["Calculate Register Offset"]
D["Validate Access Width"]
E["Read Register Value"]
F["Apply Device Logic"]
G["Return AxResult"]
end
A --> B
B --> C
C --> D
D --> E
D --> E1
D --> E2
E --> E3
E --> F
F --> E4
F --> G
F --> P1
F --> P2
F --> P3
F --> P4
Sources: axdevice_base/src/lib.rs(L27)
The read handler should support standard access widths (1, 2, 4, and 8 bytes) and return data in the appropriate format. Register values should be calculated based on the device's current state and any side effects of the read operation should be applied.
Write Handler Implementation
The handle_write() method processes guest write operations to the device's address range. Unlike read operations, write handlers do not return data but may trigger device state changes, interrupt generation, or other side effects.
Write Handler Architecture
flowchart TD
subgraph subGraph2["Side Effects"]
S1["Interrupt Generation"]
S2["DMA Transfer Start"]
S3["Device Reset"]
S4["Mode Change"]
end
subgraph subGraph1["Write Operations"]
O1["Control Register Write"]
O2["Data Buffer Write"]
O3["Configuration Update"]
O4["Command Execution"]
end
subgraph subGraph0["handle_write() Processing"]
W1["Guest Write Access"]
W2["Validate Address Range"]
W3["Calculate Register Offset"]
W4["Validate Access Width"]
W5["Extract Write Value"]
W6["Apply Register Logic"]
W7["Update Device State"]
W8["Trigger Side Effects"]
end
W1 --> W2
W2 --> W3
W3 --> W4
W4 --> W5
W5 --> W6
W6 --> O1
W6 --> O2
W6 --> O3
W6 --> O4
W6 --> W7
W7 --> W8
W8 --> S1
W8 --> S2
W8 --> S3
W8 --> S4
Sources: axdevice_base/src/lib.rs(L29)
Write handlers should validate input data, apply appropriate bit masks for register fields, and handle write-only or read-only register access restrictions. The handler should also manage any required atomicity or ordering constraints.
Device Type Selection
Choosing the appropriate EmuDeviceType is crucial for proper device categorization and management. The device type affects removability, hypervisor behavior, and integration with other system components.
Device Type Categories
| Category | Device Types | Removable | Use Cases |
|---|---|---|---|
| Console/IO | EmuDeviceTConsole,EmuDeviceTVirtioConsole | Mixed | Terminal, logging |
| Interrupt Controllers | EmuDeviceTGicdV2,EmuDeviceTGPPT,EmuDeviceTICCSRE,EmuDeviceTSGIR,EmuDeviceTGICR | Yes | ARM interrupt management |
| Virtio Devices | EmuDeviceTVirtioBlk,EmuDeviceTVirtioNet | Yes | Paravirtualized I/O |
| System Infrastructure | EmuDeviceTIOMMU,EmuDeviceTMeta | No | Core system functions |
For new device types not covered by existing categories, the EmuDeviceType enumeration must be extended with appropriate variants. The removable() method implementation should be updated to reflect the new device's characteristics.
Sources: axdevice_base/src/emu_type.rs(L5 - L28) axdevice_base/src/emu_type.rs(L52 - L64)
Implementation Best Practices
State Management
Device state should be properly encapsulated and protected against concurrent access. Use appropriate synchronization primitives when device state may be accessed from multiple contexts.
Error Handling
All operations that can fail should return appropriate AxResult types. Use specific error codes from the axerrno crate to indicate different failure conditions.
Memory Access Validation
Always validate guest physical addresses against the device's configured address range and ensure access widths are supported by the target registers.
Performance Considerations
Minimize computational overhead in read and write handlers, as these methods are called frequently during device emulation. Cache frequently accessed values and avoid unnecessary calculations.
Testing Strategy
Implement comprehensive test coverage for all register access patterns, error conditions, and state transitions. Verify compatibility with the target guest operating systems and device drivers.
Sources: axdevice_base/src/lib.rs(L1 - L31) axdevice_base/src/emu_type.rs(L1 - L84)
Overview
Relevant source files
This document provides a comprehensive overview of the arm_vcpu hypervisor system, an AArch64 virtualization implementation that provides virtual CPU management and hardware abstraction for hypervisor environments. The system implements low-level virtualization primitives including CPU context switching, exception handling, and guest state management for AArch64 platforms.
The arm_vcpu crate serves as a foundational component for hypervisor implementations, providing the core VCPU abstraction and associated hardware interfaces. For detailed information about specific subsystems, see Virtual CPU Management, Exception Handling System, and System Integration.
System Purpose and Scope
The arm_vcpu system implements virtualization support for AArch64 architecture, providing:
- Virtual CPU Management: Core VCPU lifecycle, state management, and execution control
- Hardware Abstraction: Platform-independent interfaces for virtualization hardware features
- Exception Handling: Comprehensive trap and interrupt handling for guest VMs
- Context Switching: Efficient state preservation and restoration between host and guest execution
- Security Integration: Secure Monitor Call (SMC) interface for trusted firmware interaction
Sources: README.md(L1 - L5) src/lib.rs(L1 - L33)
Primary System Components
The following diagram illustrates the main architectural components and their relationships within the arm_vcpu system:
Core Component Architecture
flowchart TD
subgraph subGraph2["External Dependencies"]
AxVCpu["axvcpuCore VCPU Traits"]
AxAddrSpace["axaddrspaceAddress Space Management"]
PerCpuCrate["percpuPer-CPU Storage"]
Aarch64Cpu["aarch64-cpuRegister Definitions"]
end
subgraph subGraph1["Internal Modules"]
ContextFrame["context_frameState Structures"]
Exception["exceptionHandler Logic"]
ExceptionUtils["exception_utilsRegister Parsing"]
PCpuMod["pcpuPer-CPU Implementation"]
SMC["smcSecure Monitor Interface"]
VCpuMod["vcpuVCPU Implementation"]
end
subgraph subGraph0["Primary Interfaces"]
VCpu["Aarch64VCpuMain VCPU Implementation"]
PerCpu["Aarch64PerCpuPer-CPU State Management"]
TrapFrame["TrapFrame(Aarch64ContextFrame)"]
Config["Aarch64VCpuCreateConfigVCPU Configuration"]
end
Exception --> Aarch64Cpu
Exception --> ExceptionUtils
PerCpu --> PCpuMod
PerCpu --> PerCpuCrate
TrapFrame --> ContextFrame
VCpu --> AxAddrSpace
VCpu --> AxVCpu
VCpu --> Config
VCpu --> PerCpu
VCpu --> TrapFrame
VCpu --> VCpuMod
VCpuMod --> Exception
VCpuMod --> SMC
Sources: src/lib.rs(L9 - L21) Cargo.toml(L6 - L19)
Code Entity Mapping
This diagram maps the natural language system concepts to specific code entities and file structures:
Implementation Structure and Code Entities
flowchart TD
subgraph subGraph2["Core Data Structures"]
Aarch64VCpuStruct["struct Aarch64VCpu"]
Aarch64PerCpuStruct["struct Aarch64PerCpu"]
ContextFrameStruct["struct Aarch64ContextFrame"]
ConfigStruct["struct Aarch64VCpuCreateConfig"]
end
subgraph subGraph1["Source File Organization"]
VCpuRs["vcpu.rsVirtual CPU Logic"]
PCpuRs["pcpu.rsPer-CPU Management"]
ContextRs["context_frame.rsState Structures"]
ExceptionRs["exception.rsException Handlers"]
ExceptionS["exception.SAssembly Vectors"]
UtilsRs["exception_utils.rsRegister Utilities"]
SmcRs["smc.rsSecure Monitor Calls"]
end
subgraph subGraph0["Public API Surface"]
LibRs["lib.rsMain Module"]
PubVCpu["pub use Aarch64VCpu"]
PubPerCpu["pub use Aarch64PerCpu"]
PubConfig["pub use Aarch64VCpuCreateConfig"]
PubTrapFrame["pub type TrapFrame"]
HwSupport["has_hardware_support()"]
end
ContextRs --> ContextFrameStruct
ExceptionRs --> ExceptionS
ExceptionRs --> UtilsRs
LibRs --> HwSupport
LibRs --> PubConfig
LibRs --> PubPerCpu
LibRs --> PubTrapFrame
LibRs --> PubVCpu
PCpuRs --> Aarch64PerCpuStruct
PubPerCpu --> PCpuRs
PubTrapFrame --> ContextRs
PubVCpu --> VCpuRs
VCpuRs --> Aarch64VCpuStruct
VCpuRs --> ConfigStruct
VCpuRs --> ExceptionRs
VCpuRs --> SmcRs
Sources: src/lib.rs(L17 - L21) src/lib.rs(L9 - L15)
Virtualization Hardware Integration
The system provides hardware abstraction for AArch64 virtualization extensions through a layered approach:
| Layer | Component | Responsibility |
|---|---|---|
| Hardware | AArch64 CPU | Virtualization extensions (EL2, VHE) |
| Low-Level | exception.S | Assembly exception vectors and context switch |
| Abstraction | AxVCpuHaltrait | Hardware abstraction interface |
| Implementation | Aarch64VCpu | Concrete VCPU implementation |
| Integration | axvcpucrate | Generic VCPU traits and interfaces |
Hardware Feature Detection and Support
flowchart TD
subgraph subGraph2["Per-CPU Control"]
HwSupport["has_hardware_support()Feature Detection"]
IdRegs["ID_AA64MMFR1_EL1Virtualization Host Extensions"]
DefaultTrue["Currently returns truePlaceholder Implementation"]
PerCpuEnable["Aarch64PerCpu::hardware_enable()"]
PerCpuDisable["Aarch64PerCpu::hardware_disable()"]
VirtualizationState["Per-CPU Virtualization State"]
end
subgraph subGraph1["Virtualization Extensions"]
EL2["Exception Level 2Hypervisor Mode"]
VHE["Virtualization Host ExtensionsEnhanced Host Support"]
HCR["HCR_EL2 RegisterHypervisor Configuration"]
VBAR["VBAR_EL2 RegisterVector Base Address"]
end
subgraph subGraph0["Platform Detection"]
HwSupport["has_hardware_support()Feature Detection"]
IdRegs["ID_AA64MMFR1_EL1Virtualization Host Extensions"]
DefaultTrue["Currently returns truePlaceholder Implementation"]
end
HwSupport --> DefaultTrue
HwSupport --> IdRegs
PerCpuEnable --> EL2
PerCpuEnable --> HCR
PerCpuEnable --> VBAR
VirtualizationState --> PerCpuDisable
VirtualizationState --> PerCpuEnable
Sources: src/lib.rs(L23 - L32) Cargo.toml(L15)
External Ecosystem Integration
The arm_vcpu crate integrates with the broader ArceOS hypervisor ecosystem through well-defined dependency relationships:
| Dependency | Purpose | Integration Point |
|---|---|---|
| axvcpu | Core VCPU traits | AxVCpuHaltrait implementation |
| axaddrspace | Address space management | Memory virtualization support |
| percpu | Per-CPU data structures | Hardware state management |
| aarch64-cpu | Register definitions | Low-level hardware access |
| aarch64_sysreg | System register access | Control register manipulation |
The system operates at Exception Level 2 (EL2) and provides virtualization services for guest operating systems running at Exception Level 1 (EL1) and applications at Exception Level 0 (EL0).
Sources: Cargo.toml(L14 - L19) README.md(L5)
System Architecture
Relevant source files
This document provides a detailed explanation of the overall system architecture for the arm_vcpu hypervisor implementation. It covers the core components, their relationships, and the data flow patterns that enable AArch64 virtualization. For specific implementation details of individual components, see Virtual CPU Management, Exception Handling System, and Context Switching and State Management.
Architecture Overview
The arm_vcpu system implements a Type-1 hypervisor architecture for AArch64 platforms, providing virtualization capabilities through the AArch64 virtualization extensions. The system operates at Exception Level 2 (EL2) and manages guest virtual machines running at EL1/EL0.
Core Component Relationships
Sources: src/vcpu.rs(L39 - L51) src/pcpu.rs(L10 - L16) src/lib.rs(L17 - L21)
System Initialization and Lifecycle
sequenceDiagram
participant HostSystem as "Host System"
participant Aarch64PerCpu as "Aarch64PerCpu"
participant Aarch64VCpu as "Aarch64VCpu"
participant GuestVM as "Guest VM"
HostSystem ->> Aarch64PerCpu: new(cpu_id)
Aarch64PerCpu ->> Aarch64PerCpu: Register IRQ_HANDLER
HostSystem ->> Aarch64PerCpu: hardware_enable()
Aarch64PerCpu ->> Aarch64PerCpu: Set VBAR_EL2 to exception_vector_base_vcpu
Aarch64PerCpu ->> Aarch64PerCpu: Configure HCR_EL2 for virtualization
HostSystem ->> Aarch64VCpu: new(Aarch64VCpuCreateConfig)
Aarch64VCpu ->> Aarch64VCpu: Initialize TrapFrame and GuestSystemRegisters
HostSystem ->> Aarch64VCpu: setup()
Aarch64VCpu ->> Aarch64VCpu: init_hv() - Configure initial state
HostSystem ->> Aarch64VCpu: set_entry(guest_entry_point)
HostSystem ->> Aarch64VCpu: set_ept_root(page_table_root)
loop VM Execution Cycle
HostSystem ->> Aarch64VCpu: run()
Aarch64VCpu ->> Aarch64VCpu: save_host_sp_el0()
Aarch64VCpu ->> Aarch64VCpu: restore_vm_system_regs()
Aarch64VCpu ->> GuestVM: Context switch to guest
GuestVM -->> Aarch64VCpu: VM-Exit (trap/exception)
Aarch64VCpu ->> Aarch64VCpu: vmexit_handler()
Aarch64VCpu -->> HostSystem: Return AxVCpuExitReason
end
Sources: src/vcpu.rs(L69 - L85) src/vcpu.rs(L99 - L111) src/pcpu.rs(L49 - L67)
Core Components
Virtual CPU (Aarch64VCpu)
The Aarch64VCpu<H: AxVCpuHal> structure serves as the primary abstraction for a virtual CPU. It maintains both guest context and runtime state required for virtualization.
| Field | Type | Purpose |
|---|---|---|
| ctx | TrapFrame | Guest general-purpose registers and execution state |
| host_stack_top | u64 | Host stack pointer for context switching |
| guest_system_regs | GuestSystemRegisters | Guest system control and configuration registers |
| mpidr | u64 | Multiprocessor Affinity Register value for guest |
The VCPU implements the AxArchVCpu trait, providing standardized interfaces for:
- Creation and configuration via
Aarch64VCpuCreateConfig - Guest execution through the
run()method - Entry point and page table configuration
- Register manipulation interfaces
Sources: src/vcpu.rs(L39 - L51) src/vcpu.rs(L64 - L124)
Per-CPU Management (Aarch64PerCpu)
The Aarch64PerCpu<H: AxVCpuHal> structure manages hardware virtualization features on a per-CPU basis:
flowchart TD PCPU["Aarch64PerCpu"] HCR["HCR_EL2 RegisterHypervisor Configuration"] VBAR["VBAR_EL2 RegisterException Vector Base"] IRQ["IRQ_HANDLERPer-CPU IRQ Dispatch"] ORIG["ORI_EXCEPTION_VECTOR_BASEOriginal Vector Base"] VM["VM Bit - Virtualization"] RW["RW Bit - 64-bit Guest"] IMO["IMO/FMO - Virtual Interrupts"] TSC["TSC - SMC Instructions"] HCR --> IMO HCR --> RW HCR --> TSC HCR --> VM PCPU --> HCR PCPU --> IRQ PCPU --> ORIG PCPU --> VBAR
Sources: src/pcpu.rs(L10 - L16) src/pcpu.rs(L49 - L67) src/pcpu.rs(L18 - L26)
Hardware Abstraction Layer
The system uses the AxVCpuHal trait to abstract platform-specific functionality:
Sources: src/vcpu.rs(L278) src/pcpu.rs(L35 - L37)
Context Switching Architecture
The system implements a sophisticated context switching mechanism that preserves both general-purpose and system registers across VM entries and exits:
flowchart TD
subgraph subGraph2["Guest Context"]
TRAP["TrapFrame(GPRs, SP_EL0, ELR, SPSR)"]
GSYS["GuestSystemRegisters(System Control Registers)"]
end
subgraph subGraph1["Context Switch Operations"]
SAVE_HOST["save_host_sp_el0()"]
RESTORE_VM["restore_vm_system_regs()"]
RUN_GUEST["run_guest()(Naked Function)"]
VMEXIT["vmexit_handler()"]
end
subgraph subGraph0["Host Context"]
HSP["Host SP_EL0(HOST_SP_EL0 per-CPU)"]
HSTACK["Host Stack(save_regs_to_stack!)"]
HSYS["Host System Registers(Original Hardware State)"]
end
GSYS --> VMEXIT
HSP --> SAVE_HOST
RESTORE_VM --> GSYS
RESTORE_VM --> RUN_GUEST
RUN_GUEST --> TRAP
SAVE_HOST --> RESTORE_VM
TRAP --> VMEXIT
VMEXIT --> HSP
VMEXIT --> HSTACK
Sources: src/vcpu.rs(L182 - L214) src/vcpu.rs(L226 - L244) src/vcpu.rs(L255 - L282)
Exception and Interrupt Handling
The system provides a multi-layered exception handling architecture that processes VM-exits and routes them appropriately:
flowchart TD
subgraph subGraph3["Exit Reasons"]
MMIO["AxVCpuExitReason::MmioRead/Write"]
EXT_IRQ["AxVCpuExitReason::ExternalInterrupt"]
SYSREG["AxVCpuExitReason::SystemRegister*"]
PSCI["AxVCpuExitReason::CpuUp/Down"]
end
subgraph subGraph2["High-Level Dispatch"]
SYNC_HANDLER["handle_exception_sync()"]
IRQ_DISPATCH["IRQ_HANDLER per-CPU"]
PANIC["invalid_exception_el2"]
end
subgraph subGraph1["Low-Level Processing"]
VECTORS["exception_vector_base_vcpu(Assembly Vector Table)"]
TRAMPOLINE["vmexit_trampoline(Context Save)"]
end
subgraph subGraph0["Exception Sources"]
SYNC["Synchronous Exceptions(Data Aborts, HVC, SMC)"]
IRQ["IRQ Interrupts"]
INVALID["Invalid Exceptions"]
end
INVALID --> VECTORS
IRQ --> VECTORS
IRQ_DISPATCH --> EXT_IRQ
SYNC --> VECTORS
SYNC_HANDLER --> MMIO
SYNC_HANDLER --> PSCI
SYNC_HANDLER --> SYSREG
TRAMPOLINE --> IRQ_DISPATCH
TRAMPOLINE --> PANIC
TRAMPOLINE --> SYNC_HANDLER
VECTORS --> TRAMPOLINE
Sources: src/vcpu.rs(L275 - L281) src/pcpu.rs(L18 - L26) src/pcpu.rs(L55 - L57)
This architecture enables efficient virtualization by maintaining clear separation between host and guest contexts while providing comprehensive exception handling capabilities for all VM-exit scenarios.
Dependencies and Build System
Relevant source files
This document covers the external dependencies, build configuration, and continuous integration setup for the arm_vcpu crate. It details the Rust crate dependencies required for AArch64 virtualization, the build system configuration, and the automated testing pipeline.
For information about how these dependencies are used within the VCPU implementation, see Virtual CPU Management. For details about the hardware abstraction interfaces, see Hardware Abstraction and Platform Support.
Purpose and Scope
The arm_vcpu crate is a no-std Rust library that implements AArch64 virtualization capabilities for hypervisors. This page documents the dependency management, build requirements, and continuous integration infrastructure that support the development and deployment of this hypervisor component.
External Dependencies
The crate relies on several categories of dependencies to provide AArch64 virtualization functionality:
Core Rust Dependencies
| Dependency | Version | Purpose |
|---|---|---|
| log | 0.4.21 | Logging infrastructure for debug and runtime information |
| spin | 0.9 | Spinlock primitives for no-std synchronization |
Sources: Cargo.toml(L7 - L8)
AArch64 Hardware Interface Dependencies
| Dependency | Version | Purpose |
|---|---|---|
| aarch64-cpu | 9.3 | AArch64 CPU register definitions and operations |
| tock-registers | 0.8 | Register field manipulation macros and utilities |
| aarch64_sysreg | 0.1.1 | AArch64 system register definitions and access |
| numeric-enum-macro | 0.2 | Macro support for numeric enum conversions |
Sources: Cargo.toml(L10 - L16)
System Infrastructure Dependencies
| Dependency | Version | Purpose |
|---|---|---|
| axerrno | 0.1.0 | Error code definitions for the ArceOS ecosystem |
| percpu | 0.2.0 | Per-CPU data storage with ARM EL2 feature support |
The percpu crate is configured with the arm-el2 feature to enable Exception Level 2 support required for hypervisor operation.
Sources: Cargo.toml(L14 - L15)
ArceOS Ecosystem Dependencies
The crate integrates with the broader ArceOS hypervisor ecosystem through Git-based dependencies:
Dependency Architecture
flowchart TD
subgraph subGraph3["System Support"]
PERCPU["percpu(Per-CPU Storage)"]
LOG["log(Logging)"]
SPIN["spin(Synchronization)"]
AXERRNO["axerrno(Error Codes)"]
end
subgraph subGraph2["Hardware Layer"]
AARCH64["aarch64-cpu(Register Definitions)"]
SYSREG["aarch64_sysreg(System Registers)"]
TOCK["tock-registers(Register Macros)"]
end
subgraph subGraph1["ArceOS Core Dependencies"]
AXVCPU["axvcpu(Core VCPU Traits)"]
AXADDR["axaddrspace(Address Space Management)"]
end
subgraph subGraph0["arm_vcpu Crate"]
MAIN["arm_vcpu"]
end
MAIN --> AARCH64
MAIN --> AXADDR
MAIN --> AXERRNO
MAIN --> AXVCPU
MAIN --> LOG
MAIN --> PERCPU
MAIN --> SPIN
MAIN --> SYSREG
MAIN --> TOCK
Git Dependencies
| Repository | Purpose |
|---|---|
| axvcpu | Defines core VCPU traits (AxVCpu,AxVCpuHal) implemented byAarch64VCpu |
| axaddrspace | Provides address space management interfaces for guest memory virtualization |
Both dependencies are sourced from the arceos-hypervisor GitHub organization, indicating a modular hypervisor architecture where arm_vcpu provides the AArch64-specific implementation.
Sources: Cargo.toml(L18 - L19)
Build Configuration
Package Metadata
The crate is configured as a library with the following specifications:
[package]
name = "arm_vcpu"
version = "0.1.0"
edition = "2024"
The use of Rust edition 2024 indicates adoption of the latest Rust language features and optimizations.
Sources: Cargo.toml(L1 - L4)
Target Architecture
The primary build target is aarch64-unknown-none-softfloat, which specifies:
- aarch64: 64-bit ARM architecture
- unknown: No specific vendor
- none: Bare metal (no operating system)
- softfloat: Software floating-point implementation
This target configuration is appropriate for hypervisor code running at Exception Level 2 (EL2) without OS dependencies.
Sources: .github/workflows/ci.yml(L12)
Continuous Integration System
CI Pipeline Architecture
flowchart TD
subgraph subGraph3["CI Jobs"]
DOC_DEPLOY["Deploy to GitHub Pages"]
subgraph subGraph1["Primary CI Job"]
CHECKOUT["Checkout Code"]
SETUP["Setup Rust Toolchain"]
VERSION["Check Rust Version"]
FORMAT["Format Check (rustfmt)"]
CLIPPY["Clippy Linting"]
BUILD["Build"]
TEST["Unit Tests"]
end
end
subgraph subGraph0["Build Matrix"]
TOOLCHAIN1["nightly-2024-12-25"]
TOOLCHAIN2["nightly"]
TARGET["aarch64-unknown-none-softfloat"]
end
subgraph subGraph2["Documentation Job"]
DOC_CHECKOUT["Checkout Code"]
DOC_SETUP["Setup Rust Toolchain"]
DOC_BUILD["Build Documentation"]
DOC_DEPLOY["Deploy to GitHub Pages"]
end
TRIGGER["Push/Pull Request"]
MATRIX["Matrix Strategy"]
BUILD --> TEST
CHECKOUT --> SETUP
CLIPPY --> BUILD
DOC_BUILD --> DOC_DEPLOY
DOC_CHECKOUT --> DOC_SETUP
DOC_SETUP --> DOC_BUILD
FORMAT --> CLIPPY
MATRIX --> TOOLCHAIN1
MATRIX --> TOOLCHAIN2
SETUP --> VERSION
TOOLCHAIN1 --> CHECKOUT
TOOLCHAIN2 --> CHECKOUT
TRIGGER --> MATRIX
VERSION --> FORMAT
CI Job Configuration
The continuous integration system uses GitHub Actions with two primary jobs:
Primary CI Job
- Rust Toolchains: Tests against both
nightly-2024-12-25(pinned) andnightly(latest) - Components: Includes
rust-src,clippy, andrustfmt - Quality Checks: Format verification, linting, and build verification
- Testing: Unit tests (currently limited to x86_64 targets)
Documentation Job
- Environment: Uses
RUSTDOCFLAGSto enforce documentation standards - Deployment: Automatically publishes documentation to GitHub Pages on main branch
- Documentation Quality: Requires all public APIs to be documented
Sources: .github/workflows/ci.yml(L6 - L59)
Build Quality Gates
The CI system enforces several quality standards:
| Check | Tool | Purpose |
|---|---|---|
| Code Formatting | rustfmt | Enforces consistent code style |
| Linting | clippy | Catches common mistakes and suggests improvements |
| Documentation | rustdoc | Ensures all public APIs are documented |
| Build Verification | cargo build | Confirms code compiles for target architecture |
The configuration includes continue-on-error flags for the latest nightly toolchain to accommodate potential breaking changes in unstable Rust features.
Sources: .github/workflows/ci.yml(L22 - L49)
Development Environment
Git Configuration
The repository excludes common build artifacts and development files:
- Build outputs:
target/,*.asm,*.img,*.bin,*.elf - Development artifacts:
qemu.log,actual.out,rusty-tags.vi - IDE settings:
.vscode/ - System files:
.DS_Store
Notably, Cargo.lock is ignored since arm_vcpu is distributed as a library crate.
Sources: .gitignore(L1 - L18)
Virtual CPU Management
Relevant source files
This document covers the core virtual CPU implementation in the arm_vcpu hypervisor system. It focuses on the Aarch64VCpu struct, its lifecycle management, configuration mechanisms, and the fundamental execution model for running guest virtual machines on AArch64 hardware.
For detailed information about VCPU lifecycle operations and VM exit handling, see VCPU Lifecycle and Operations. For per-CPU hardware state management and virtualization control, see Per-CPU State Management. For low-level context switching mechanics, see Context Switching and State Management.
Core VCPU Implementation
The Aarch64VCpu<H: AxVCpuHal> struct is the central component implementing virtual CPU functionality. It maintains both guest execution context and system register state, providing the foundation for running guest operating systems under hypervisor control.
VCPU Structure and Components
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
+set_entry(entry) AxResult
+set_ept_root(ept_root) AxResult
+run() AxResult~AxVCpuExitReason~
+bind() AxResult
+unbind() AxResult
+set_gpr(idx, val)
}
class TrapFrame {
+guest execution context
+GPRs, SP_EL0, ELR, SPSR
}
class GuestSystemRegisters {
+system control registers
+timer registers
+memory management registers
}
class VmCpuRegisters {
+TrapFrame trap_context_regs
+GuestSystemRegisters vm_system_regs
}
class Aarch64VCpuCreateConfig {
+u64 mpidr_el1
+usize dtb_addr
}
Aarch64VCpu *-- TrapFrame : "contains"
Aarch64VCpu *-- GuestSystemRegisters : "contains"
VmCpuRegisters *-- TrapFrame : "aggregates"
VmCpuRegisters *-- GuestSystemRegisters : "aggregates"
Aarch64VCpu ..> Aarch64VCpuCreateConfig : "uses for creation"
The VCPU structure maintains critical ordering constraints where ctx and host_stack_top must remain at the beginning of the struct to support assembly code expectations for context switching operations.
Sources: src/vcpu.rs(L40 - L51) src/vcpu.rs(L30 - L37) src/vcpu.rs(L54 - L62)
Hardware Abstraction Integration
The generic H: AxVCpuHal parameter allows the VCPU implementation to work with different hardware abstraction layers while maintaining platform independence.
Sources: src/vcpu.rs(L8) src/vcpu.rs(L64 - L65) src/vcpu.rs(L278)
VCPU Configuration and Initialization
Creation and Setup Process
The VCPU follows a two-phase initialization pattern: creation with hardware-specific configuration, followed by hypervisor setup.
| Phase | Function | Purpose | Configuration |
|---|---|---|---|
| Creation | new(config) | Basic structure allocation | Aarch64VCpuCreateConfig |
| Setup | setup(config) | Hypervisor initialization | ()(empty) |
| Entry Configuration | set_entry(entry) | Guest entry point | GuestPhysAddr |
| Memory Setup | set_ept_root(ept_root) | Extended page table root | HostPhysAddr |
Sources: src/vcpu.rs(L69 - L84) src/vcpu.rs(L87 - L96)
System Register Initialization
flowchart TD init_hv["init_hv()"] init_spsr["Configure SPSR_EL1EL1h mode, masks set"] init_vm_context["init_vm_context()"] timer_config["Configure TimerCNTHCTL_EL2, CNTVOFF_EL2"] sctlr_config["Configure SCTLR_EL10x30C50830"] vtcr_config["Configure VTCR_EL240-bit PA, 4KB granules"] hcr_config["Configure HCR_EL2VM enable, AArch64, SMC trap"] vmpidr_config["Configure VMPIDR_EL2Based on mpidr parameter"] ready["VCPU Ready for Execution"] hcr_config --> ready init_hv --> init_spsr init_hv --> init_vm_context init_vm_context --> hcr_config init_vm_context --> sctlr_config init_vm_context --> timer_config init_vm_context --> vmpidr_config init_vm_context --> vtcr_config sctlr_config --> ready timer_config --> ready vmpidr_config --> ready vtcr_config --> ready
The initialization process configures essential system registers to establish the proper hypervisor and guest execution environment, including memory management, exception handling, and CPU identification.
Sources: src/vcpu.rs(L128 - L166)
Execution Model and Context Management
Host-Guest Context Switching
sequenceDiagram
participant HostContext as "Host Context"
participant Aarch64VCpurun as "Aarch64VCpu::run()"
participant GuestExecution as "Guest Execution"
participant ExceptionHandler as "Exception Handler"
HostContext ->> Aarch64VCpurun: run()
Aarch64VCpurun ->> Aarch64VCpurun: save_host_sp_el0()
Aarch64VCpurun ->> Aarch64VCpurun: restore_vm_system_regs()
Aarch64VCpurun ->> Aarch64VCpurun: run_guest() [naked asm]
Note over Aarch64VCpurun: save_regs_to_stack!()
Note over Aarch64VCpurun: context_vm_entry
Aarch64VCpurun ->> GuestExecution: Enter guest execution
GuestExecution ->> ExceptionHandler: VM Exit (trap/interrupt)
ExceptionHandler ->> Aarch64VCpurun: return exit_reason
Aarch64VCpurun ->> Aarch64VCpurun: vmexit_handler(trap_kind)
Aarch64VCpurun ->> Aarch64VCpurun: guest_system_regs.store()
Aarch64VCpurun ->> Aarch64VCpurun: restore_host_sp_el0()
Aarch64VCpurun ->> HostContext: return AxVCpuExitReason
The execution model implements a clean separation between host and guest contexts, with careful register state management and stack handling to ensure proper isolation.
Sources: src/vcpu.rs(L99 - L111) src/vcpu.rs(L182 - L214) src/vcpu.rs(L255 - L282)
Per-CPU State Management
flowchart TD
subgraph subGraph2["VCPU Execution"]
run_start["run() entry"]
run_exit["vmexit_handler()"]
end
subgraph subGraph1["Context Operations"]
save_host["save_host_sp_el0()"]
restore_host["restore_host_sp_el0()"]
SP_EL0_reg["SP_EL0 register"]
end
subgraph subGraph0["Per-CPU Storage"]
HOST_SP_EL0["HOST_SP_EL0percpu static"]
end
HOST_SP_EL0 --> restore_host
restore_host --> SP_EL0_reg
run_exit --> restore_host
run_start --> save_host
save_host --> HOST_SP_EL0
save_host --> SP_EL0_reg
The per-CPU state management ensures that host context is properly preserved across guest execution cycles, using percpu storage to maintain thread-local state.
Sources: src/vcpu.rs(L15 - L26) src/vcpu.rs(L102 - L104) src/vcpu.rs(L270 - L272)
VM Exit Processing
Exit Reason Classification
The VCPU handles different types of VM exits through a structured dispatch mechanism:
flowchart TD vmexit_handler["vmexit_handler(exit_reason)"] store_guest["guest_system_regs.store()"] restore_host_sp["restore_host_sp_el0()"] dispatch["Match exit_reason"] sync_handler["handle_exception_sync(&ctx)"] irq_handler["AxVCpuExitReason::ExternalInterrupt"] panic_handler["panic!(Unhandled exception)"] return_reason["Return AxVCpuExitReason"] irq_fetch["H::irq_fetch() for vector"] system_halt["System Halt"] dispatch --> irq_handler dispatch --> panic_handler dispatch --> sync_handler irq_fetch --> return_reason irq_handler --> irq_fetch panic_handler --> system_halt restore_host_sp --> dispatch store_guest --> restore_host_sp sync_handler --> return_reason vmexit_handler --> store_guest
The exit processing maintains proper state transitions and delegates complex synchronous exceptions to specialized handlers while handling interrupts directly.
Sources: src/vcpu.rs(L255 - L282)
Register State Preservation
During VM exits, the system carefully manages register state to maintain proper isolation:
| Register Set | Storage Location | Timing |
|---|---|---|
| Guest GPRs | TrapFrame.ctx | During exception entry (assembly) |
| Guest System Regs | GuestSystemRegisters | Invmexit_handler() |
| Host SP_EL0 | HOST_SP_EL0percpu | Before guest execution |
| Guest SP_EL0 | ctx.sp_el0 | Fromguest_system_regs.sp_el0 |
Sources: src/vcpu.rs(L262 - L272)
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.
Sources: src/vcpu.rs(L15 - L26) src/vcpu.rs(L262 - L273)
Per-CPU State Management
Relevant source files
This document covers the per-CPU state management system in the arm_vcpu hypervisor, which handles virtualization control and interrupt management on a per-processor basis. The system is responsible for enabling/disabling hardware virtualization features, managing exception vectors, and coordinating interrupt handling between the hypervisor and host OS.
For information about the overall VCPU lifecycle and operations, see 2.1. For details about context switching and register state management, see 3.
Per-CPU Data Structure
The core of per-CPU state management is the Aarch64PerCpu structure, which implements the AxArchPerCpu trait to provide processor-specific virtualization control.
Aarch64PerCpu Structure
classDiagram
note for Aarch64PerCpu "4096-byte alignedRepresents per-CPU statefor virtualization control"
class Aarch64PerCpu {
+cpu_id: usize
-_phantom: PhantomData~H~
+new(cpu_id: usize) AxResult~Self~
+is_enabled() bool
+hardware_enable() AxResult
+hardware_disable() AxResult
}
class AxArchPerCpu {
<<trait>>
+new(cpu_id: usize) AxResult~Self~
+is_enabled() bool
+hardware_enable() AxResult
+hardware_disable() AxResult
}
class AxVCpuHal {
<<trait>>
+irq_hanlder()
}
Aarch64PerCpu ..|> AxArchPerCpu : implements
Aarch64PerCpu --> AxVCpuHal : uses H generic
Sources: src/pcpu.rs(L10 - L16)
The structure is aligned to 4096 bytes and contains minimal data, serving primarily as a coordination point for per-CPU virtualization operations. The generic parameter H provides hardware abstraction through the AxVCpuHal trait.
Per-CPU Variables
The system uses two key per-CPU variables to manage state across processor cores:
| Variable | Type | Purpose |
|---|---|---|
| ORI_EXCEPTION_VECTOR_BASE | usize | Stores originalVBAR_EL2value before virtualization |
| IRQ_HANDLER | OnceCell<&(dyn Fn() + Send + Sync)> | Host OS IRQ handler for this CPU |
Exception Vector Management
stateDiagram-v2
state HostVectors {
[*] --> OriginalVBAR
OriginalVBAR : "VBAR_EL2 = host exception vectors"
OriginalVBAR : "Stored in ORI_EXCEPTION_VECTOR_BASE"
}
state VCpuVectors {
[*] --> VCpuVBAR
VCpuVBAR : "VBAR_EL2 = exception_vector_base_vcpu"
VCpuVBAR : "Handles VM exits and traps"
}
[*] --> HostVectors : "Initial state"
HostVectors --> VCpuVectors : "hardware_enable()"
VCpuVectors --> HostVectors : "hardware_disable()"
note left of VCpuVectors : ['exception_vector_base_vcpu()<br>defined in assembly']
Sources: src/pcpu.rs(L18 - L19) src/pcpu.rs(L28 - L30) src/pcpu.rs(L53 - L57) src/pcpu.rs(L74 - L77)
Hardware Virtualization Control
The per-CPU system manages the AArch64 virtualization extensions through the Hypervisor Configuration Register (HCR_EL2).
Virtualization State Management
flowchart TD
subgraph subGraph0["HCR_EL2 Configuration"]
G1["HCR_EL2::VM::Enable"]
G2["HCR_EL2::RW::EL1IsAarch64"]
G3["HCR_EL2::IMO::EnableVirtualIRQ"]
G4["HCR_EL2::FMO::EnableVirtualFIQ"]
G5["HCR_EL2::TSC::EnableTrapEl1SmcToEl2"]
end
A["new(cpu_id)"]
B["Register IRQ handler"]
C["Per-CPU instance ready"]
D["hardware_enable()"]
E["Save original VBAR_EL2"]
F["Set VCPU exception vectors"]
G["Configure HCR_EL2"]
H["Virtualization enabled"]
I["is_enabled() returns true"]
J["hardware_disable()"]
K["Restore original VBAR_EL2"]
L["Clear HCR_EL2.VM"]
M["Virtualization disabled"]
A --> B
B --> C
C --> D
D --> E
E --> F
F --> G
G --> G1
G --> G2
G --> G3
G --> G4
G --> G5
G --> H
H --> I
H --> J
J --> K
K --> L
L --> M
Sources: src/pcpu.rs(L32 - L78)
HCR_EL2 Register Configuration
When enabling virtualization, the system configures several key control bits:
| Bit Field | Setting | Purpose |
|---|---|---|
| VM | Enable | Enables virtualization extensions |
| RW | EL1IsAarch64 | Sets EL1 execution state to AArch64 |
| IMO | EnableVirtualIRQ | Routes IRQs to virtual interrupt controller |
| FMO | EnableVirtualFIQ | Routes FIQs to virtual interrupt controller |
| TSC | EnableTrapEl1SmcToEl2 | Traps SMC instructions from EL1 to EL2 |
Sources: src/pcpu.rs(L59 - L65)
IRQ Handler Integration
The per-CPU system coordinates interrupt handling between the hypervisor and host OS through a registered handler mechanism.
IRQ Handler Registration
sequenceDiagram
participant HostOS as Host OS
participant Aarch64PerCpunew as Aarch64PerCpu::new()
participant IRQ_HANDLER as IRQ_HANDLER
participant AxVCpuHalirq_hanlder as AxVCpuHal::irq_hanlder()
HostOS ->> Aarch64PerCpunew: Create per-CPU instance
Aarch64PerCpunew ->> IRQ_HANDLER: Get current CPU's IRQ_HANDLER
Aarch64PerCpunew ->> AxVCpuHalirq_hanlder: Call H::irq_hanlder()
AxVCpuHalirq_hanlder -->> Aarch64PerCpunew: Return handler function
Aarch64PerCpunew ->> IRQ_HANDLER: Set handler in OnceCell
IRQ_HANDLER -->> Aarch64PerCpunew: Registration complete
Aarch64PerCpunew -->> HostOS: Per-CPU instance ready
Note over IRQ_HANDLER: Handler stored as<br>per-CPU variable<br>for current processor
Sources: src/pcpu.rs(L21 - L26) src/pcpu.rs(L34 - L37)
The IRQ handler is set once per CPU during initialization and provides a mechanism for the hypervisor's exception handling system to dispatch interrupts back to the host OS when appropriate.
Integration with VCPU System
The per-CPU state management integrates closely with the broader VCPU lifecycle:
Per-CPU and VCPU Relationship
flowchart TD
subgraph subGraph2["Host OS Integration"]
HostIRQ["Host IRQ Handler"]
HostVectors["Host Exception Vectors"]
end
subgraph subGraph1["VCPU Operations"]
VCPUCreate["Aarch64VCpu creation"]
VCPURun["vcpu.run()"]
ExceptionVectors["exception_vector_base_vcpu"]
end
subgraph subGraph0["CPU Core N"]
PerCPU["Aarch64PerCpu<H>"]
VBAR["VBAR_EL2 Register"]
HCR["HCR_EL2 Register"]
IRQVar["IRQ_HANDLER per-CPU var"]
end
note1["hardware_enable() sets upvirtualization for this CPU"]
note2["hardware_disable() restoresoriginal host state"]
ExceptionVectors --> VBAR
IRQVar --> HostIRQ
PerCPU --> HCR
PerCPU --> HostVectors
PerCPU --> IRQVar
PerCPU --> VBAR
PerCPU --> note1
PerCPU --> note2
VCPUCreate --> PerCPU
VCPURun --> PerCPU
Sources: src/pcpu.rs(L45 - L78)
The per-CPU system provides the foundation for VCPU operations by ensuring that hardware virtualization extensions are properly configured and that interrupt handling is coordinated between the hypervisor and host OS on each processor core.
Context Switching and State Management
Relevant source files
This document covers the data structures and mechanisms used for saving and restoring CPU state during context switches between the hypervisor (host) and guest virtual machines. The context switching system is responsible for preserving and restoring both general-purpose registers and system control registers to enable seamless transitions between execution contexts.
For information about the detailed register layouts and data structure implementations, see TrapFrame and System Registers. For the broader exception handling pipeline that triggers context switches, see Exception Handling System.
Context Switching Overview
The hypervisor implements a two-level context switching mechanism that manages transitions between host (EL2) and guest (EL1) execution contexts. Context switches occur during VM entry when launching guest execution and during VM exits when handling exceptions or interrupts from the guest.
Context Switch Data Flow
flowchart TD Host["Host Execution (EL2)"] SaveHost["save_regs_to_stack!()"] SaveSP["save_host_sp_el0()"] RestoreGuest["restore_vm_system_regs()"] GuestEntry["context_vm_entry"] Guest["Guest Execution (EL1)"] TrapEntry["Exception Vector"] SaveGuestCtx["SAVE_REGS_FROM_EL1"] SaveGuestSys["guest_system_regs.store()"] RestoreHostSP["restore_host_sp_el0()"] RestoreHostCtx["restore_regs_from_stack!()"] Guest --> TrapEntry GuestEntry --> Guest Host --> SaveHost RestoreGuest --> GuestEntry RestoreHostCtx --> Host RestoreHostSP --> RestoreHostCtx SaveGuestCtx --> SaveGuestSys SaveGuestSys --> RestoreHostSP SaveHost --> SaveSP SaveSP --> RestoreGuest TrapEntry --> SaveGuestCtx
Sources: src/vcpu.rs(L182 - L283) src/context_frame.rs(L199 - L300)
State Management Data Structures
The system uses two primary data structures to manage CPU state during context switches:
Core Context Structures
classDiagram
class VmCpuRegisters {
+TrapFrame trap_context_regs
+GuestSystemRegisters vm_system_regs
}
class Aarch64ContextFrame {
+u64[31] gpr
+u64 sp_el0
+u64 elr
+u64 spsr
+set_gpr(index, val)
+gpr(index) usize
+set_exception_pc(pc)
}
class GuestSystemRegisters {
+u64 cntvoff_el2
+u32 cntkctl_el1
+u64 sp_el0
+u32 sctlr_el1
+u64 hcr_el2
+u64 vttbr_el2
+u64 vtcr_el2
+store()
+restore()
}
class Aarch64VCpu {
+TrapFrame ctx
+u64 host_stack_top
+GuestSystemRegisters guest_system_regs
+run() AxVCpuExitReason
}
VmCpuRegisters --> Aarch64ContextFrame
VmCpuRegisters --> GuestSystemRegisters
Aarch64VCpu --> Aarch64ContextFrame
Aarch64VCpu --> GuestSystemRegisters
Sources: src/vcpu.rs(L28 - L51) src/context_frame.rs(L17 - L28) src/context_frame.rs(L145 - L197)
| Structure | Purpose | Key Fields |
|---|---|---|
| Aarch64ContextFrame | General-purpose register context | gpr[31],sp_el0,elr,spsr |
| GuestSystemRegisters | System control registers | Timer, MMU, hypervisor control registers |
| VmCpuRegisters | Combined guest state | Combines both context types |
Context Switch Mechanisms
Host-to-Guest Transition
The transition from host to guest execution follows a carefully orchestrated sequence implemented in the run_guest() function:
sequenceDiagram
participant HostContext as "Host Context"
participant HostStack as "Host Stack"
participant PerCPUStorage as "Per-CPU Storage"
participant SystemRegisters as "System Registers"
participant GuestContext as "Guest Context"
HostContext ->> HostStack: "save_regs_to_stack!()"
HostContext ->> PerCPUStorage: "save_host_sp_el0()"
Note over HostContext,PerCPUStorage: "Store HOST_SP_EL0 to per-CPU region"
HostContext ->> SystemRegisters: "restore_vm_system_regs()"
SystemRegisters ->> GuestContext: "context_vm_entry"
Note over GuestContext: "Guest begins execution"
Sources: src/vcpu.rs(L182 - L214) src/vcpu.rs(L15 - L26) src/vcpu.rs(L225 - L244)
The run_guest() function is implemented as a naked function to ensure precise control over the stack and register state:
- Host Register Preservation: Uses
save_regs_to_stack!()macro to save callee-saved registers (x19-x30) - Stack Pointer Management: Stores current stack pointer in
host_stack_topfield of the VCPU structure - SP_EL0 Handling: Saves host's
SP_EL0to per-CPU storage usingsave_host_sp_el0() - System Register Restoration: Calls
restore_vm_system_regs()to load guest system state
Guest-to-Host Transition
Guest-to-host transitions occur during VM exits and are handled by the exception vector table and the vmexit_handler():
flowchart TD Exception["Guest Exception/Interrupt"] SaveRegs["SAVE_REGS_FROM_EL1"] StoreGuest["guest_system_regs.store()"] SaveSP["ctx.sp_el0 = guest_system_regs.sp_el0"] RestoreSP["restore_host_sp_el0()"] Handler["Exception Handler"] Return["Return to Host"] Exception --> SaveRegs Handler --> Return RestoreSP --> Handler SaveRegs --> StoreGuest SaveSP --> RestoreSP StoreGuest --> SaveSP
Sources: src/vcpu.rs(L255 - L282) src/context_frame.rs(L213 - L253)
The guest-to-host transition process:
- Exception Context Capture: Assembly code saves guest GPRs to
TrapFrame - System Register Storage:
guest_system_regs.store()captures current system register state - SP_EL0 Coordination: Guest's
SP_EL0is transferred from system registers to the context frame - Host SP_EL0 Restoration:
restore_host_sp_el0()restores host's stack pointer - Exception Processing: Control passes to exception handlers
State Preservation Mechanisms
Per-CPU State Management
The system uses per-CPU storage to maintain host state across context switches:
flowchart TD
subgraph subGraph2["System Register"]
SP_EL0_REG["SP_EL0 Register"]
end
subgraph subGraph1["Host Context Operations"]
SaveSP["save_host_sp_el0()"]
RestoreSP["restore_host_sp_el0()"]
end
subgraph subGraph0["Per-CPU Storage"]
HOST_SP_EL0["HOST_SP_EL0: u64"]
end
HOST_SP_EL0 --> RestoreSP
RestoreSP --> SP_EL0_REG
SP_EL0_REG --> SaveSP
SaveSP --> HOST_SP_EL0
Sources: src/vcpu.rs(L15 - L26)
System Register State Management
The GuestSystemRegisters structure provides comprehensive system register preservation through assembly-based store() and restore() methods:
| Register Category | Key Registers | Purpose |
|---|---|---|
| Timer Registers | CNTVOFF_EL2,CNTKCTL_EL1,CNTV_CTL_EL0 | Virtual timer state |
| Memory Management | TTBR0_EL1,TTBR1_EL1,TCR_EL1,MAIR_EL1 | Address translation setup |
| Hypervisor Control | HCR_EL2,VTTBR_EL2,VTCR_EL2 | Virtualization configuration |
| Exception State | VBAR_EL1,ESR_EL1,FAR_EL1 | Exception handling state |
Sources: src/context_frame.rs(L149 - L197) src/context_frame.rs(L213 - L253) src/context_frame.rs(L263 - L299)
Integration with Exception Handling
Context switching is tightly integrated with the exception handling system. The context switch mechanisms are triggered by:
- VM Entry: Initiated by calls to
Aarch64VCpu::run() - VM Exit: Triggered by exceptions caught by the vector table in
exception.S - Exception Return: Managed by assembly routines that restore guest context
The vmexit_handler() function coordinates the transition from low-level exception handling back to the hypervisor control flow:
flowchart TD VMExit["VM Exit Event"] SaveState["Save Guest State"] RestoreHost["Restore Host State"] ExceptionSync["handle_exception_sync()"] ExitReason["Return AxVCpuExitReason"] Hypervisor["Hypervisor Handles Exit"] ExceptionSync --> ExitReason ExitReason --> Hypervisor RestoreHost --> ExceptionSync SaveState --> RestoreHost VMExit --> SaveState
Sources: src/vcpu.rs(L255 - L282)
The context switching system ensures that state transitions are atomic and that both host and guest contexts are properly preserved across the hypervisor's execution boundaries.
TrapFrame and System Registers
Relevant source files
This document details the core data structures used for preserving CPU state during context switches in the arm_vcpu hypervisor. The system uses two primary structures: Aarch64ContextFrame for saving general-purpose registers and basic execution state, and GuestSystemRegisters for preserving system control registers and hypervisor-specific state.
For information about the broader context switching mechanism and how these structures are used during VM exits, see Context Switching and State Management. For details on the assembly-level context switching code that manipulates these structures, see Assembly Exception Vectors.
TrapFrame Structure Overview
The Aarch64ContextFrame serves as the primary trap frame for saving guest execution context when exceptions occur. This structure captures the minimal set of registers needed to preserve the guest's execution state across VM exits.
flowchart TD
subgraph subGraph2["Special Handling"]
ZERO["Register x31Always returns 0Cannot be modified(AArch64 Zero Register)"]
end
subgraph subGraph1["Access Methods"]
GET["gpr(index) -> usizeset_gpr(index, val)exception_pc() -> usizeset_exception_pc(pc)set_argument(arg)"]
end
subgraph subGraph0["Aarch64ContextFrame Memory Layout"]
GPR["gpr[31]: u64General Purpose Registersx0 through x30"]
SP["sp_el0: u64EL0 Stack Pointer"]
ELR["elr: u64Exception Link Register(Return Address)"]
SPSR["spsr: u64Saved Program Status Register(CPU Flags & State)"]
end
ELR --> GET
GPR --> GET
GPR --> ZERO
SP --> GET
SPSR --> GET
The structure uses #[repr(C)] layout to ensure predictable memory organization for hardware and assembly code interaction. The default state configures SPSR to mask all exceptions and set EL1h mode for safe guest initialization.
Sources: src/context_frame.rs(L17 - L63)
System Registers Structure
The GuestSystemRegisters structure manages the complete system-level state of a guest VM, including control registers, memory management settings, timer state, and hypervisor configuration.
flowchart TD
subgraph Operations["Operations"]
STORE["store() - Save hardware to structUses MRS instructions"]
RESTORE["restore() - Load struct to hardwareUses MSR instructions"]
RESET["reset() - Zero all fields"]
end
subgraph subGraph0["GuestSystemRegisters Categories"]
TIMER["Timer Registerscntvoff_el2, cntp_cval_el0cntv_cval_el0, cntkctl_el1cntvct_el0, cntp_ctl_el0cntv_ctl_el0, cntp_tval_el0cntv_tval_el0"]
PROC["Processor IDvpidr_el2vmpidr_el2"]
EL1["EL1/EL0 System Statesp_el0, sp_el1, elr_el1spsr_el1, sctlr_el1actlr_el1, cpacr_el1"]
MMU["Memory Managementttbr0_el1, ttbr1_el1tcr_el1, mair_el1amair_el1, par_el1"]
EXC["Exception Handlingesr_el1, far_el1vbar_el1, far_el2hpfar_el2"]
THREAD["Thread Pointerstpidr_el0, tpidr_el1tpidrro_el0contextidr_el1"]
HYP["Hypervisor Contexthcr_el2, vttbr_el2cptr_el2, hstr_el2pmcr_el0, vtcr_el2"]
end
EL1 --> STORE
EXC --> STORE
HYP --> STORE
MMU --> STORE
PROC --> STORE
RESTORE --> RESET
STORE --> RESTORE
THREAD --> STORE
TIMER --> STORE
The structure is aligned to 16 bytes (#[repr(align(16))]) for optimal memory access performance during context switches.
Sources: src/context_frame.rs(L138 - L300)
Register State Preservation
The system employs a two-tier approach to state preservation, with different registers handled by different mechanisms based on their access patterns and importance.
TrapFrame Register Operations
flowchart TD
subgraph subGraph2["Default State"]
DEF["SPSR ConfigurationM: EL1h modeI,F,A,D: All maskedELR: 0, SP_EL0: 0"]
end
subgraph subGraph1["Special Operations"]
PC["exception_pc()set_exception_pc(pc)Maps to ELR field"]
ARG["set_argument(arg)Sets x0 registerFor return values"]
end
subgraph subGraph0["GPR Access Pattern"]
INDEX["Register Index0-30: Normal GPRs31: Zero Register"]
GET["gpr(index)Returns register valueIndex 31 -> 0"]
SET["set_gpr(index, val)Sets register valueIndex 31 -> Warning"]
end
ARG --> SET
INDEX --> GET
INDEX --> SET
PC --> GET
SET --> DEF
Sources: src/context_frame.rs(L65 - L136)
System Register Save/Restore Cycle
sequenceDiagram
participant GuestVM as "Guest VM"
participant HardwareRegisters as "Hardware Registers"
participant GuestSystemRegisters as "GuestSystemRegisters"
participant HostHypervisor as "Host/Hypervisor"
Note over GuestVM,HostHypervisor: VM Exit Sequence
GuestVM ->> HardwareRegisters: Exception/Trap occurs
HardwareRegisters ->> GuestSystemRegisters: store() - MRS instructions
Note over GuestSystemRegisters: All system state captured
GuestSystemRegisters ->> HostHypervisor: Context available for inspection
Note over GuestVM,HostHypervisor: VM Entry Sequence
HostHypervisor ->> GuestSystemRegisters: Configure guest state
GuestSystemRegisters ->> HardwareRegisters: restore() - MSR instructions
Note over HardwareRegisters: Guest context loaded
HardwareRegisters ->> GuestVM: ERET to guest execution
The store() and restore() methods use inline assembly with MRS (Move Register from System) and MSR (Move System Register) instructions to efficiently transfer register state between hardware and memory.
Sources: src/context_frame.rs(L213 - L299)
Integration with Context Switching
These structures integrate closely with the assembly-level context switching mechanism to provide complete state preservation during VM transitions.
flowchart TD
subgraph subGraph2["State Structures"]
TF["Aarch64ContextFrameGPRs, SP_EL0, ELR, SPSR"]
GSR["GuestSystemRegistersSystem control state"]
end
subgraph subGraph1["VM Entry Flow"]
SETUP["Host configuresguest state"]
SYS_RESTORE["guest_system_regs.restore()Struct to hardware"]
ASM_RESTORE["context_vm_entryAssembly restores from TrapFrame"]
GUEST["Guest executionresumes"]
end
subgraph subGraph0["VM Exit Flow"]
TRAP["Exception OccursHardware trap"]
ASM_SAVE["SAVE_REGS_FROM_EL1Assembly saves to TrapFrame"]
SYS_SAVE["guest_system_regs.store()System registers to struct"]
HANDLER["Exception handlerProcesses exit reason"]
end
ASM_RESTORE --> GUEST
ASM_RESTORE --> TF
ASM_SAVE --> SYS_SAVE
ASM_SAVE --> TF
SETUP --> SYS_RESTORE
SYS_RESTORE --> ASM_RESTORE
SYS_RESTORE --> GSR
SYS_SAVE --> GSR
SYS_SAVE --> HANDLER
TRAP --> ASM_SAVE
The TrapFrame is manipulated directly by assembly code during the low-level context switch, while the GuestSystemRegisters structure is managed by Rust code using the store() and restore() methods.
Sources: src/context_frame.rs(L1 - L301)
Register Categories and Usage
| Category | Registers | Purpose | Access Pattern |
|---|---|---|---|
| General Purpose | x0-x30, SP_EL0 | Function arguments, local variables, stack management | Every VM exit/entry via assembly |
| Execution State | ELR, SPSR | Program counter and processor status | Every VM exit/entry via assembly |
| Memory Management | TTBR0_EL1, TTBR1_EL1, TCR_EL1, MAIR_EL1 | Page table configuration, memory attributes | Restored on VM entry |
| System Control | SCTLR_EL1, CPACR_EL1, ACTLR_EL1 | Processor features, access control | Restored on VM entry |
| Exception Handling | VBAR_EL1, ESR_EL1, FAR_EL1 | Vector table, exception information | Preserved across exits |
| Timer Management | CNTVOFF_EL2, CNTKCTL_EL1, timer control/value registers | Virtual timer state | Managed by hypervisor |
| Hypervisor Control | HCR_EL2, VTTBR_EL2, VTCR_EL2 | Virtualization configuration, stage-2 MMU | Set during VCPU setup |
The register categories reflect the layered nature of AArch64 virtualization, with some registers requiring preservation on every context switch while others are configured once during VCPU initialization.
Sources: src/context_frame.rs(L148 - L197)
Exception Handling System
Relevant source files
This document covers the multi-layered exception handling architecture that manages VM exits, traps, and interrupts when a guest VM encounters exceptional conditions. The system provides a complete pipeline from low-level assembly exception vectors through register analysis utilities to high-level exception dispatch and handling.
For information about the VCPU lifecycle and context switching mechanisms, see Virtual CPU Management. For details about the assembly exception vectors specifically, see Assembly Exception Vectors.
Architecture Overview
The exception handling system operates across three distinct layers, each responsible for different aspects of exception processing:
Exception Handling System Architecture
flowchart TD
subgraph subGraph4["VCPU Integration"]
TRAP_FRAME["TrapFrame"]
EXIT_REASON["AxVCpuExitReason"]
VCPU_RUN["Aarch64VCpu::run()"]
end
subgraph subGraph3["Handler Layer (exception.rs)"]
HANDLE_EXCEPTION_SYNC["handle_exception_sync()"]
HANDLE_DATA_ABORT["handle_data_abort()"]
HANDLE_SYSTEM_REGISTER["handle_system_register()"]
HANDLE_PSCI_CALL["handle_psci_call()"]
HANDLE_SMC64["handle_smc64_exception()"]
end
subgraph subGraph2["Utility Layer (exception_utils.rs)"]
EXCEPTION_ESR["exception_esr()"]
EXCEPTION_CLASS["exception_class()"]
EXCEPTION_FAULT_ADDR["exception_fault_addr()"]
DATA_ABORT_UTILS["Data Abort Analysis Functions"]
SYSREG_UTILS["System Register Analysis Functions"]
end
subgraph subGraph1["Assembly Layer (exception.S)"]
VECTOR_TABLE["exception_vector_base_vcpu"]
SAVE_REGS["SAVE_REGS_FROM_EL1"]
RESTORE_REGS["RESTORE_REGS_INTO_EL1"]
VMEXIT_TRAMP["vmexit_trampoline"]
CONTEXT_VM_ENTRY["context_vm_entry"]
end
subgraph subGraph0["Guest VM (EL1/EL0)"]
GUEST_CODE["Guest Code Execution"]
GUEST_EXC["Exception/Trap Triggered"]
end
CONTEXT_VM_ENTRY --> RESTORE_REGS
EXIT_REASON --> VCPU_RUN
GUEST_CODE --> GUEST_EXC
GUEST_EXC --> VECTOR_TABLE
HANDLE_DATA_ABORT --> DATA_ABORT_UTILS
HANDLE_DATA_ABORT --> EXCEPTION_FAULT_ADDR
HANDLE_DATA_ABORT --> EXIT_REASON
HANDLE_EXCEPTION_SYNC --> EXCEPTION_CLASS
HANDLE_EXCEPTION_SYNC --> EXCEPTION_ESR
HANDLE_EXCEPTION_SYNC --> HANDLE_DATA_ABORT
HANDLE_EXCEPTION_SYNC --> HANDLE_PSCI_CALL
HANDLE_EXCEPTION_SYNC --> HANDLE_SMC64
HANDLE_EXCEPTION_SYNC --> HANDLE_SYSTEM_REGISTER
HANDLE_PSCI_CALL --> EXIT_REASON
HANDLE_SMC64 --> EXIT_REASON
HANDLE_SYSTEM_REGISTER --> EXIT_REASON
HANDLE_SYSTEM_REGISTER --> SYSREG_UTILS
RESTORE_REGS --> GUEST_CODE
RESTORE_REGS --> TRAP_FRAME
SAVE_REGS --> TRAP_FRAME
SAVE_REGS --> VMEXIT_TRAMP
VCPU_RUN --> CONTEXT_VM_ENTRY
VECTOR_TABLE --> SAVE_REGS
VMEXIT_TRAMP --> HANDLE_EXCEPTION_SYNC
Sources: src/exception.S(L106 - L141) src/exception.rs(L72 - L126) src/exception_utils.rs(L1 - L312)
Exception Processing Pipeline
The system processes exceptions through a structured pipeline that preserves guest state while analyzing and dispatching exceptions to appropriate handlers:
Exception Processing Flow
flowchart TD
subgraph subGraph4["Exit Reason Generation"]
MMIO_READ["AxVCpuExitReason::MmioRead"]
MMIO_WRITE["AxVCpuExitReason::MmioWrite"]
HYPERCALL["AxVCpuExitReason::Hypercall"]
CPU_UP["AxVCpuExitReason::CpuUp"]
CPU_DOWN["AxVCpuExitReason::CpuDown"]
SYSTEM_DOWN["AxVCpuExitReason::SystemDown"]
SYSREG_READ["AxVCpuExitReason::SysRegRead"]
SYSREG_WRITE["AxVCpuExitReason::SysRegWrite"]
NOTHING["AxVCpuExitReason::Nothing"]
end
subgraph subGraph3["Specific Handlers"]
DATA_ABORT["DataAbortLowerEL → handle_data_abort()"]
HVC_CALL["HVC64 → handle_psci_call() or Hypercall"]
SYS_REG["TrappedMsrMrs → handle_system_register()"]
SMC_CALL["SMC64 → handle_smc64_exception()"]
UNHANDLED["Unhandled → panic!"]
end
subgraph subGraph2["Exception Classification"]
GET_ESR["exception_esr()"]
GET_CLASS["exception_class()"]
CLASS_MATCH["Match ESR_EL2::EC"]
end
subgraph subGraph1["Context Save & Dispatch"]
SAVE_CONTEXT["SAVE_REGS_FROM_EL1 macro"]
VMEXIT_FUNC["vmexit_trampoline()"]
SYNC_HANDLER["handle_exception_sync()"]
end
subgraph subGraph0["Assembly Vector Dispatch"]
VECTOR_BASE["exception_vector_base_vcpu"]
CURRENT_EL_SYNC["current_el_sync_handler"]
CURRENT_EL_IRQ["current_el_irq_handler"]
LOWER_EL_SYNC["HANDLE_LOWER_SYNC_VCPU"]
LOWER_EL_IRQ["HANDLE_LOWER_IRQ_VCPU"]
INVALID_EXC["invalid_exception_el2"]
end
GUEST_TRAP["Guest VM Exception/Trap"]
CLASS_MATCH --> DATA_ABORT
CLASS_MATCH --> HVC_CALL
CLASS_MATCH --> SMC_CALL
CLASS_MATCH --> SYS_REG
CLASS_MATCH --> UNHANDLED
DATA_ABORT --> MMIO_READ
DATA_ABORT --> MMIO_WRITE
GET_CLASS --> CLASS_MATCH
GUEST_TRAP --> VECTOR_BASE
HVC_CALL --> CPU_DOWN
HVC_CALL --> CPU_UP
HVC_CALL --> HYPERCALL
HVC_CALL --> SYSTEM_DOWN
LOWER_EL_IRQ --> SAVE_CONTEXT
LOWER_EL_SYNC --> SAVE_CONTEXT
SAVE_CONTEXT --> VMEXIT_FUNC
SMC_CALL --> NOTHING
SYNC_HANDLER --> GET_CLASS
SYNC_HANDLER --> GET_ESR
SYS_REG --> SYSREG_READ
SYS_REG --> SYSREG_WRITE
VECTOR_BASE --> CURRENT_EL_IRQ
VECTOR_BASE --> CURRENT_EL_SYNC
VECTOR_BASE --> INVALID_EXC
VECTOR_BASE --> LOWER_EL_IRQ
VECTOR_BASE --> LOWER_EL_SYNC
VMEXIT_FUNC --> SYNC_HANDLER
Sources: src/exception.S(L108 - L141) src/exception.rs(L72 - L126) src/exception.rs(L276 - L290)
Exception Types and Dispatch Logic
The handle_exception_sync function serves as the primary dispatcher, using the Exception Class (EC) field from ESR_EL2 to route exceptions to specialized handlers:
| Exception Class | ESR_EL2::EC Value | Handler Function | Exit Reason Generated |
|---|---|---|---|
| Data Abort Lower EL | DataAbortLowerEL | handle_data_abort() | MmioRead,MmioWrite |
| Hypervisor Call | HVC64 | handle_psci_call()or Hypercall | CpuUp,CpuDown,SystemDown,Hypercall |
| System Register Access | TrappedMsrMrs | handle_system_register() | SysRegRead,SysRegWrite |
| Secure Monitor Call | SMC64 | handle_smc64_exception() | Nothing(forwarded to ATF) |
Sources: src/exception.rs(L72 - L126)
Data Abort Handling
Data aborts occur when the guest VM accesses memory that triggers a fault. The handle_data_abort function processes these by:
- Fault Analysis: Using
exception_fault_addr()to determine the guest physical address - Access Classification: Determining read/write direction and access width using utility functions
- Register Identification: Finding which guest register was involved in the access
- MMIO Translation: Converting the fault into an appropriate
AxVCpuExitReason
The function performs several safety checks using exception_data_abort_handleable() and exception_data_abort_is_translate_fault() before proceeding with MMIO emulation.
Sources: src/exception.rs(L128 - L182) src/exception_utils.rs(L132 - L142) src/exception_utils.rs(L202 - L254)
System Register Access Handling
System register accesses are trapped when the guest VM attempts to read or write control registers. The handle_system_register function:
- ISS Parsing: Extracts the Instruction Specific Syndrome from
ESR_EL2 - Register Identification: Uses
exception_sysreg_addr()to identify the target register - Direction Detection: Determines read vs write using
exception_sysreg_direction_write() - GPR Mapping: Identifies the guest register involved using
exception_sysreg_gpr()
Sources: src/exception.rs(L184 - L211) src/exception_utils.rs(L174 - L195)
PSCI and Hypervisor Call Handling
The system supports both PSCI (Power State Coordination Interface) calls and general hypervisor calls through the HVC instruction. The handle_psci_call function recognizes PSCI function IDs in specific ranges:
- 32-bit PSCI: Function IDs
0x8400_0000..=0x8400_001F - 64-bit PSCI: Function IDs
0xC400_0000..=0xC400_001F
Supported PSCI operations include:
PSCI_FN_CPU_OFF→AxVCpuExitReason::CpuDownPSCI_FN_CPU_ON→AxVCpuExitReason::CpuUpPSCI_FN_SYSTEM_OFF→AxVCpuExitReason::SystemDown
Non-PSCI HVC calls are treated as general hypervisor calls with the call number in x0 and arguments in x1-x6.
Sources: src/exception.rs(L220 - L254) src/exception.rs(L80 - L102)
SMC Call Handling
Secure Monitor Calls (SMC) are either handled as PSCI calls or forwarded directly to the ARM Trusted Firmware (ATF). The handle_smc64_exception function first checks for PSCI compatibility, then uses the smc_call function to forward non-PSCI SMCs to the secure world.
Sources: src/exception.rs(L256 - L271) src/exception.rs(L104 - L109)
Integration with Context Management
The exception handling system integrates tightly with the VCPU context management through several mechanisms:
Context Integration Points
flowchart TD
subgraph subGraph2["Exception Exit"]
RESTORE_MACRO["RESTORE_REGS_INTO_EL1"]
CONTEXT_ENTRY["context_vm_entry"]
ERET_INST["eret instruction"]
end
subgraph subGraph1["Handler Processing"]
CONTEXT_PARAM["ctx: &mut TrapFrame"]
GPR_ACCESS["ctx.gpr[n] access"]
PC_UPDATE["ctx.set_exception_pc()"]
end
subgraph subGraph0["Exception Entry"]
SAVE_MACRO["SAVE_REGS_FROM_EL1"]
TRAP_FRAME_SAVE["TrapFrame Population"]
end
CONTEXT_ENTRY --> ERET_INST
CONTEXT_PARAM --> GPR_ACCESS
CONTEXT_PARAM --> PC_UPDATE
GPR_ACCESS --> RESTORE_MACRO
PC_UPDATE --> RESTORE_MACRO
RESTORE_MACRO --> CONTEXT_ENTRY
SAVE_MACRO --> TRAP_FRAME_SAVE
TRAP_FRAME_SAVE --> CONTEXT_PARAM
The system maintains guest CPU state through the TrapFrame structure, allowing handlers to:
- Read guest register values for processing hypervisor calls and data aborts
- Update the program counter when advancing past trapped instructions
- Modify register contents for return values (e.g., MMIO read results)
Sources: src/exception.S(L1 - L30) src/exception.S(L32 - L58) src/exception.rs(L75 - L77) src/exception.rs(L105 - L107)
Assembly Exception Vectors
Relevant source files
This document covers the low-level assembly exception vector table and context switching implementation that forms the foundation of the hypervisor's exception handling system. The assembly code provides the first-level exception entry points, register preservation, and routing to higher-level exception handlers.
For information about the high-level exception dispatch logic and handler implementations, see High-Level Exception Handling. For details about the register parsing and analysis utilities, see Exception Analysis and Utilities.
Exception Vector Table Structure
The exception vector table exception_vector_base_vcpu implements the AArch64 exception vector layout with 16 entries, each aligned to 128-byte boundaries. The table handles exceptions from different execution states and privilege levels.
flowchart TD
subgraph exception_vector_base_vcpu["exception_vector_base_vcpu"]
subgraph subGraph3["Lower EL AArch32 (0x600-0x7FF)"]
LE32_SYNC["0x600: INVALID_EXCP_EL2 0 3"]
LE32_IRQ["0x680: INVALID_EXCP_EL2 1 3"]
LE32_FIQ["0x700: INVALID_EXCP_EL2 2 3"]
LE32_ERR["0x780: INVALID_EXCP_EL2 3 3"]
end
subgraph subGraph2["Lower EL AArch64 (0x400-0x5FF)"]
LE64_SYNC["0x400: HANDLE_LOWER_SYNC_VCPU"]
LE64_IRQ["0x480: HANDLE_LOWER_IRQ_VCPU"]
LE64_FIQ["0x500: INVALID_EXCP_EL2 2 2"]
LE64_ERR["0x580: INVALID_EXCP_EL2 3 2"]
end
subgraph subGraph1["Current EL with SP_ELx (0x200-0x3FF)"]
CEX_SYNC["0x200: HANDLE_CURRENT_SYNC"]
CEX_IRQ["0x280: HANDLE_CURRENT_IRQ"]
CEX_FIQ["0x300: INVALID_EXCP_EL2 2 1"]
CEX_ERR["0x380: INVALID_EXCP_EL2 3 1"]
end
subgraph subGraph0["Current EL with SP_EL0 (0x000-0x1FF)"]
CE0_SYNC["0x000: INVALID_EXCP_EL2 0 0"]
CE0_IRQ["0x080: INVALID_EXCP_EL2 1 0"]
CE0_FIQ["0x100: INVALID_EXCP_EL2 2 0"]
CE0_ERR["0x180: INVALID_EXCP_EL2 3 0"]
end
end
CURR_SYNC["current_el_sync_handler"]
CURR_IRQ["current_el_irq_handler"]
VMEXIT["vmexit_trampoline(exception_sync)"]
VMEXIT_IRQ["vmexit_trampoline(exception_irq)"]
INVALID["invalid_exception_el2"]
CE0_SYNC --> INVALID
CEX_IRQ --> CURR_IRQ
CEX_SYNC --> CURR_SYNC
LE32_SYNC --> INVALID
LE64_IRQ --> VMEXIT_IRQ
LE64_SYNC --> VMEXIT
Sources: src/exception.S(L105 - L131)
The table supports four types of exceptions for each execution state:
- Synchronous exceptions (offset 0x000): System calls, data aborts, instruction aborts
- IRQ interrupts (offset 0x080): Asynchronous hardware interrupts
- FIQ interrupts (offset 0x100): Fast interrupt requests
- System Error/SError (offset 0x180): Asynchronous system errors
Register Context Management
The assembly code implements comprehensive register preservation using two primary macros that save and restore the complete processor state during exception handling.
Register Save Operation
flowchart TD
subgraph subGraph0["Register Storage Layout"]
X0_X1["[sp + 0]: x0, x1"]
X2_X3["[sp + 16]: x2, x3"]
DOTS["..."]
X28_X29["[sp + 224]: x28, x29"]
X30_SP["[sp + 240]: x30, sp_el0"]
ELR_SPSR["[sp + 256]: elr_el2, spsr_el2"]
end
ENTRY["Exception Entry"]
SAVE_START["SAVE_REGS_FROM_EL1"]
SP_ADJ["sub sp, sp, 34 * 8"]
GPR_SAVE["Save GPRs x0-x29"]
SPECIAL_SAVE["Save x30, sp_el0"]
ELR_SAVE["Save elr_el2, spsr_el2"]
HANDLER["Jump to Exception Handler"]
ELR_SAVE --> ELR_SPSR
ELR_SAVE --> HANDLER
ENTRY --> SAVE_START
GPR_SAVE --> SPECIAL_SAVE
GPR_SAVE --> X0_X1
GPR_SAVE --> X28_X29
GPR_SAVE --> X2_X3
SAVE_START --> SP_ADJ
SPECIAL_SAVE --> ELR_SAVE
SPECIAL_SAVE --> X30_SP
SP_ADJ --> GPR_SAVE
Sources: src/exception.S(L1 - L30)
Register Restore Operation
The RESTORE_REGS_INTO_EL1 macro reverses the save operation, restoring all registers and returning to the guest execution context:
| Restore Order | Registers | Assembly Instructions |
|---|---|---|
| 1. Exception State | elr_el2,spsr_el2 | msr elr_el2, x10; msr spsr_el2, x11 |
| 2. Stack Pointer | sp_el0 | msr sp_el0, x9 |
| 3. General Purpose | x0-x29 | ldpinstructions in reverse order |
| 4. Return | Stack adjustment | add sp, sp, 34 * 8 |
Sources: src/exception.S(L32 - L58)
Exception Routing and Handler Dispatch
The exception vectors route different exception types to specialized handlers based on the source execution level and exception characteristics.
flowchart TD
subgraph subGraph2["Handler Functions"]
VMEXIT_SYNC["vmexit_trampoline(exception_sync)"]
VMEXIT_IRQ["vmexit_trampoline(exception_irq)"]
CURRENT_SYNC["current_el_sync_handler"]
CURRENT_IRQ["current_el_irq_handler"]
INVALID_HANDLER["invalid_exception_el2"]
end
subgraph subGraph1["Assembly Macros"]
HANDLE_LOWER_SYNC["HANDLE_LOWER_SYNC_VCPU"]
HANDLE_LOWER_IRQ["HANDLE_LOWER_IRQ_VCPU"]
HANDLE_CURRENT_SYNC["HANDLE_CURRENT_SYNC"]
HANDLE_CURRENT_IRQ["HANDLE_CURRENT_IRQ"]
INVALID_EXCP["INVALID_EXCP_EL2"]
end
subgraph subGraph0["Exception Types"]
GUEST_SYNC["Guest Sync Exception"]
GUEST_IRQ["Guest IRQ"]
HOST_SYNC["Host EL2 Sync"]
HOST_IRQ["Host EL2 IRQ"]
INVALID["Invalid Exception"]
end
GUEST_IRQ --> HANDLE_LOWER_IRQ
GUEST_SYNC --> HANDLE_LOWER_SYNC
HANDLE_CURRENT_IRQ --> CURRENT_IRQ
HANDLE_CURRENT_SYNC --> CURRENT_SYNC
HANDLE_LOWER_IRQ --> VMEXIT_IRQ
HANDLE_LOWER_SYNC --> VMEXIT_SYNC
HOST_IRQ --> HANDLE_CURRENT_IRQ
HOST_SYNC --> HANDLE_CURRENT_SYNC
INVALID --> INVALID_EXCP
INVALID_EXCP --> INVALID_HANDLER
Sources: src/exception.S(L71 - L101)
Handler Macro Implementations
Each handler macro follows a consistent pattern:
- Alignment:
.p2align 7ensures 128-byte vector alignment - Context Save:
SAVE_REGS_FROM_EL1preserves processor state - Parameter Setup: Load appropriate parameters into registers
- Handler Call: Branch to the corresponding C handler function
- Return: Jump to common return path or let handler manage return
The HANDLE_LOWER_SYNC_VCPU and HANDLE_LOWER_IRQ_VCPU macros use parameterized calls to vmexit_trampoline with exception type constants {exception_sync} and {exception_irq}.
Sources: src/exception.S(L87 - L101)
VM Entry and Exit Points
The context_vm_entry function provides the entry point for transitioning from host context back to guest execution, implementing the counterpart to exception-based VM exits.
sequenceDiagram
participant HostHypervisor as "Host/Hypervisor"
participant context_vm_entry as "context_vm_entry"
participant RESTORE_REGS_INTO_EL1 as "RESTORE_REGS_INTO_EL1"
participant GuestVM as "Guest VM"
HostHypervisor ->> context_vm_entry: Call with host_stack_top in x0
context_vm_entry ->> context_vm_entry: Set sp = x0 (host_stack_top)
context_vm_entry ->> context_vm_entry: Adjust sp to TrapFrame base
context_vm_entry ->> RESTORE_REGS_INTO_EL1: Execute macro
RESTORE_REGS_INTO_EL1 ->> RESTORE_REGS_INTO_EL1: Restore elr_el2, spsr_el2
RESTORE_REGS_INTO_EL1 ->> RESTORE_REGS_INTO_EL1: Restore sp_el0
RESTORE_REGS_INTO_EL1 ->> RESTORE_REGS_INTO_EL1: Restore GPRs x0-x29
RESTORE_REGS_INTO_EL1 ->> RESTORE_REGS_INTO_EL1: Adjust sp back to host_stack_top
RESTORE_REGS_INTO_EL1 ->> GuestVM: eret instruction
Note over GuestVM: Guest execution resumes
GuestVM -->> context_vm_entry: Exception occurs
Note over context_vm_entry: Exception vector called
context_vm_entry ->> HostHypervisor: Eventually returns to host
Sources: src/exception.S(L132 - L141)
The VM entry process:
- Stack Setup: Sets stack pointer to
host_stack_topaddress passed inx0 - Frame Positioning: Adjusts stack pointer to point to guest
TrapFramebase - Register Restoration: Uses
RESTORE_REGS_INTO_EL1to restore guest state - Guest Entry: Executes
eretto return to guest execution at EL1
The .Lexception_return_el2 label provides a common return path used by all exception handlers, ensuring consistent state restoration regardless of the exception type or handler complexity.
Sources: src/exception.S(L138 - L141)
Exception Analysis and Utilities
Relevant source files
This document covers the exception analysis and parsing utilities provided by the exception_utils.rs module. These utilities form a critical layer between the low-level assembly exception vectors and high-level exception handlers, providing functions to extract and interpret information from AArch64 exception syndrome registers, perform address translations, and manage register context during exception handling.
For information about the assembly exception vectors that capture exceptions, see Assembly Exception Vectors. For details about high-level exception dispatch and handling logic, see High-Level Exception Handling.
Exception Syndrome Register Analysis
The exception analysis system provides a comprehensive interface for reading and interpreting the Exception Syndrome Register (ESR_EL2), which contains detailed information about the cause and nature of exceptions that occur during guest execution.
Exception Syndrome Register Reading Functions
flowchart TD
subgraph subGraph1["Exception Classification"]
EC["Exception Class (EC)"]
ISS["Instruction Specific Syndrome (ISS)"]
IL["Instruction Length (IL)"]
end
subgraph subGraph0["Core Reading Functions"]
exception_esr["exception_esr()"]
exception_class["exception_class()"]
exception_class_value["exception_class_value()"]
exception_iss["exception_iss()"]
end
ESR_EL2["ESR_EL2 Hardware Register"]
DataAbort["Data Abort Analysis"]
SystemReg["System Register Analysis"]
HVC["HVC Call Analysis"]
EC --> DataAbort
EC --> HVC
EC --> SystemReg
ESR_EL2 --> exception_class
ESR_EL2 --> exception_class_value
ESR_EL2 --> exception_esr
ESR_EL2 --> exception_iss
ISS --> DataAbort
ISS --> SystemReg
exception_class --> EC
exception_esr --> IL
exception_iss --> ISS
The system provides several core functions for accessing different fields of the ESR_EL2 register:
exception_esr()src/exception_utils.rs(L12 - L14) returns the complete ESR_EL2 register valueexception_class()src/exception_utils.rs(L21 - L23) extracts the Exception Class field as an enum valueexception_class_value()src/exception_utils.rs(L30 - L32) extracts the Exception Class as a raw numeric valueexception_iss()src/exception_utils.rs(L170 - L172) extracts the Instruction Specific Syndrome field
Sources: src/exception_utils.rs(L7 - L32) src/exception_utils.rs(L165 - L172)
Instruction Analysis
The system provides utilities for analyzing instruction-related information from exceptions:
| Function | Purpose | Return Value |
|---|---|---|
| exception_instruction_length() | Determines if instruction is 16-bit or 32-bit | 0 for 16-bit, 1 for 32-bit |
| exception_next_instruction_step() | Calculates step size for instruction pointer advancement | 2 for 16-bit, 4 for 32-bit |
Sources: src/exception_utils.rs(L144 - L163)
Fault Address Translation System
The fault address translation system handles the complex process of converting virtual fault addresses to physical addresses using the ARM Address Translation (AT) instructions and register coordination.
Address Translation Flow
flowchart TD
subgraph subGraph2["Final Address Calculation"]
Page_Offset["FAR & 0xfff"]
Page_Number["HPFAR << 8"]
Final_GPA["GuestPhysAddr"]
end
subgraph subGraph1["Translation Process"]
arm_at["arm_at!(s1e1r, far)"]
translate_far_to_hpfar["translate_far_to_hpfar()"]
par_to_far["par_to_far() helper"]
exception_hpfar["exception_hpfar()"]
end
subgraph subGraph0["Translation Decision Logic"]
S1PTW_Check["Check ESR_ELx_S1PTW bit"]
Permission_Check["exception_data_abort_is_permission_fault()"]
Translation_Needed["Translation Needed?"]
end
FAR_EL2["FAR_EL2: Fault Address Register"]
HPFAR_EL2["HPFAR_EL2: Hypervisor Physical Fault Address Register"]
PAR_EL1["PAR_EL1: Physical Address Register"]
FAR_EL2 --> Page_Offset
FAR_EL2 --> S1PTW_Check
PAR_EL1 --> par_to_far
Page_Number --> Final_GPA
Page_Offset --> Final_GPA
Permission_Check --> Translation_Needed
S1PTW_Check --> Permission_Check
Translation_Needed --> exception_hpfar
Translation_Needed --> translate_far_to_hpfar
arm_at --> PAR_EL1
exception_hpfar --> Page_Number
par_to_far --> Page_Number
translate_far_to_hpfar --> arm_at
The exception_fault_addr() function src/exception_utils.rs(L133 - L142) implements a sophisticated address translation algorithm that:
- Reads the FAR_EL2 register to get the virtual fault address
- Determines whether address translation is needed based on the S1PTW bit and fault type
- Either performs address translation using
translate_far_to_hpfar()or directly reads HPFAR_EL2 - Combines the page offset from FAR_EL2 with the page number from HPFAR_EL2
The translate_far_to_hpfar() function src/exception_utils.rs(L92 - L113) uses the ARM Address Translation instruction to convert a virtual address to physical, handling the PAR_EL1 register state and error conditions.
Sources: src/exception_utils.rs(L34 - L142)
Data Abort Exception Analysis
The system provides comprehensive analysis capabilities for data abort exceptions, which are among the most common exceptions in virtualization scenarios involving MMIO operations.
Data Abort Analysis Functions
flowchart TD
subgraph subGraph1["Access Pattern Analysis"]
exception_data_abort_access_is_write["exception_data_abort_access_is_write()"]
exception_data_abort_access_width["exception_data_abort_access_width()"]
exception_data_abort_access_reg["exception_data_abort_access_reg()"]
exception_data_abort_access_reg_width["exception_data_abort_access_reg_width()"]
exception_data_abort_access_is_sign_ext["exception_data_abort_access_is_sign_ext()"]
end
subgraph subGraph0["Data Abort Classification"]
exception_data_abort_is_permission_fault["exception_data_abort_is_permission_fault()"]
exception_data_abort_is_translate_fault["exception_data_abort_is_translate_fault()"]
exception_data_abort_handleable["exception_data_abort_handleable()"]
end
ESR_ISS["ESR_EL2.ISS Field"]
ESR_ISS --> exception_data_abort_access_is_sign_ext
ESR_ISS --> exception_data_abort_access_is_write
ESR_ISS --> exception_data_abort_access_reg
ESR_ISS --> exception_data_abort_access_reg_width
ESR_ISS --> exception_data_abort_access_width
ESR_ISS --> exception_data_abort_handleable
ESR_ISS --> exception_data_abort_is_permission_fault
ESR_ISS --> exception_data_abort_is_translate_fault
The data abort analysis functions extract specific information from the ISS field of ESR_EL2:
| Function | Bit Fields | Purpose |
|---|---|---|
| exception_data_abort_is_permission_fault() | ISS[5:0] & 0xF0 == 12 | Identifies permission faults vs translation faults |
| exception_data_abort_is_translate_fault() | ISS[5:0] & 0xF0 == 4 | Identifies translation faults |
| exception_data_abort_access_is_write() | ISS[6] | Determines read vs write access |
| exception_data_abort_access_width() | ISS[23:22] | Access width (1, 2, 4, or 8 bytes) |
| exception_data_abort_access_reg() | ISS[20:16] | Register index (0-31) |
| exception_data_abort_handleable() | ISS[24] | !ISS[10] | Determines if abort can be handled |
Sources: src/exception_utils.rs(L197 - L255)
System Register Access Analysis
The system provides specialized parsing for system register access exceptions, which occur when guests attempt to access system registers that require hypervisor intervention.
System Register Parsing Functions
flowchart TD
subgraph subGraph1["Parsing Functions"]
exception_sysreg_direction_write["exception_sysreg_direction_write()"]
exception_sysreg_gpr["exception_sysreg_gpr()"]
exception_sysreg_addr["exception_sysreg_addr()"]
end
subgraph subGraph0["System Register ISS Format"]
ISS_Field["ESR_EL2.ISS[24:0]"]
Op0["Op0[21:20]"]
Op2["Op2[19:17]"]
Op1["Op1[16:14]"]
CRn["CRn[13:10]"]
CRm["CRm[4:1]"]
Direction["Direction[0]"]
RT["RT[9:5]"]
end
CRm --> exception_sysreg_addr
CRn --> exception_sysreg_addr
Direction --> exception_sysreg_direction_write
ISS_Field --> CRm
ISS_Field --> CRn
ISS_Field --> Direction
ISS_Field --> Op0
ISS_Field --> Op1
ISS_Field --> Op2
ISS_Field --> RT
Op0 --> exception_sysreg_addr
Op1 --> exception_sysreg_addr
Op2 --> exception_sysreg_addr
RT --> exception_sysreg_gpr
The system register analysis functions provide:
exception_sysreg_direction_write()src/exception_utils.rs(L175 - L178) determines if the access is a write (true) or read (false)exception_sysreg_gpr()src/exception_utils.rs(L181 - L186) extracts the general-purpose register index (RT field)exception_sysreg_addr()src/exception_utils.rs(L192 - L195) constructs the system register address from encoding fields
The exception_sysreg_addr() function implements the ARMv8 system register encoding format, combining Op0, Op1, Op2, CRn, and CRm fields into a unique register identifier.
Sources: src/exception_utils.rs(L174 - L195)
Register Context Management Macros
The system provides assembly macros for managing host register context during exception handling, ensuring proper preservation and restoration of callee-saved registers.
Context Switching Macros
flowchart TD
subgraph subGraph1["Exception Flow Integration"]
VM_Entry["VM Entry from Aarch64VCpu::run()"]
VM_Exit["VM Exit to Exception Handler"]
Return_to_Host["Return to Aarch64VCpu::run()"]
end
subgraph subGraph0["Host Context Management"]
Host_Registers["Host Callee-Saved Registersx19-x30"]
Stack_Frame["Stack Frame12 * 8 bytes"]
save_regs_to_stack["save_regs_to_stack!"]
restore_regs_from_stack["restore_regs_from_stack!"]
end
Exception_Processing["Exception Processing"]
Exception_Processing --> restore_regs_from_stack
Host_Registers --> Return_to_Host
Host_Registers --> Stack_Frame
Stack_Frame --> Host_Registers
VM_Entry --> save_regs_to_stack
VM_Exit --> Exception_Processing
restore_regs_from_stack --> Stack_Frame
save_regs_to_stack --> Host_Registers
The register context management system consists of two complementary macros:
save_regs_to_stack! src/exception_utils.rs(L278 - L289) :
- Allocates 96 bytes (12 * 8) on the stack
- Saves registers x19-x30 using
stp(store pair) instructions - Used during VM entry to preserve host state
restore_regs_from_stack! src/exception_utils.rs(L300 - L311) :
- Restores registers x19-x30 using
ldp(load pair) instructions - Deallocates the 96-byte stack frame
- Used during VM exit to restore host state
These macros ensure that the host's callee-saved registers are properly preserved across guest execution, maintaining the calling convention for the Aarch64VCpu::run() function.
Sources: src/exception_utils.rs(L267 - L311)
Integration with Exception Handling Pipeline
The exception utilities integrate seamlessly with the broader exception handling system, providing the analytical foundation for exception dispatch and handling decisions.
Utility Function Usage Pattern
flowchart TD
subgraph subGraph2["Exception Handlers"]
handle_data_abort["handle_data_abort()"]
handle_system_register["handle_system_register()"]
handle_psci_call["handle_psci_call()"]
end
subgraph subGraph1["Exception Type Specific Analysis"]
Data_Abort["Data Abort Functions"]
System_Register["System Register Functions"]
HVC_Analysis["HVC Call Analysis"]
end
subgraph subGraph0["Exception Analysis Phase"]
exception_esr["exception_esr()"]
exception_class["exception_class()"]
exception_fault_addr["exception_fault_addr()"]
end
Exception_Vector["Assembly Exception Vector"]
Exception_Context["Exception Context Capture"]
Data_Abort --> exception_fault_addr
Data_Abort --> handle_data_abort
Exception_Context --> exception_esr
Exception_Vector --> Exception_Context
HVC_Analysis --> handle_psci_call
System_Register --> handle_system_register
exception_class --> Data_Abort
exception_class --> HVC_Analysis
exception_class --> System_Register
exception_esr --> exception_class
The utilities serve as the foundational layer that enables higher-level exception handlers to make informed decisions about how to process different types of exceptions, providing both classification and detailed analysis capabilities.
Sources: src/exception_utils.rs(L1 - L311)
High-Level Exception Handling
Relevant source files
This document covers the high-level exception dispatch logic and handler implementations that process exceptions after they have been captured by the assembly exception vectors and analyzed by the parsing utilities. The high-level handlers determine the appropriate response to each exception type and return structured exit reasons to the VCPU management layer.
For information about the low-level assembly exception vector table and context switching, see Assembly Exception Vectors. For details about register parsing and exception analysis utilities, see Exception Analysis and Utilities.
Exception Dispatch Architecture
The high-level exception handling system centers around the handle_exception_sync function, which serves as the main dispatcher for synchronous exceptions from guest VMs. This function examines the Exception Class (EC) field in ESR_EL2 to determine the exception type and routes it to the appropriate specialized handler.
Exception Dispatch Flow
flowchart TD A["handle_exception_sync(ctx: &mut TrapFrame)"] B["exception_class()"] C["Exception Class"] D["handle_data_abort()"] E["handle_psci_call()"] F["Return CpuUp/CpuDown/SystemDown"] G["Return Hypercall"] H["handle_system_register()"] I["handle_smc64_exception()"] J["panic!()"] K["AxVCpuExitReason::MmioRead/Write"] L["AxVCpuExitReason::SysRegRead/Write"] M["AxVCpuExitReason::Nothing or PSCI"] N["Return to Aarch64VCpu::run()"] A --> B B --> C C --> D C --> E C --> H C --> I C --> J D --> K E --> F E --> G F --> N G --> N H --> L I --> M K --> N L --> N M --> N
Exception Class Mapping:
| Exception Class | Handler Function | Exit Reason |
|---|---|---|
| DataAbortLowerEL | handle_data_abort | MmioRead/MmioWrite |
| HVC64 | handle_psci_callor hypercall | CpuUp/CpuDown/SystemDown/Hypercall |
| TrappedMsrMrs | handle_system_register | SysRegRead/SysRegWrite |
| SMC64 | handle_smc64_exception | Nothingor PSCI |
Sources: src/exception.rs(L72 - L126)
Data Abort Handler
The handle_data_abort function processes memory access exceptions from guest VMs, typically resulting from MMIO operations to unmapped or restricted memory regions.
Data Abort Processing Logic
flowchart TD A["handle_data_abort(context_frame)"] B["exception_fault_addr()"] C["exception_data_abort_access_width()"] D["exception_data_abort_access_is_write()"] E["exception_data_abort_access_reg()"] F["Fault Analysis"] G["exception_data_abort_handleable()"] H["panic!()"] I["exception_data_abort_is_translate_fault()"] J["exception_data_abort_is_permission_fault()"] K["Return AxError::Unsupported"] L["panic!()"] M["Write Access?"] N["AxVCpuExitReason::MmioWrite"] O["AxVCpuExitReason::MmioRead"] P["Extract data from context_frame.gpr[reg]"] Q["Return reg and reg_width for host to fill"] A --> B A --> C A --> D A --> E B --> F C --> F D --> F E --> F F --> G G --> H G --> I I --> J I --> M J --> K J --> L M --> N M --> O N --> P O --> Q
The handler extracts key information from the exception:
- Fault Address: Guest physical address that caused the exception
- Access Width: Size of the memory operation (1, 2, 4, or 8 bytes)
- Direction: Read or write operation
- Register: Which guest register was involved in the access
Sources: src/exception.rs(L128 - L182) src/exception_utils.rs(L211 - L254)
System Register Handler
The handle_system_register function processes trapped system register accesses when the guest VM attempts to read or write control registers that are managed by the hypervisor.
System Register Access Processing
flowchart TD A["handle_system_register(context_frame)"] B["ESR_EL2.read(ESR_EL2::ISS)"] C["exception_sysreg_addr(iss)"] D["exception_sysreg_direction_write(iss)"] E["exception_sysreg_gpr(iss)"] F["Write Access?"] G["AxVCpuExitReason::SysRegWrite"] H["AxVCpuExitReason::SysRegRead"] I["Extract value from context_frame.gpr[reg]"] J["Return reg for host to populate"] K["Advance exception_pc by instruction step"] A --> B B --> C B --> D B --> E C --> F D --> F E --> F F --> G F --> H G --> I H --> J I --> K J --> K
The system register address encoding follows the ARM ISS format: <op0><op2><op1><CRn>00000<CRm>0, allowing the hypervisor to identify exactly which system register the guest attempted to access.
Sources: src/exception.rs(L184 - L211) src/exception_utils.rs(L174 - L195)
PSCI Support Implementation
The Power State Coordination Interface (PSCI) support enables guest VMs to perform power management operations like CPU power on/off and system shutdown through standardized function calls.
PSCI Call Handling
flowchart TD A["handle_psci_call(ctx)"] B["Check ctx.gpr[0] function ID"] C["Function Range"] D["32-bit PSCI range"] E["64-bit PSCI range"] F["Return None (not PSCI)"] G["Function Offset"] H["AxVCpuExitReason::CpuDown"] I["AxVCpuExitReason::CpuUp"] J["AxVCpuExitReason::SystemDown"] K["Return None (forward to ATF)"] L["Extract state from ctx.gpr[1]"] M["Extract target_cpu, entry_point, arg"] N["No additional parameters"] O["target_cpu: ctx.gpr[1]entry_point: GuestPhysAddr(ctx.gpr[2])arg: ctx.gpr[3]"] A --> B B --> C C --> D C --> E C --> F D --> G E --> G G --> H G --> I G --> J G --> K H --> L I --> M J --> N M --> O
PSCI Function Support:
| Function ID | Name | Parameters | Exit Reason |
|---|---|---|---|
| 0x2 | CPU_OFF | state | CpuDown |
| 0x3 | CPU_ON | target_cpu,entry_point,arg | CpuUp |
| 0x8 | SYSTEM_OFF | None | SystemDown |
| 0x0, 0x5, 0x9 | Version/Migrate/Reset | Various | Forwarded to ATF |
Sources: src/exception.rs(L213 - L254)
SMC Handler and ATF Integration
The handle_smc64_exception function processes Secure Monitor Calls (SMC), which are used to communicate with secure firmware like ARM Trusted Firmware (ATF).
SMC Processing Flow
flowchart TD A["handle_smc64_exception(ctx)"] B["handle_psci_call(ctx)"] C["Return PSCI exit reason"] D["Forward to ATF"] E["crate::smc::smc_call()"] F["Update ctx.gpr[0-3] with ATF response"] G["AxVCpuExitReason::Nothing"] H["Advance exception_pc"] A --> B B --> C B --> D C --> H D --> E E --> F F --> G G --> H
The SMC handler first checks if the call is a PSCI operation. If not, it forwards the call directly to the ARM Trusted Firmware using the smc_call function, which executes the actual SMC instruction and returns the results to the guest VM registers.
Sources: src/exception.rs(L256 - L271)
Current EL Exception Handlers
The system also handles exceptions that occur at the current exception level (EL2) while the hypervisor itself is running.
Current EL Handler Architecture
flowchart TD A["Current EL Exceptions"] B["Exception Type"] C["current_el_irq_handler(tf)"] D["current_el_sync_handler(tf)"] E["crate::pcpu::IRQ_HANDLER.current_ref_raw()"] F["Dispatch to host OS IRQ handler"] G["panic!() with TrapFrame details"] H["Return to hypervisor execution"] I["System halt"] A --> B B --> C B --> D C --> E D --> G E --> F F --> H G --> I
The IRQ handler delegates to the host OS interrupt handler registered during Aarch64PerCpu::new(), while synchronous exceptions at EL2 are considered fatal errors and cause a system panic.
Sources: src/exception.rs(L273 - L290)
VM Exit Trampoline Mechanism
The vmexit_trampoline function provides the critical bridge between guest exception handling and returning control to the host VCPU management code.
Stack Layout and Transition
flowchart TD A["Guest Exception Occurs"] B["SAVE_REGS_FROM_EL1 macro saves to stack"] C["Stack pointer at TrapFrame base"] D["vmexit_trampoline()"] E["add x9, sp, 34 * 8 // Skip TrapFrame"] F["ldr x10, [x9] // Load host_stack_top"] G["mov sp, x10 // Restore host stack"] H["restore_regs_from_stack!()"] I["ret // Return to Aarch64VCpu::run()"] J["Host continues with AxVCpuExitReason"] A --> B B --> C C --> D D --> E E --> F F --> G G --> H H --> I I --> J
Memory Layout During VM Exit:
Stack Layout:
┌─────────────────┐ ← sp (initially)
│ TrapFrame │ (34 * 8 bytes)
│ (guest regs) │
├─────────────────┤ ← sp + 34*8
│ host_stack_top │ (pointer to host stack)
├─────────────────┤
│ Host saved regs │ (callee-saved x19-x30)
│ │
└─────────────────┘ ← host_stack_top
Sources: src/exception.rs(L292 - L341)
Integration with VCPU Lifecycle
The high-level exception handlers integrate seamlessly with the VCPU execution cycle by returning structured AxVCpuExitReason values that inform the host hypervisor about what action triggered the VM exit.
Exit Reason Types and Usage
| Exit Reason | Triggered By | Host Action Required |
|---|---|---|
| MmioRead/MmioWrite | Guest MMIO access | Emulate device operation |
| SysRegRead/SysRegWrite | Trapped system register | Emulate register access |
| Hypercall | Guest HVC instruction | Process hypercall |
| CpuUp/CpuDown | PSCI power management | Manage CPU state |
| SystemDown | PSCI system shutdown | Shutdown guest system |
| Nothing | Handled internally | Continue guest execution |
This structured approach allows the host hypervisor to implement device emulation, power management, and other virtualization services based on the specific needs indicated by each exit reason.
Sources: src/exception.rs(L49 - L125)
System Integration
Relevant source files
This document covers the integration interfaces and hardware abstraction mechanisms that enable the arm_vcpu hypervisor to work with host systems, hardware platforms, and external components. It focuses on how the core virtualization functionality interfaces with the broader hypervisor ecosystem and underlying hardware.
For detailed information about the Secure Monitor Call interface, see Secure Monitor Interface. For platform-specific hardware support details, see Hardware Abstraction and Platform Support.
Integration Architecture Overview
The arm_vcpu crate serves as a hardware-specific implementation that integrates with higher-level hypervisor frameworks through well-defined interfaces. The integration occurs at multiple layers, from hardware abstraction to external crate dependencies.
Integration Architecture with Code Entities
flowchart TD
subgraph subGraph3["Hardware/Firmware Interface"]
SMC_IF["smc.rssmc_call()"]
HW_DETECT["Hardware ExtensionDetection"]
SECURE_FW["Secure Firmware(ATF/EL3)"]
end
subgraph subGraph2["External Crate Dependencies"]
AXVCPU_CRATE["axvcpu crateCore VCPU traits"]
AXADDR_CRATE["axaddrspace crateAddress space mgmt"]
PERCPU_CRATE["percpu cratePer-CPU storage"]
AARCH64_CRATE["aarch64-cpu crateRegister definitions"]
end
subgraph subGraph1["arm_vcpu Public Interface"]
LIB["lib.rs"]
VCPU_EXPORT["Aarch64VCpu"]
PCPU_EXPORT["Aarch64PerCpu"]
CONFIG_EXPORT["Aarch64VCpuCreateConfig"]
TRAPFRAME_EXPORT["TrapFrame"]
HW_SUPPORT["has_hardware_support()"]
end
subgraph subGraph0["Host Hypervisor System"]
HOST["Host OS/Hypervisor"]
HAL_TRAIT["AxVCpuHal trait(from axvcpu crate)"]
end
HAL_TRAIT --> VCPU_EXPORT
HOST --> HAL_TRAIT
HW_SUPPORT --> HW_DETECT
LIB --> AARCH64_CRATE
LIB --> CONFIG_EXPORT
LIB --> HW_SUPPORT
LIB --> PCPU_EXPORT
LIB --> TRAPFRAME_EXPORT
LIB --> VCPU_EXPORT
PCPU_EXPORT --> PERCPU_CRATE
SMC_IF --> SECURE_FW
VCPU_EXPORT --> AXADDR_CRATE
VCPU_EXPORT --> AXVCPU_CRATE
The integration architecture centers around the public interface defined in src/lib.rs which exports the core components that host hypervisors use to manage AArch64 virtual CPUs.
Sources: src/lib.rs(L17 - L18) src/lib.rs(L21) src/lib.rs(L24 - L32)
Hardware Abstraction Layer Interface
The arm_vcpu crate implements hardware-specific functionality while depending on abstract interfaces for integration with higher-level hypervisor components. The primary abstraction mechanism is the AxVCpuHal trait from the axvcpu crate.
Hardware Abstraction Layer Structure
flowchart TD
subgraph subGraph2["Hardware Platform"]
VIRT_EXT["AArch64 VirtualizationExtensions (VHE/EL2)"]
CPU_REGS["aarch64-cpu crateRegister definitions"]
PLATFORM_HW["Platform HardwareFeatures"]
end
subgraph subGraph1["arm_vcpu Implementation"]
AARCH64_VCPU["Aarch64VCpuimplements AxVCpuHal"]
AARCH64_PCPU["Aarch64PerCpuPer-CPU management"]
HW_SUPPORT_FUNC["has_hardware_support()Platform detection"]
end
subgraph subGraph0["Abstract Interface Layer"]
AXVCPU_HAL["AxVCpuHal trait(axvcpu crate)"]
AXVCPU_CORE["AxVCpu core traits(axvcpu crate)"]
end
AARCH64_PCPU --> VIRT_EXT
AARCH64_VCPU --> AARCH64_PCPU
AARCH64_VCPU --> CPU_REGS
AXVCPU_CORE --> AARCH64_VCPU
AXVCPU_HAL --> AARCH64_VCPU
HW_SUPPORT_FUNC --> PLATFORM_HW
The hardware abstraction enables the arm_vcpu implementation to provide AArch64-specific virtualization functionality while remaining compatible with generic hypervisor frameworks that depend on the abstract AxVCpuHal interface.
Sources: src/lib.rs(L17 - L18) src/lib.rs(L24 - L32)
External Dependency Integration
The arm_vcpu crate integrates with several external crates that provide fundamental hypervisor infrastructure. Each dependency serves a specific role in the overall system architecture.
| Crate | Purpose | Integration Point |
|---|---|---|
| axvcpu | Core VCPU traits and interfaces | Aarch64VCpuimplementsAxVCpuHal |
| axaddrspace | Address space management | Used byAarch64VCpufor memory virtualization |
| percpu | Per-CPU data storage | Used byAarch64PerCpufor CPU-local state |
| aarch64-cpu | AArch64 register definitions | Used throughout for hardware register access |
| log | Logging infrastructure | Used for debug and error reporting |
External Dependency Flow
flowchart TD
subgraph subGraph1["External Crate APIs"]
AXVCPU_API["axvcpu::AxVCpuHalHardware abstraction"]
AXADDR_API["axaddrspaceMemory management"]
PERCPU_API["percpuPer-CPU storage"]
AARCH64_API["aarch64_cpuRegister access"]
LOG_API["log macrosdebug!, warn!, error!"]
end
subgraph subGraph0["arm_vcpu Components"]
VCPU_IMPL["src/vcpu.rsAarch64VCpu"]
PCPU_IMPL["src/pcpu.rsAarch64PerCpu"]
CONTEXT["src/context_frame.rsContext management"]
EXCEPTION["src/exception.rsException handling"]
end
CONTEXT --> AARCH64_API
EXCEPTION --> AARCH64_API
EXCEPTION --> LOG_API
PCPU_IMPL --> PERCPU_API
VCPU_IMPL --> AXADDR_API
VCPU_IMPL --> AXVCPU_API
The external dependencies provide the foundational infrastructure that the arm_vcpu implementation builds upon, allowing it to focus on AArch64-specific virtualization concerns.
Sources: src/lib.rs(L6 - L7) src/lib.rs(L17 - L18)
Platform Hardware Integration
The arm_vcpu crate provides platform hardware integration through hardware capability detection and secure firmware communication interfaces.
Hardware Integration Points
flowchart TD
subgraph subGraph2["Hardware Features"]
VIRT_EXT["AArch64 VirtualizationExtensions"]
EL2_MODE["Exception Level 2Hypervisor mode"]
SECURE_STATE["Secure/Non-secureState separation"]
end
subgraph subGraph1["Secure Communication"]
SMC_CALL["smc_call()smc.rs:10-26"]
SMC_ASM["smc #0Assembly instruction"]
ATF_EL3["ARM Trusted FirmwareEL3 Secure Monitor"]
end
subgraph subGraph0["Platform Detection"]
HAS_HW_SUPPORT["has_hardware_support()lib.rs:24-32"]
VHE_CHECK["ID_AA64MMFR1_EL1Virtualization Host Extensions"]
CORTEX_A78["Cortex-A78Example platform"]
end
HAS_HW_SUPPORT --> VHE_CHECK
HAS_HW_SUPPORT --> VIRT_EXT
SMC_ASM --> ATF_EL3
SMC_CALL --> SECURE_STATE
SMC_CALL --> SMC_ASM
VHE_CHECK --> CORTEX_A78
VIRT_EXT --> EL2_MODE
The platform integration provides two key capabilities:
- Hardware Capability Detection: The
has_hardware_support()function determines if the current platform supports AArch64 virtualization extensions - Secure Firmware Communication: The
smc_call()function enables communication with secure firmware components
Sources: src/lib.rs(L24 - L32) src/smc.rs(L3 - L26)
Integration Safety and Constraints
The integration interfaces include several safety-critical components that require careful handling by host hypervisor systems.
The smc_call() function in src/smc.rs(L10 - L26) is marked as unsafe and requires the caller to ensure:
x0contains a valid SMC function number per the SMC Calling Convention- Arguments
x1,x2,x3are valid for the specified SMC function - The calling context is appropriate for secure monitor calls
The hardware support detection in src/lib.rs(L24 - L32) currently returns true by default but includes documentation for implementing proper detection using ID_AA64MMFR1_EL1 register checks on platforms like Cortex-A78.
These safety constraints ensure that the arm_vcpu crate integrates properly with both hardware platforms and secure firmware components while maintaining the security boundaries required for hypervisor operation.
Sources: src/smc.rs(L5 - L9) src/lib.rs(L25 - L30)
Secure Monitor Interface
Relevant source files
This document covers the Secure Monitor Call (SMC) interface implementation in the ARM vCPU hypervisor. The SMC interface provides a mechanism for the hypervisor to communicate with secure firmware, typically ARM Trusted Firmware (ATF), running at the secure monitor level (EL3). This interface is essential for implementing security features, power management operations, and other privileged system functions that require secure world access.
For information about how SMC exceptions from guest VMs are handled, see the exception handling documentation in High-Level Exception Handling.
ARM Security Architecture and SMC Role
The SMC interface operates within ARM's TrustZone security architecture, which divides the system into secure and non-secure worlds across different exception levels.
ARM Security Model with SMC Interface
flowchart TD
subgraph subGraph1["Non-Secure World"]
EL2_NS["EL2 Non-SecureHypervisorarm_vcpu"]
EL1_NS["EL1 Non-SecureGuest OSVirtual Machines"]
EL0_NS["EL0 Non-SecureGuest Applications"]
end
subgraph subGraph0["Secure World"]
EL3_S["EL3 Secure MonitorARM Trusted FirmwareSecure Boot, PSCI"]
EL1_S["EL1 SecureTrusted OSTEE Services"]
EL0_S["EL0 SecureTrusted Applications"]
end
EL0_NS --> EL1_NS
EL0_S --> EL1_S
EL1_NS --> EL2_NS
EL1_S --> EL3_S
EL2_NS --> EL3_S
EL3_S --> EL2_NS
Sources: src/smc.rs(L1 - L27)
SMC Call Implementation
The core SMC functionality is implemented through the smc_call function, which provides a direct interface to invoke secure monitor calls from the hypervisor.
SMC Call Function Interface
flowchart TD
subgraph subGraph2["Secure Monitor"]
EL3_HANDLER["EL3 SMC HandlerFunction DispatchService Implementation"]
RETURN["Return Valuesr0: Result/Statusr1-r3: Return Data"]
end
subgraph subGraph1["smc_call Function"]
UNSAFE["unsafe fn smc_call(x0, x1, x2, x3)"]
ASM["Inline Assemblysmc #0inout registers"]
end
subgraph subGraph0["Hypervisor Context"]
CALLER["Caller FunctionPSCI HandlerSecure Service Request"]
PARAMS["Parametersx0: Function IDx1-x3: Arguments"]
end
ASM --> EL3_HANDLER
CALLER --> PARAMS
EL3_HANDLER --> RETURN
PARAMS --> UNSAFE
RETURN --> CALLER
UNSAFE --> ASM
The smc_call function is marked as unsafe and #[inline(never)] to ensure proper handling of the security-sensitive operation:
| Parameter | Purpose | SMC Calling Convention |
|---|---|---|
| x0 | Function identifier | SMC function number per ARM SMC Calling Convention |
| x1-x3 | Function arguments | Service-specific parameters |
| Returnr0 | Status/result | Success/error code or primary return value |
| Returnr1-r3 | Additional data | Service-specific return values |
Sources: src/smc.rs(L3 - L26)
SMC Assembly Implementation
The actual secure monitor call is implemented using inline assembly with specific constraints to ensure safe execution.
SMC Assembly Execution Flow
flowchart TD
subgraph subGraph0["EL3 Processing"]
EL3_ENTRY["EL3 SMC VectorContext Save"]
EL3_DISPATCH["Function DispatchBased on x0"]
EL3_SERVICE["Service ImplementationPSCI, Secure Services"]
EL3_RETURN["Prepare Return ValuesLoad r0-r3"]
EL3_EXIT["Context RestoreWorld Switch to EL2"]
end
ENTRY["Function Entrysmc_call(x0, x1, x2, x3)"]
SETUP["Register SetupLoad input parametersinto x0-x3"]
SMC_INST["Execute SMC #0Secure Monitor CallWorld Switch to EL3"]
CAPTURE["Register CaptureExtract r0-r3from output registers"]
RETURN_TUPLE["Return Tuple(r0, r1, r2, r3)"]
CAPTURE --> RETURN_TUPLE
EL3_DISPATCH --> EL3_SERVICE
EL3_ENTRY --> EL3_DISPATCH
EL3_EXIT --> CAPTURE
EL3_RETURN --> EL3_EXIT
EL3_SERVICE --> EL3_RETURN
ENTRY --> SETUP
SETUP --> SMC_INST
SMC_INST --> EL3_ENTRY
The assembly implementation uses specific constraints:
inoutconstraints for registersx0-x3to handle both input and outputoptions(nomem, nostack)to indicate the SMC instruction doesn't access memory or modify the stack#[inline(never)]attribute to prevent inlining and ensure the SMC happens at the correct execution context
Sources: src/smc.rs(L15 - L25)
Integration with Exception Handling
When guest VMs attempt to execute SMC instructions, they are trapped by the hypervisor and can be handled through the exception system before potentially being forwarded to the secure monitor.
Guest SMC Exception Flow
flowchart TD
subgraph subGraph1["SMC Execution Paths"]
FORWARD_SMC["Forward via smc_callHypervisor → EL3"]
EMULATE_SMC["Emulate ResponseSynthetic Return Values"]
DENY_SMC["Deny OperationInject Exception to Guest"]
end
subgraph subGraph0["SMC Decision Logic"]
POLICY_CHECK["Security Policy CheckFunction ID Validation"]
FORWARD_DECISION["Forward to EL3?"]
EMULATE_DECISION["Emulate in EL2?"]
end
GUEST_SMC["Guest VMExecutes SMC"]
TRAP["HCR_EL2.TSC TrapException to EL2"]
EXC_HANDLER["handle_smc64_exceptionException Analysis"]
RETURN_GUEST["Return to GuestWith Results"]
DENY_SMC --> RETURN_GUEST
EMULATE_DECISION --> EMULATE_SMC
EMULATE_SMC --> RETURN_GUEST
EXC_HANDLER --> POLICY_CHECK
FORWARD_DECISION --> DENY_SMC
FORWARD_DECISION --> FORWARD_SMC
FORWARD_SMC --> RETURN_GUEST
GUEST_SMC --> TRAP
POLICY_CHECK --> EMULATE_DECISION
POLICY_CHECK --> FORWARD_DECISION
TRAP --> EXC_HANDLER
Sources: Based on system architecture patterns referenced in overview diagrams
Security Considerations and Trust Boundaries
The SMC interface represents a critical trust boundary in the system, requiring careful consideration of security implications.
Trust Boundary and Attack Surface
flowchart TD
subgraph subGraph2["Secure Monitor Domain"]
EL3_MONITOR["ARM Trusted FirmwareEL3 Secure MonitorRoot of Trust"]
SECURE_SERVICES["Secure ServicesPSCI, Crypto, Boot"]
end
subgraph subGraph1["Hypervisor Trust Domain"]
HYPERVISOR["arm_vcpu HypervisorEL2 Non-SecureTrusted Compute Base"]
SMC_FILTER["SMC Policy EngineFunction ID FilteringParameter Validation"]
SMC_CALL["smc_call FunctionDirect EL3 Interface"]
end
subgraph subGraph0["Untrusted Domain"]
GUEST_VM["Guest VMsPotentially MaliciousEL1/EL0 Non-Secure"]
GUEST_SMC["Guest SMC CallsTrapped and Filtered"]
end
EL3_MONITOR --> SECURE_SERVICES
GUEST_SMC --> SMC_FILTER
GUEST_VM --> GUEST_SMC
HYPERVISOR --> SMC_CALL
SMC_CALL --> EL3_MONITOR
SMC_FILTER --> SMC_CALL
Security Properties
| Property | Implementation | Rationale |
|---|---|---|
| Function ID validation | Policy checking before forwarding | Prevents unauthorized access to secure services |
| Parameter sanitization | Input validation in exception handlers | Mitigates parameter injection attacks |
| Direct hypervisor access | smc_callfunction for hypervisor use | Enables privileged operations like PSCI |
| Guest call filtering | Exception trapping and analysis | Maintains security boundaries between VMs |
Sources: src/smc.rs(L6 - L9) for safety documentation
Hardware Abstraction and Platform Support
Relevant source files
This document covers the hardware abstraction mechanisms and platform support capabilities in the arm_vcpu hypervisor implementation. It explains how the system abstracts AArch64 virtualization hardware features, detects platform capabilities, and integrates with host operating systems through well-defined interfaces.
For information about the specific per-CPU state management implementation, see Per-CPU State Management. For details about secure monitor integration, see Secure Monitor Interface.
Purpose and Scope
The hardware abstraction layer serves as the bridge between the generic virtualization logic and platform-specific implementations. It provides a clean interface for host operating systems to integrate the arm_vcpu hypervisor while isolating hardware-specific details from the core virtualization algorithms.
Hardware Abstraction Layer Design
The system uses the AxVCpuHal trait as the primary abstraction mechanism. This trait defines the interface that host systems must implement to provide platform-specific services to the hypervisor.
HAL Integration Architecture
flowchart TD
subgraph subGraph3["AArch64 Hardware"]
CPU_FEATURES["ID_AA64MMFR1_EL1Feature Register"]
VIRT_REGS["HCR_EL2VBAR_EL2Virtualization Registers"]
EL2_MODE["Exception Level 2Hypervisor Mode"]
end
subgraph subGraph2["Hardware Interface Layer"]
HW_DETECT["has_hardware_support()"]
REG_ACCESS["AArch64 Register Access"]
VIRT_EXT["Virtualization Extensions"]
end
subgraph subGraph1["arm_vcpu Hardware Abstraction"]
HAL_TRAIT["AxVCpuHal Trait"]
PCPU_GENERIC["Aarch64PerCpu"]
VCPU_GENERIC["Aarch64VCpu"]
end
subgraph subGraph0["Host Operating System Layer"]
HOST_OS["Host OS Implementation"]
HAL_IMPL["AxVCpuHal Implementation"]
end
HAL_IMPL --> HAL_TRAIT
HAL_TRAIT --> PCPU_GENERIC
HAL_TRAIT --> VCPU_GENERIC
HOST_OS --> HAL_IMPL
HW_DETECT --> CPU_FEATURES
PCPU_GENERIC --> HW_DETECT
PCPU_GENERIC --> REG_ACCESS
REG_ACCESS --> VIRT_REGS
VIRT_EXT --> EL2_MODE
Sources: src/pcpu.rs(L1 - L80) src/lib.rs(L1 - L33)
HAL Trait Interface
The AxVCpuHal trait provides the following key abstractions:
| Method | Purpose | Implementation Responsibility |
|---|---|---|
| irq_hanlder() | Host IRQ dispatch | Host OS provides IRQ routing logic |
| Platform services | Memory management, device access | Host OS provides platform-specific implementations |
The generic design allows the same hypervisor core to work across different host environments by parameterizing the per-CPU and VCPU structures with the HAL implementation.
Sources: src/pcpu.rs(L7 - L8) src/pcpu.rs(L32 - L43)
Platform Detection and Capabilities
Hardware Support Detection
The system provides platform capability detection through the has_hardware_support() function:
flowchart TD INIT["System Initialization"] DETECT["has_hardware_support()"] CHECK_REG["Read ID_AA64MMFR1_EL1"] VHE_CHECK["Check Virtualization Host Extensions"] CURRENT_IMPL["Return true (default)"] SUPPORTED["Platform Supported"] note1["Note: Currently simplifiedimplementation returns trueFuture: Parse feature register"] CHECK_REG --> VHE_CHECK CHECK_REG --> note1 CURRENT_IMPL --> SUPPORTED DETECT --> CHECK_REG INIT --> DETECT VHE_CHECK --> CURRENT_IMPL
Sources: src/lib.rs(L24 - L32)
Feature Register Analysis
The implementation includes provisions for proper hardware feature detection using AArch64 feature registers:
- ID_AA64MMFR1_EL1: Memory Model Feature Register for Virtualization Host Extensions detection
- Cortex-A78 Support: Specific mention of Cortex-A78 feature detection capabilities
- Future Extensibility: Framework for expanding platform detection logic
The current implementation uses a conservative approach by defaulting to supported status, but includes documentation for proper feature register parsing implementation.
Sources: src/lib.rs(L25 - L29)
Hardware Feature Management
Virtualization Hardware Control
The per-CPU implementation manages AArch64 virtualization hardware features through register manipulation:
flowchart TD
subgraph subGraph1["HCR_EL2 Configuration"]
VM_ENABLE["VM: Enable virtualization"]
RW_MODE["RW: EL1 is AArch64"]
IRQ_VIRT["IMO: Virtual IRQ enable"]
FIQ_VIRT["FMO: Virtual FIQ enable"]
SMC_TRAP["TSC: Trap SMC to EL2"]
end
subgraph subGraph0["Hardware Enable Sequence"]
SAVE_VBAR["Save Original VBAR_EL2ORI_EXCEPTION_VECTOR_BASE"]
SET_VBAR["Set VBAR_EL2 toexception_vector_base_vcpu"]
CONFIG_HCR["Configure HCR_EL2Virtualization Features"]
end
subgraph subGraph2["Hardware Disable Sequence"]
RESTORE_VBAR["Restore Original VBAR_EL2"]
DISABLE_VM["HCR_EL2.VM: Disable"]
end
CONFIG_HCR --> FIQ_VIRT
CONFIG_HCR --> IRQ_VIRT
CONFIG_HCR --> RW_MODE
CONFIG_HCR --> SMC_TRAP
CONFIG_HCR --> VM_ENABLE
RESTORE_VBAR --> DISABLE_VM
SAVE_VBAR --> SET_VBAR
SET_VBAR --> CONFIG_HCR
Sources: src/pcpu.rs(L49 - L78)
Register Management Implementation
The hardware abstraction manages critical AArch64 system registers:
| Register | Purpose | Management |
|---|---|---|
| VBAR_EL2 | Exception vector base | Saved/restored during enable/disable |
| HCR_EL2 | Hypervisor configuration | Configured with virtualization features |
| Per-CPU storage | IRQ handlers, original state | Managed through percpu crate |
The implementation ensures proper state management by saving original register values before modification and restoring them during hardware disable operations.
Sources: src/pcpu.rs(L53 - L57) src/pcpu.rs(L59 - L65) src/pcpu.rs(L74 - L76)
Integration with Host Systems
Per-CPU Integration Model
The system integrates with host operating systems through a per-CPU abstraction model:
flowchart TD
subgraph subGraph2["Hardware State"]
VIRT_ENABLED["Virtualization Enabled"]
EXCEPTION_VECTORS["Custom Exception Vectors"]
IRQ_ROUTING["IRQ Routing Active"]
end
subgraph subGraph1["arm_vcpu Per-CPU Layer"]
PCPU_NEW["Aarch64PerCpu::new(cpu_id)"]
IRQ_CELL["IRQ_HANDLER OnceCell"]
HW_ENABLE["hardware_enable()"]
HW_DISABLE["hardware_disable()"]
end
subgraph subGraph0["Host OS Integration Points"]
HOST_INIT["Host System Initialization"]
IRQ_REG["IRQ Handler Registration"]
PERCPU_SETUP["Per-CPU Data Setup"]
end
HOST_INIT --> PCPU_NEW
HW_DISABLE --> HOST_INIT
HW_ENABLE --> EXCEPTION_VECTORS
HW_ENABLE --> IRQ_ROUTING
HW_ENABLE --> VIRT_ENABLED
IRQ_REG --> IRQ_CELL
PERCPU_SETUP --> HW_ENABLE
Sources: src/pcpu.rs(L18 - L26) src/pcpu.rs(L33 - L43)
IRQ Handler Abstraction
The system provides IRQ handling abstraction through per-CPU handler registration:
- OnceCell Storage: Thread-safe per-CPU IRQ handler storage using
OnceCell<&(dyn Fn() + Send + Sync)> - Host Integration: Host OS registers IRQ handlers during per-CPU initialization
- HAL Dispatch: IRQ handlers are called through the
AxVCpuHal::irq_hanlder()interface
This design allows the hypervisor to route interrupts back to the host OS while maintaining isolation and type safety.
Sources: src/pcpu.rs(L21 - L26) src/pcpu.rs(L35 - L37)
Export Interface
The crate provides a clean public interface for host system integration:
| Export | Type | Purpose |
|---|---|---|
| Aarch64PerCpu | Generic struct | Per-CPU management implementation |
| Aarch64VCpu | Generic struct | Virtual CPU implementation |
| Aarch64VCpuCreateConfig | Configuration | VCPU creation parameters |
| TrapFrame | Type alias | Context frame for AArch64 |
| has_hardware_support() | Function | Platform capability detection |
These exports provide the complete interface needed for host systems to integrate the arm_vcpu hypervisor functionality.
Sources: src/lib.rs(L17 - L22) src/lib.rs(L24 - L32)
Overview
Relevant source files
This document provides an overview of the axvcpu crate, which serves as a virtual CPU abstraction layer for the ArceOS hypervisor. The crate provides architecture-independent VCPU management capabilities, comprehensive exit handling, and multi-platform support through a trait-based design.
For detailed information about VCPU lifecycle management and state transitions, see Core VCPU Management. For comprehensive coverage of VM exit handling and processing, see Exit Handling System. For implementation specifics including per-CPU state and hardware abstraction, see Implementation Details.
Purpose and Scope
The axvcpu crate implements a virtualization abstraction that enables hypervisors to manage virtual CPUs across different hardware architectures. It provides a unified interface for VCPU creation, execution control, exit handling, and resource management while abstracting away architecture-specific implementation details.
The crate operates in a no_std environment and integrates with the broader ArceOS hypervisor ecosystem through well-defined interfaces and dependency relationships.
Sources: src/lib.rs(L1 - L4) README.md(L1 - L3)
System Architecture
The following diagram illustrates the high-level architecture of the axvcpu system, showing how the main code entities relate to each other:
AxVCpu Core Architecture
flowchart TD
subgraph subGraph3["External Dependencies"]
AxErrno["axerrnoError Types"]
MemoryAddr["memory_addrAddress Abstractions"]
Percpu["percpuPer-CPU Variables"]
AxAddrspace["axaddrspaceAddress Space Management"]
end
subgraph subGraph2["Core Modules"]
VcpuMod["vcpu.rsVCPU Implementation"]
ExitMod["exit.rsExit Reason Definitions"]
ArchVcpuMod["arch_vcpu.rsArchitecture Interface"]
HalMod["hal.rsHardware Layer"]
PercpuMod["percpu.rsPer-CPU State"]
end
subgraph subGraph1["Abstraction Layer"]
AxArchVCpu["AxArchVCpuArchitecture Trait"]
AxVCpuHal["AxVCpuHalHardware Abstraction"]
AccessWidth["AccessWidthMemory Access Specification"]
end
subgraph subGraph0["Public API Layer"]
LibRS["lib.rsPublic Exports"]
AxVCpu["AxVCpuMain VCPU Manager"]
AxVCpuExitReason["AxVCpuExitReasonExit Event Types"]
end
AxArchVCpu --> ArchVcpuMod
AxVCpu --> VcpuMod
AxVCpuExitReason --> ExitMod
AxVCpuHal --> HalMod
HalMod --> AxAddrspace
LibRS --> AccessWidth
LibRS --> AxArchVCpu
LibRS --> AxErrno
LibRS --> AxVCpu
LibRS --> AxVCpuExitReason
LibRS --> AxVCpuHal
LibRS --> MemoryAddr
LibRS --> Percpu
VcpuMod --> ArchVcpuMod
VcpuMod --> ExitMod
VcpuMod --> PercpuMod
This diagram shows the modular design where the public API layer exposes the main abstractions (AxVCpu, AxVCpuExitReason, AxArchVCpu, AxVCpuHal) which are implemented by corresponding core modules, with clear dependency relationships to external crates.
Sources: src/lib.rs(L9 - L22) Cargo.toml(L7 - L12)
Core Component Overview
VCPU Management Components
| Component | Module | Purpose |
|---|---|---|
| AxVCpu | vcpu.rs | Primary VCPU abstraction managing lifecycle and execution |
| AxArchVCpu | arch_vcpu.rs | Architecture-specific trait for platform implementations |
| AxVCpuExitReason | exit.rs | Enumeration of all possible VM exit conditions |
| AxVCpuHal | hal.rs | Hardware abstraction layer for memory and interrupt management |
| Per-CPU state | percpu.rs | Management of per-CPU virtualization state |
The system follows a layered approach where the generic AxVCpu manager coordinates with architecture-specific implementations through the AxArchVCpu trait, while exit events are processed through the comprehensive AxVCpuExitReason enumeration.
Sources: src/lib.rs(L15 - L21)
VCPU Execution and Exit Flow
sequenceDiagram
participant HypervisorbrClientCode as "Hypervisor<br>Client Code"
participant AxVCpubrManager as "AxVCpu<br>Manager"
participant AxArchVCpubrArchitectureImpl as "AxArchVCpu<br>Architecture Impl"
participant AxVCpuExitReasonbrExitHandler as "AxVCpuExitReason<br>Exit Handler"
participant AxVCpuHalbrHardwareLayer as "AxVCpuHal<br>Hardware Layer"
HypervisorbrClientCode ->> AxVCpubrManager: "new() + setup()"
AxVCpubrManager ->> AxArchVCpubrArchitectureImpl: "create architecture instance"
AxVCpubrManager ->> AxVCpuHalbrHardwareLayer: "initialize hardware resources"
HypervisorbrClientCode ->> AxVCpubrManager: "run()"
AxVCpubrManager ->> AxArchVCpubrArchitectureImpl: "execute guest code"
alt "VM Exit Event"
AxArchVCpubrArchitectureImpl ->> AxVCpuExitReasonbrExitHandler: "exit event data"
AxVCpuExitReasonbrExitHandler ->> AxVCpubrManager: "processed exit reason"
AxVCpubrManager ->> HypervisorbrClientCode: "return exit information"
end
alt "Memory Access Required"
AxVCpubrManager ->> AxVCpuHalbrHardwareLayer: "handle memory operation"
AxVCpuHalbrHardwareLayer ->> AxVCpubrManager: "memory access result"
end
This sequence diagram demonstrates how the core components interact during VCPU execution, with the AxVCpu manager orchestrating between architecture-specific implementations and hardware abstraction layers to handle VM exits and memory operations.
Sources: src/lib.rs(L9 - L21)
Integration with ArceOS Ecosystem
The axvcpu crate serves as a foundational component in the ArceOS hypervisor stack, providing the essential virtual CPU management capabilities that higher-level hypervisor components depend on. It integrates with several key external dependencies:
axerrno: Provides standardized error handling across the hypervisormemory_addr: Offers memory address abstractions for guest physical addressingpercpu: Enables per-CPU variable management for hypervisor stateaxaddrspace: Provides address space management for guest memory virtualization
The crate's no_std design ensures compatibility with kernel-level hypervisor environments while maintaining portability across different host operating systems and hardware platforms.
Sources: Cargo.toml(L7 - L12) src/lib.rs(L4)
Core VCPU Management
Relevant source files
This document covers the central AxVCpu abstraction that provides the primary interface for virtual CPU management in the axvcpu crate. The AxVCpu struct serves as an architecture-independent wrapper that delegates platform-specific operations to architecture implementations while maintaining consistent state management and lifecycle control.
For detailed information about VCPU state transitions and lifecycle stages, see VCPU State Machine and Lifecycle. For architecture-specific implementation details, see Architecture Abstraction Layer. For information about handling VCPU exits and events, see Exit Handling System.
AxVCpu Structure and Components
The AxVCpu struct is designed as a generic wrapper that provides architecture-independent VCPU management by delegating specific operations to types implementing the AxArchVCpu trait.
Core Data Structure
classDiagram
class AxVCpu {
-AxVCpuInnerConst inner_const
-RefCell~AxVCpuInnerMut~ inner_mut
-UnsafeCell~A~ arch_vcpu
+new(id, favor_phys_cpu, phys_cpu_set, arch_config) AxResult~Self~
+setup(entry, ept_root, arch_config) AxResult
+run() AxResult~AxVCpuExitReason~
+bind() AxResult
+unbind() AxResult
+state() VCpuState
+id() usize
+is_bsp() bool
+set_gpr(reg, val)
}
class AxVCpuInnerConst {
-usize id
-usize favor_phys_cpu
-Option~usize~ phys_cpu_set
}
class AxVCpuInnerMut {
-VCpuState state
}
class VCpuState {
<<enumeration>>
Invalid
Created
Free
Ready
Running
Blocked
}
AxVCpu *-- AxVCpuInnerConst
AxVCpu *-- AxVCpuInnerMut
AxVCpuInnerMut *-- VCpuState
The AxVCpu structure separates concerns into three main components:
| Component | Purpose | Mutability |
|---|---|---|
| AxVCpuInnerConst | Immutable VCPU configuration (ID, CPU affinity) | Constant |
| AxVCpuInnerMut | Mutable state information managed throughRefCell | Interior mutable |
| arch_vcpu | Architecture-specific implementation viaUnsafeCell | Unsafe interior mutable |
Sources: src/vcpu.rs(L8 - L63)
Architecture Independence Design
The AxVCpu achieves architecture independence by using the AxArchVCpu trait as a type parameter, allowing the same interface to work with different CPU architectures.
Trait Integration Pattern
The AxArchVCpu trait defines the contract that architecture-specific implementations must fulfill:
| Method | Purpose | State Requirement |
|---|---|---|
| new() | Create new architecture VCPU | - |
| set_entry() | Set guest entry point | Before setup |
| set_ept_root() | Set memory translation root | Before setup |
| setup() | Complete VCPU initialization | After entry/EPT set |
| run() | Execute guest code | Running state |
| bind()/unbind() | CPU binding operations | Ready/Free transition |
Sources: src/arch_vcpu.rs(L6 - L44) src/vcpu.rs(L53 - L63)
VCPU Lifecycle Operations
The AxVCpu provides key lifecycle operations that manage VCPU state transitions and delegate to architecture-specific implementations.
Creation and Initialization Flow
Runtime Operations
The main runtime operations follow a consistent pattern of state validation and architecture delegation:
flowchart TD
subgraph subGraph1["Key Operations"]
Bind["bind(): Free → Ready"]
Run["run(): Ready → Running → Ready"]
Unbind["unbind(): Ready → Free"]
end
subgraph subGraph0["VCPU Operation Pattern"]
StateCheck["Check Current State"]
ArchDelegate["Delegate to AxArchVCpu"]
StateTransition["Update VCPU State"]
Result["Return Result"]
end
ArchDelegate --> StateTransition
Bind --> StateCheck
Run --> StateCheck
StateCheck --> ArchDelegate
StateTransition --> Result
Unbind --> StateCheck
Sources: src/vcpu.rs(L205 - L225) src/vcpu.rs(L86 - L99)
Current VCPU Management
The crate maintains per-CPU tracking of the currently executing VCPU using a percpu variable, enabling architecture implementations to access their containing AxVCpu instance.
Per-CPU VCPU Tracking
flowchart TD
subgraph subGraph2["Access Functions"]
GetCurrent["get_current_vcpu<A>()"]
SetCurrent["set_current_vcpu(vcpu)"]
ClearCurrent["clear_current_vcpu<A>()"]
end
subgraph subGraph1["Physical CPU 1"]
PCPU1["CURRENT_VCPU: Option<*mut u8>"]
VCPU1["AxVCpu instance"]
end
subgraph subGraph0["Physical CPU 0"]
PCPU0["CURRENT_VCPU: Option<*mut u8>"]
VCPU0["AxVCpu instance"]
end
ClearCurrent --> PCPU0
ClearCurrent --> PCPU1
GetCurrent --> PCPU0
GetCurrent --> PCPU1
PCPU0 --> VCPU0
PCPU1 --> VCPU1
SetCurrent --> PCPU0
SetCurrent --> PCPU1
The current VCPU mechanism provides:
| Function | Purpose | Safety |
|---|---|---|
| get_current_vcpu() | Get current VCPU reference | Safe |
| get_current_vcpu_mut() | Get mutable current VCPU reference | Safe |
| set_current_vcpu() | Set current VCPU pointer | Unsafe |
| clear_current_vcpu() | Clear current VCPU pointer | Unsafe |
The with_current_cpu_set method automatically manages current VCPU setup and cleanup:
sequenceDiagram
participant Caller as Caller
participant AxVCpu as AxVCpu
participant CURRENT_VCPU as "CURRENT_VCPU"
participant UserOperation as "User Operation"
Caller ->> AxVCpu: "with_current_cpu_set(closure)"
AxVCpu ->> CURRENT_VCPU: "check if already set"
alt Already Set
AxVCpu -->> AxVCpu: "panic!(nested operation)"
else Not Set
AxVCpu ->> CURRENT_VCPU: "set_current_vcpu(self)"
AxVCpu ->> UserOperation: "execute closure"
UserOperation -->> AxVCpu: "result"
AxVCpu ->> CURRENT_VCPU: "clear_current_vcpu()"
AxVCpu -->> Caller: "result"
end
Sources: src/vcpu.rs(L238 - L290) src/vcpu.rs(L164 - L180)
Interior Mutability Pattern
The AxVCpu uses a sophisticated interior mutability design to handle the different access patterns required for VCPU state and architecture-specific data.
Mutability Strategy
flowchart TD
subgraph Methods["Methods"]
GetId["id(), favor_phys_cpu()"]
GetState["state(), with_state_transition()"]
GetArch["get_arch_vcpu()"]
end
subgraph subGraph1["Access Patterns"]
ConstAccess["ID, CPU affinity queries"]
StateAccess["State transitions via RefCell"]
ArchAccess["Direct architecture operations"]
end
subgraph subGraph0["AxVCpu Mutability Design"]
InnerConst["AxVCpuInnerConst(Immutable)"]
InnerMut["RefCell<AxVCpuInnerMut>(Safe Interior Mutability)"]
ArchVCpu["UnsafeCell<A>(Unsafe Interior Mutability)"]
end
ArchAccess --> GetArch
ArchVCpu --> ArchAccess
ConstAccess --> GetId
InnerConst --> ConstAccess
InnerMut --> StateAccess
StateAccess --> GetState
The design rationale for different mutability approaches:
| Component | Mutability Type | Reason |
|---|---|---|
| inner_const | None | Immutable configuration data |
| inner_mut | RefCell | Safe runtime borrowing for state |
| arch_vcpu | UnsafeCell | Direct access needed during VCPU execution |
The manipulate_arch_vcpu method combines state transition with architecture delegation:
flowchart TD Start["manipulate_arch_vcpu(from, to, closure)"] StateTransition["with_state_transition(from, to)"] CurrentSet["with_current_cpu_set()"] ArchAccess["get_arch_vcpu()"] Execute["Execute closure with &mut A"] Result["Return AxResult<T>"] ArchAccess --> Execute CurrentSet --> ArchAccess Execute --> Result Start --> StateTransition StateTransition --> CurrentSet
Sources: src/vcpu.rs(L53 - L63) src/vcpu.rs(L185 - L192) src/vcpu.rs(L199 - L203)
VCPU State Machine and Lifecycle
Relevant source files
This document covers the state machine and lifecycle management of virtual CPUs in the axvcpu crate. It details the five-state VCPU model, state transition rules, lifecycle operations, and the per-CPU current VCPU tracking mechanism. For architecture-specific VCPU implementations, see Architecture Abstraction Layer. For VCPU exit handling during execution, see Exit Handling System.
VCPU State Model
The VCpuState enum defines a five-state model that governs VCPU lifecycle management. Each state represents a distinct phase in the VCPU's existence and determines which operations are permitted.
State Definitions
| State | Value | Description |
|---|---|---|
| Invalid | 0 | Error state indicating the VCPU is unusable |
| Created | 1 | VCPU exists but requires setup before use |
| Free | 2 | VCPU is configured and can be bound to a physical CPU |
| Ready | 3 | VCPU is bound to a physical CPU and ready to execute |
| Running | 4 | VCPU is actively executing guest code |
| Blocked | 5 | VCPU is temporarily blocked (currently unused) |
Sources: src/vcpu.rs(L20 - L35)
State Transition Management
The AxVCpu struct enforces strict state transition rules through the with_state_transition method, which atomically checks the current state and transitions to a new state only if the precondition is met.
flowchart TD A["with_state_transition(from, to, f)"] B["current_state == from?"] C["state = Invalid"] D["return BadState error"] E["execute f()"] F["f() succeeded?"] G["state = Invalid"] H["return error from f()"] I["state = to"] J["return Ok(result)"] A --> B B --> C B --> E C --> D E --> F F --> G F --> I G --> H I --> J
State Transition Methods
The VCPU provides several methods for managing state transitions:
with_state_transition()- Core method that atomically checks current state and transitions if validtransition_state()- Simple state transition without executing additional operationsmanipulate_arch_vcpu()- Combines state transition with architecture-specific operationsunsafe set_state()- Bypasses validation for emergency use
Sources: src/vcpu.rs(L136 - L197)
VCPU Lifecycle Operations
The VCPU lifecycle consists of four main phases: creation, setup, binding, and execution. Each phase corresponds to specific state transitions and operations.
sequenceDiagram
participant Hypervisor as "Hypervisor"
participant AxVCpu as "AxVCpu"
participant AxArchVCpu as "AxArchVCpu"
participant PhysicalCPU as "Physical CPU"
Hypervisor ->> AxVCpu: "new(id, favor_cpu, cpu_set, config)"
AxVCpu ->> AxArchVCpu: "A::new(arch_config)"
AxVCpu ->> AxVCpu: "state = Created"
Hypervisor ->> AxVCpu: "setup(entry, ept_root, config)"
AxVCpu ->> AxVCpu: "transition Created → Free"
AxVCpu ->> AxArchVCpu: "set_entry(), set_ept_root(), setup()"
Hypervisor ->> AxVCpu: "bind()"
AxVCpu ->> AxVCpu: "transition Free → Ready"
AxVCpu ->> AxArchVCpu: "bind()"
AxArchVCpu ->> PhysicalCPU: "configure CPU for this VCPU"
loop "Execution Loop"
Hypervisor ->> AxVCpu: "run()"
AxVCpu ->> AxVCpu: "transition Ready → Running"
AxVCpu ->> AxArchVCpu: "run()"
AxArchVCpu ->> AxArchVCpu: "execute guest code"
AxArchVCpu -->> AxVCpu: "AxVCpuExitReason"
AxVCpu ->> AxVCpu: "transition Running → Ready"
end
Hypervisor ->> AxVCpu: "unbind()"
AxVCpu ->> AxVCpu: "transition Ready → Free"
AxVCpu ->> AxArchVCpu: "unbind()"
Creation Phase
The new() method creates a VCPU instance with immutable configuration and initializes it in the Created state. The VCPU stores its ID, favored physical CPU, and CPU set restrictions.
Setup Phase
The setup() method configures the VCPU with guest entry point, extended page table root, and architecture-specific settings, transitioning from Created to Free state.
Binding Phase
The bind() method associates the VCPU with the current physical CPU, transitioning from Free to Ready state. This prepares the hardware for VCPU execution.
Execution Phase
The run() method executes the VCPU, transitioning from Ready to Running and back to Ready upon VM exit. This phase handles guest code execution and exit processing.
Sources: src/vcpu.rs(L66 - L225)
Current VCPU Management
The system maintains a per-CPU pointer to the currently active VCPU using the CURRENT_VCPU per-CPU variable. This enables architecture-specific code to access the containing AxVCpu instance.
flowchart TD
subgraph subGraph3["API Functions"]
GET["get_current_vcpu()"]
GETMUT["get_current_vcpu_mut()"]
SET["set_current_vcpu()"]
CLEAR["clear_current_vcpu()"]
end
subgraph subGraph2["Physical CPU N"]
CPUN["CURRENT_VCPU[N]"]
VCPUN["AxVCpuinstance"]
end
subgraph subGraph1["Physical CPU 1"]
CPU1["CURRENT_VCPU[1]"]
VCPU1["AxVCpuinstance"]
end
subgraph subGraph0["Physical CPU 0"]
CPU0["CURRENT_VCPU[0]"]
VCPU0["AxVCpuinstance"]
end
CPU0 --> VCPU0
CPU1 --> VCPU1
CPUN --> VCPUN
GET --> CPU0
GET --> CPU1
GET --> CPUN
GETMUT --> CPU0
GETMUT --> CPU1
GETMUT --> CPUN
Current VCPU API
| Function | Purpose |
|---|---|
| get_current_vcpu() | Returns immutable reference to current VCPU |
| get_current_vcpu_mut() | Returns mutable reference to current VCPU |
| set_current_vcpu() | Sets the current VCPU (unsafe) |
| clear_current_vcpu() | Clears the current VCPU (unsafe) |
The with_current_cpu_set() method ensures proper setup and cleanup of the current VCPU context, preventing nested VCPU operations which could cause corruption.
Sources: src/vcpu.rs(L238 - L290)
Error Handling and Invalid States
The state machine includes comprehensive error handling that transitions VCPUs to the Invalid state when errors occur. This prevents continued use of corrupted VCPU instances.
Error Scenarios
- State Mismatch: Attempting operations in wrong state transitions to
Invalid - Architecture Errors: Failures in
AxArchVCpuoperations transition toInvalid - Nested Operations: Attempting nested VCPU operations causes panic for safety
- Setup Failures: Errors during
setup(),bind(), orrun()transition toInvalid
Recovery Strategy
Once a VCPU enters the Invalid state, it cannot be recovered and must be recreated. This fail-fast approach prevents cascade failures and maintains system integrity.
flowchart TD A["Any State"] B["Operation Fails"] C["State = Invalid"] D["VCPU Unusable"] E["Must Recreate"] A --> B B --> C C --> D D --> E
Sources: src/vcpu.rs(L136 - L161)
Architecture Abstraction Layer
Relevant source files
This document covers the architecture abstraction mechanisms in the axvcpu crate that enable support for multiple CPU architectures through trait-based interfaces. The abstraction layer consists of two primary traits: AxArchVCpu for architecture-specific virtual CPU operations and AxVCpuHal for hardware abstraction layer functionality.
For information about the core VCPU management and state machine, see VCPU State Machine and Lifecycle. For details about how these abstractions integrate with the broader hypervisor ecosystem, see Dependencies and Integration.
AxArchVCpu Trait Interface
The AxArchVCpu trait serves as the primary abstraction for architecture-specific virtual CPU implementations. This trait defines a common interface that enables the same hypervisor code to manage virtual CPUs across different hardware architectures such as x86_64, ARM64, and RISC-V.
Trait Definition and Core Methods
The trait interface defines essential lifecycle methods and configuration types:
| Method | Purpose | Timing Constraints |
|---|---|---|
| new() | Create new VCPU instance | First call |
| set_entry() | Configure guest entry point | Beforesetup() |
| set_ept_root() | Configure extended page tables | Beforesetup() |
| setup() | Complete VCPU initialization | After entry/EPT configuration |
| run() | Execute guest until VM exit | During VCPU execution |
| bind()/unbind() | Manage physical CPU binding | During lifecycle transitions |
| set_gpr() | Set general-purpose registers | Runtime configuration |
AxArchVCpu Trait Interface
classDiagram
class AxArchVCpu {
<<trait>>
+CreateConfig: Type
+SetupConfig: Type
+new(config) AxResult~Self~
+set_entry(entry: GuestPhysAddr) AxResult
+set_ept_root(ept_root: HostPhysAddr) AxResult
+setup(config) AxResult
+run() AxResult~AxVCpuExitReason~
+bind() AxResult
+unbind() AxResult
+set_gpr(reg: usize, val: usize)
}
class CreateConfig {
<<associated type>>
"Architecture-specific creation parameters"
}
class SetupConfig {
<<associated type>>
"Architecture-specific setup parameters"
}
AxArchVCpu --> CreateConfig
AxArchVCpu --> SetupConfig
Sources: src/arch_vcpu.rs(L9 - L44)
Configuration Types and Lifecycle Management
The trait uses associated types CreateConfig and SetupConfig to allow each architecture implementation to define its own configuration parameters. This design enables architecture-specific customization while maintaining a consistent interface.
The lifecycle methods follow a strict calling sequence with timing guarantees enforced by the trait documentation:
new()creates the VCPU instance with architecture-specific configurationset_entry()andset_ept_root()configure memory layout (called exactly once each)setup()completes initialization after memory configurationbind()/unbind()manage physical CPU association during runtimerun()executes the guest and returns exit reasons for hypervisor handling
Sources: src/arch_vcpu.rs(L10 - L13) src/arch_vcpu.rs(L15 - L31)
Hardware Abstraction Layer (AxVCpuHal)
The AxVCpuHal trait provides a hardware abstraction layer for memory management and interrupt handling operations that the VCPU system requires from the underlying kernel or hypervisor.
Memory Management Interface
The HAL defines essential memory operations for frame allocation and address translation:
| Method | Operation | Return Type |
|---|---|---|
| alloc_frame() | Allocate physical frame | Option |
| dealloc_frame() | Deallocate physical frame | () |
| phys_to_virt() | Physical to virtual address translation | HostVirtAddr |
| virt_to_phys() | Virtual to physical address translation | HostPhysAddr |
Interrupt Handling Interface
The HAL provides interrupt management capabilities with default implementations:
irq_fetch()returns the current interrupt number (defaults to 0)irq_hanlder()dispatches interrupts to the host OS (unimplemented by default)
AxVCpuHal Interface Structure
classDiagram
class AxVCpuHal {
<<trait>>
+alloc_frame() Option~HostPhysAddr~
+dealloc_frame(paddr: HostPhysAddr)
+phys_to_virt(paddr: HostPhysAddr) HostVirtAddr
+virt_to_phys(vaddr: HostVirtAddr) HostPhysAddr
+irq_fetch() usize
+irq_hanlder()
}
class HostPhysAddr {
"Physical address in host address space"
}
class HostVirtAddr {
"Virtual address in host address space"
}
AxVCpuHal --> HostPhysAddr
AxVCpuHal --> HostVirtAddr
Sources: src/hal.rs(L4 - L54)
Multi-Architecture Support Design
The abstraction layer enables support for multiple CPU architectures through a trait-based design that separates generic VCPU management from architecture-specific implementation details.
Architecture Implementation Pattern
Each supported architecture provides its own implementation of the AxArchVCpu trait with architecture-specific:
- Hardware virtualization support (VMX for x86, VHE for ARM, H-extension for RISC-V)
- System register access patterns
- Exception and interrupt handling mechanisms
- Memory management unit integration
- Power management interfaces
Multi-Architecture Abstraction Pattern
Sources: src/arch_vcpu.rs(L6 - L9) src/hal.rs(L3 - L4)
Lifecycle and Method Call Sequences
The architecture abstraction enforces a specific sequence of method calls to ensure proper VCPU initialization and execution.
VCPU Initialization Sequence
The trait design guarantees that initialization methods are called in the correct order through documentation contracts and type system constraints:
VCPU Lifecycle Method Sequence
sequenceDiagram
participant Hypervisor as "Hypervisor"
participant AxVCpuT as "AxVCpu<T>"
participant TAxArchVCpu as "T: AxArchVCpu"
participant AxVCpuHal as "AxVCpuHal"
Hypervisor ->> AxVCpuT: "new(create_config)"
AxVCpuT ->> TAxArchVCpu: "AxArchVCpu::new(config)"
TAxArchVCpu ->> AxVCpuHal: "alloc_frame() (if needed)"
TAxArchVCpu -->> AxVCpuT: "Return VCPU instance"
Hypervisor ->> AxVCpuT: "set_entry(guest_entry)"
AxVCpuT ->> TAxArchVCpu: "set_entry(entry)"
Note over TAxArchVCpu: "Called exactly once before setup()"
Hypervisor ->> AxVCpuT: "set_ept_root(page_table)"
AxVCpuT ->> TAxArchVCpu: "set_ept_root(ept_root)"
Note over TAxArchVCpu: "Called exactly once before setup()"
Hypervisor ->> AxVCpuT: "setup(setup_config)"
AxVCpuT ->> TAxArchVCpu: "setup(config)"
Note over TAxArchVCpu: "Called after entry and EPT configuration"
Hypervisor ->> AxVCpuT: "bind_to_cpu()"
AxVCpuT ->> TAxArchVCpu: "bind()"
Note over TAxArchVCpu: "Bind to physical CPU for execution"
loop "Execution Loop"
Hypervisor ->> AxVCpuT: "run()"
AxVCpuT ->> TAxArchVCpu: "run()"
TAxArchVCpu -->> AxVCpuT: "AxVCpuExitReason"
Note over AxVCpuT: "Handle VM exit and return control"
end
Hypervisor ->> AxVCpuT: "unbind_from_cpu()"
AxVCpuT ->> TAxArchVCpu: "unbind()"
Sources: src/arch_vcpu.rs(L15 - L41)
Exit Handling Integration
The run() method returns AxVCpuExitReason values that integrate with the exit handling system, allowing architecture-specific implementations to communicate VM exit events through a common interface.
The abstraction enables architecture implementations to:
- Map hardware-specific exit conditions to common exit reason types
- Provide exit-specific data through standardized structures
- Maintain consistent error handling across different architectures
Sources: src/arch_vcpu.rs(L34) src/arch_vcpu.rs(L4)
Exit Handling System
Relevant source files
Purpose and Scope
The Exit Handling System provides a comprehensive framework for managing VCPU exits in the ArceOS hypervisor. When guest code execution encounters events that require hypervisor intervention, the VCPU exits to the host and provides detailed information about the cause through the AxVCpuExitReason enum. This system abstracts exit handling across different CPU architectures while providing sufficient detail for the hypervisor to emulate devices, handle hypercalls, manage power states, and process memory operations.
For detailed categorization of specific exit types, see Exit Reasons and Categories. For information about memory and I/O operation handling specifics, see Memory Access and I/O Operations. For the broader VCPU management context, see Core VCPU Management.
Exit Handling Architecture
The exit handling system centers around two primary data structures that work together to provide precise information about why a VCPU exited and what data is associated with that exit.
VCPU Exit Flow
flowchart TD A["Guest Execution"] B["Exit Event Occurs"] C["Hardware Exit"] D["AxArchVCpu::run()"] E["AxVCpuExitReason Creation"] F["Return to Hypervisor"] G["Exit Type Analysis"] H["Memory Operations"] I["System Control"] J["I/O Operations"] K["Interrupts & Faults"] L["Power Management"] M["Error Conditions"] N["Device Emulation"] O["Hypercall Processing"] P["Port I/O Emulation"] Q["Interrupt Injection"] R["CPU Management"] S["Error Recovery"] T["Resume Guest"] U["CPU State Change"] V["Exit or Recovery"] A --> B B --> C C --> D D --> E E --> F F --> G G --> H G --> I G --> J G --> K G --> L G --> M H --> N I --> O J --> P K --> Q L --> R M --> S N --> T O --> T P --> T Q --> T R --> U S --> V
Sources: src/exit.rs(L66 - L210)
Access Width Specification System
The system uses a precise access width specification to handle different data sizes across memory and I/O operations:
flowchart TD A["AccessWidth Enum"] B["Byte (8-bit)"] C["Word (16-bit)"] D["Dword (32-bit)"] E["Qword (64-bit)"] F["usize Conversion"] G["Size Calculation"] H["Bit Range Mapping"] I["1 byte operations"] J["2 byte operations"] K["4 byte operations"] L["8 byte operations"] A --> B A --> C A --> D A --> E A --> G A --> H B --> I C --> J D --> K E --> L F --> A
Sources: src/exit.rs(L6 - L61)
Core Data Structures
AccessWidth Enum
The AccessWidth enum provides standardized representation of memory and I/O access sizes. It supports conversion from and to usize values and provides utility methods for size calculations and bit range mapping.
| Access Width | Size (bytes) | Bit Range | Usage |
|---|---|---|---|
| Byte | 1 | 0..8 | Byte-level operations |
| Word | 2 | 0..16 | 16-bit operations (x86 terminology) |
| Dword | 4 | 0..32 | 32-bit operations |
| Qword | 8 | 0..64 | 64-bit operations |
Sources: src/exit.rs(L6 - L61)
AxVCpuExitReason Enum
The AxVCpuExitReason enum is marked as #[non_exhaustive] to allow future expansion and contains comprehensive exit information for all supported virtualization scenarios.
Exit Reason Categories
Code Entity Mapping
flowchart TD
subgraph subGraph6["Supporting Types"]
AW["AccessWidth"]
GPA["GuestPhysAddr"]
MF["MappingFlags"]
P["Port (u16)"]
end
subgraph subGraph4["Power Management"]
CU["CpuUp"]
CD["CpuDown"]
SD["SystemDown"]
end
subgraph subGraph2["I/O Operations"]
IOR["IoRead"]
IOW["IoWrite"]
end
subgraph subGraph1["System Control"]
HC["Hypercall"]
SRR["SysRegRead"]
SRW["SysRegWrite"]
end
subgraph subGraph0["Memory Operations"]
MR["MmioRead"]
MW["MmioWrite"]
NPF["NestedPageFault"]
end
subgraph subGraph3["Interrupts & Faults"]
subgraph subGraph5["Control Flow"]
N["Nothing"]
FE["FailEntry"]
EI["ExternalInterrupt"]
H["Halt"]
end
end
CU --> GPA
IOR --> AW
IOR --> P
IOW --> AW
IOW --> P
MR --> AW
MR --> GPA
MW --> AW
MW --> GPA
NPF --> GPA
NPF --> MF
SRR --> AW
Sources: src/exit.rs(L70 - L210)
Exit Processing Workflow
Memory and I/O Exit Handling
Memory-mapped I/O operations (MmioRead, MmioWrite) and port I/O operations (IoRead, IoWrite) include precise access width information and target addresses. The system distinguishes between different register widths for MMIO reads to support complex instruction emulation.
System Register Access
System register operations (SysRegRead, SysRegWrite) provide architecture-specific register addressing schemes:
- x86_64: MSR addresses
- ARM64: ESR_EL2.ISS format encoding (
<op0><op2><op1><CRn>00000<CRm>0) - RISC-V: CSR addresses
Power Management Operations
Power management exits (CpuUp, CpuDown, SystemDown) handle CPU lifecycle events with architecture-specific target CPU identification:
- ARM64: MPIDR affinity fields
- x86_64: APIC ID values
- RISC-V: Hart ID values
Error and Control Flow
The system provides Nothing exits for polling opportunities and FailEntry exits for hardware-level virtualization failures with architecture-specific failure reason codes.
Sources: src/exit.rs(L70 - L210)
Integration Points
The exit handling system integrates with several key components:
- AxArchVCpu trait: Architecture implementations return
AxVCpuExitReasonfrom theirrun()methods - GuestPhysAddr: Physical address representation from
axaddrspacecrate - MappingFlags: Memory permission flags for nested page fault handling
- Hypervisor: Processes exit reasons to emulate devices and manage guest state
The #[non_exhaustive] attribute on AxVCpuExitReason ensures forward compatibility as new exit types are added for expanded virtualization features.
Sources: src/exit.rs(L1 - L4) src/exit.rs(L66 - L67)
Exit Reasons and Categories
Relevant source files
This page documents the comprehensive set of virtual CPU exit reasons that can occur during guest execution in the AxVCpu system. It covers the AxVCpuExitReason enum and related types that define when and why a virtual CPU exits guest mode to return control to the hypervisor.
For information about how these exit reasons are processed and handled by the hypervisor, see Memory Access and I/O Operations. For details about the VCPU state management during exits, see VCPU State Machine and Lifecycle.
Exit Reason Overview
The AxVCpuExitReason enum defined in src/exit.rs(L68 - L210) provides a comprehensive taxonomy of all possible reasons why a virtual CPU might exit guest execution. Each exit reason includes specific data fields relevant to that type of exit, enabling the hypervisor to process the exit appropriately.
The exit system is designed to be architecture-independent while accommodating architecture-specific details where necessary. All exit reasons include detailed context information to allow the hypervisor to emulate the operation, handle the event, or make decisions about guest execution.
Sources: src/exit.rs(L66 - L210)
Exit Categories and Types
Memory Operations
Memory-related exits occur when the guest attempts operations that require hypervisor intervention:
| Exit Type | Purpose | Key Data |
|---|---|---|
| MmioRead | Guest reads from memory-mapped I/O | Address, access width, target register |
| MmioWrite | Guest writes to memory-mapped I/O | Address, access width, data value |
| NestedPageFault | Page fault in nested paging | Fault address, access flags |
MMIO Operations
MmioReadsrc/exit.rs(L78 - L88) captures reads with target register informationMmioWritesrc/exit.rs(L89 - L97) includes the data being written- Both specify the
GuestPhysAddrandAccessWidthfor precise emulation
Page Faults
NestedPageFaultsrc/exit.rs(L153 - L161) occurs during EPT violations on x86 or stage-2 faults on ARM- Includes
MappingFlagsto indicate the type of access attempted
System Control Operations
System-level control operations that require hypervisor mediation:
| Exit Type | Purpose | Architecture Scope |
|---|---|---|
| SysRegRead | Read system registers | MSRs (x86), CSRs (RISC-V), System regs (ARM) |
| SysRegWrite | Write system registers | MSRs (x86), CSRs (RISC-V), System regs (ARM) |
| Hypercall | Guest-initiated hypervisor calls | All architectures |
System Register Access
- src/exit.rs(L98 - L125) defines both read and write operations
- Address encoding varies by architecture (ESR_EL2.ISS format for ARM64)
- Register index specified for read operations to identify target GPR
Hypercalls
- src/exit.rs(L71 - L77) includes hypercall number and up to 6 arguments
- Provides standardized interface for guest-hypervisor communication
I/O Operations (x86-specific)
Direct I/O port access operations, specific to x86 architecture:
IoReadsrc/exit.rs(L126 - L134) for reading from I/O portsIoWritesrc/exit.rs(L135 - L145) for writing to I/O ports- Both use
Porttype (u16) src/exit.rs(L64) and specifyAccessWidth - Target register is implicit (always al/ax/eax) due to x86 I/O instruction semantics
Interrupt and Exception Handling
Events that interrupt normal guest execution:
ExternalInterruptsrc/exit.rs(L146 - L152) captures hardware interrupts with vector numberHaltsrc/exit.rs(L163) indicates the guest has halted execution
Power Management
CPU and system power state changes:
| Exit Type | Purpose | Data Fields |
|---|---|---|
| CpuUp | Bring up secondary CPU | Target CPU ID, entry point, argument |
| CpuDown | Power down CPU | Power state (currently unused) |
| SystemDown | Power down entire system | None |
Multi-CPU Management
CpuUpsrc/exit.rs(L164 - L186) handles architecture-specific CPU startup (PSCI/SIPI/SBI)- Target CPU identification varies by architecture (MPIDR/APIC ID/hartid)
Error and Special Conditions
Exceptional conditions and edge cases:
Nothingsrc/exit.rs(L199 - L202) indicates no special handling neededFailEntrysrc/exit.rs(L203 - L209) represents VM entry failures with hardware-specific error codes
Sources: src/exit.rs(L68 - L210)
Access Width Specifications
The AccessWidth enum src/exit.rs(L6 - L61) provides precise specification of memory and I/O operation sizes:
flowchart TD
subgraph subGraph2["Bit Ranges"]
R1["0..8 bits"]
R2["0..16 bits"]
R3["0..32 bits"]
R4["0..64 bits"]
end
subgraph subGraph1["Conversion Methods"]
B1["1 byte"]
B2["2 bytes"]
B4["4 bytes"]
B8["8 bytes"]
end
subgraph subGraph0["AccessWidth Enum"]
AW["AccessWidth"]
Byte["Byte (8-bit)"]
Word["Word (16-bit)"]
Dword["Dword (32-bit)"]
Qword["Qword (64-bit)"]
end
AW --> Byte
AW --> Dword
AW --> Qword
AW --> Word
B1 --> R1
B2 --> R2
B4 --> R3
B8 --> R4
Byte --> B1
Dword --> B4
Qword --> B8
Word --> B2
AccessWidth Conversion and Utility Methods
TryFrom<usize>src/exit.rs(L21 - L33) converts byte sizes to width enumFrom<AccessWidth>src/exit.rs(L35 - L44) converts width to byte sizesize()method src/exit.rs(L47 - L51) returns size in bytesbits_range()method src/exit.rs(L52 - L61) returns bit range covered
Sources: src/exit.rs(L6 - L61)
Exit Reason Classification
flowchart TD
subgraph subGraph6["Error Conditions"]
Nothing["Nothing"]
FailEntry["FailEntry"]
end
subgraph subGraph5["Power Management"]
CpuUp["CpuUp"]
CpuDown["CpuDown"]
SystemDown["SystemDown"]
end
subgraph subGraph4["Interrupts & Faults"]
ExternalInterrupt["ExternalInterrupt"]
Halt["Halt"]
end
subgraph subGraph3["I/O Operations"]
IoRead["IoRead (x86 only)"]
IoWrite["IoWrite (x86 only)"]
end
subgraph subGraph2["System Control"]
SysRegRead["SysRegRead"]
SysRegWrite["SysRegWrite"]
Hypercall["Hypercall"]
end
subgraph subGraph1["Memory Operations"]
MmioRead["MmioRead"]
MmioWrite["MmioWrite"]
NestedPageFault["NestedPageFault"]
end
subgraph subGraph0["AxVCpuExitReason Categories"]
MEM["Memory Operations"]
SYS["System Control"]
IO["I/O Operations"]
INT["Interrupts & Faults"]
PWR["Power Management"]
ERR["Error Conditions"]
end
ERR --> FailEntry
ERR --> Nothing
INT --> ExternalInterrupt
INT --> Halt
IO --> IoRead
IO --> IoWrite
MEM --> MmioRead
MEM --> MmioWrite
MEM --> NestedPageFault
PWR --> CpuDown
PWR --> CpuUp
PWR --> SystemDown
SYS --> Hypercall
SYS --> SysRegRead
SYS --> SysRegWrite
This diagram shows the logical grouping of exit reasons by their primary function and the specific enum variants that fall into each category.
Sources: src/exit.rs(L68 - L210)
Exit Processing Flow
sequenceDiagram
participant GuestExecution as "Guest Execution"
participant AxVCpu as "AxVCpu"
participant AxVCpuExitReason as "AxVCpuExitReason"
participant HypervisorHandler as "Hypervisor Handler"
GuestExecution ->> AxVCpu: "Trigger exit event"
AxVCpu ->> AxVCpuExitReason: "Create specific exit reason"
alt Memory Operation
AxVCpuExitReason ->> AxVCpuExitReason: "MmioRead/MmioWrite + AccessWidth"
else System Control
AxVCpuExitReason ->> AxVCpuExitReason: "SysRegRead/SysRegWrite/Hypercall"
else I/O Operation (x86)
AxVCpuExitReason ->> AxVCpuExitReason: "IoRead/IoWrite + Port"
else Power Management
AxVCpuExitReason ->> AxVCpuExitReason: "CpuUp/CpuDown/SystemDown"
else Interrupt/Fault
AxVCpuExitReason ->> AxVCpuExitReason: "ExternalInterrupt/Halt/NestedPageFault"
else Error/Special
AxVCpuExitReason ->> AxVCpuExitReason: "Nothing/FailEntry"
end
AxVCpuExitReason ->> HypervisorHandler: "Return exit reason with context data"
HypervisorHandler ->> HypervisorHandler: "Process based on exit type"
HypervisorHandler ->> AxVCpu: "Resume or handle appropriately"
This sequence diagram illustrates how different types of guest operations result in specific exit reasons being created and returned to the hypervisor for processing.
Sources: src/exit.rs(L66 - L210)
Memory Access and I/O Operations
Relevant source files
This document covers how memory access and I/O operations are handled within the axvcpu exit system. It details the mechanisms for memory-mapped I/O (MMIO), system register access, I/O port operations, and memory access violations that trigger VM exits. The focus is on the data structures and workflows that enable the hypervisor to intercept and emulate these operations.
For information about the broader exit handling categories and system, see Exit Reasons and Categories. For details on VCPU lifecycle management, see Core VCPU Management.
Access Width Specification
The AccessWidth enum provides a standardized way to specify the size of memory and I/O operations across different architectures. It supports 8-bit through 64-bit accesses with explicit bit range calculations.
| Access Type | Size (bytes) | Bit Range | Use Case |
|---|---|---|---|
| Byte | 1 | 0..8 | Character data, byte operations |
| Word | 2 | 0..16 | 16-bit integers, x86 word operations |
| Dword | 4 | 0..32 | 32-bit integers, most common size |
| Qword | 8 | 0..64 | 64-bit integers, pointer operations |
flowchart TD AccessWidth["AccessWidth Enum"] Byte["Byte (1 byte)0..8 bits"] Word["Word (2 bytes)0..16 bits"] Dword["Dword (4 bytes)0..32 bits"] Qword["Qword (8 bytes)0..64 bits"] ByteOps["Character I/OByte registers"] WordOps["x86 16-bit operationsLegacy I/O"] DwordOps["32-bit registersMost MMIO operations"] QwordOps["64-bit pointersLarge data transfers"] AccessWidth --> Byte AccessWidth --> Dword AccessWidth --> Qword AccessWidth --> Word Byte --> ByteOps Dword --> DwordOps Qword --> QwordOps Word --> WordOps
The AccessWidth implementation provides conversion methods between enum values and byte sizes, enabling precise operation handling across architectures.
Sources: src/exit.rs(L6 - L61)
Memory-Mapped I/O (MMIO) Operations
MMIO operations represent the most common form of device interaction in virtualized environments. When a guest attempts to access memory-mapped device registers, the hardware triggers a VM exit that the hypervisor must handle through device emulation.
MMIO Read Operations
MMIO read exits occur when the guest CPU attempts to read from a memory-mapped device register. The exit provides the physical address, access width, and register information needed for emulation.
sequenceDiagram
participant GuestOS as "Guest OS"
participant GuestCPU as "Guest CPU"
participant Hypervisor as "Hypervisor"
participant EmulatedDevice as "Emulated Device"
GuestOS ->> GuestCPU: "ldr x0, [device_base + offset]"
GuestCPU ->> Hypervisor: "MmioRead exit"
Note over Hypervisor: "addr: GuestPhysAddr<br>width: AccessWidth<br>reg: usize<br>reg_width: AccessWidth"
Hypervisor ->> EmulatedDevice: "Read device register"
EmulatedDevice ->> Hypervisor: "Register value"
Hypervisor ->> GuestCPU: "Set guest register"
GuestCPU ->> GuestOS: "Continue execution"
The MmioRead variant includes both the memory access width and the target register width, allowing for proper handling of sub-register operations and sign extension.
MMIO Write Operations
MMIO write exits are triggered when the guest writes to memory-mapped device registers. The exit provides the target address, data value, and access width for the hypervisor to emulate the write operation.
flowchart TD MmioWrite["MmioWrite Exit"] addr["addr: GuestPhysAddrDevice register address"] width["width: AccessWidthOperation size"] data["data: u64Value to write"] DeviceEmu["Device Emulation"] RegUpdate["Update Device State"] SideEffects["Trigger Side Effects"] Interrupts["Generate Interrupts"] DeviceEmu --> Interrupts DeviceEmu --> RegUpdate DeviceEmu --> SideEffects MmioWrite --> addr MmioWrite --> data MmioWrite --> width addr --> DeviceEmu data --> DeviceEmu width --> DeviceEmu
Sources: src/exit.rs(L78 - L97)
System Register Access
System register operations provide a unified interface for accessing architecture-specific control and status registers. The implementation handles MSRs (x86), CSRs (RISC-V), and system registers (ARM64) through a common exit mechanism.
Register Address Encoding
Different architectures use distinct addressing schemes for system registers:
| Architecture | Register Type | Address Format |
|---|---|---|
| x86_64 | MSR | Direct MSR number |
| RISC-V | CSR | Direct CSR address |
| ARM64 | System Register | ESR_EL2.ISS format: |
System Register Read/Write Flow
flowchart TD SysReg["System Register Access"] SysRegRead["SysRegRead"] SysRegWrite["SysRegWrite"] ReadAddr["addr: usizeRegister address"] ReadReg["reg: usizeTarget GPR index"] WriteAddr["addr: usizeRegister address"] WriteValue["value: u64Data to write"] ArchHandler["Architecture-Specific Handler"] x86MSR["x86: MSR Access"] RISCVCSR["RISC-V: CSR Access"] ARMSysReg["ARM64: System Register"] ArchHandler --> ARMSysReg ArchHandler --> RISCVCSR ArchHandler --> x86MSR ReadAddr --> ArchHandler ReadReg --> ArchHandler SysReg --> SysRegRead SysReg --> SysRegWrite SysRegRead --> ReadAddr SysRegRead --> ReadReg SysRegWrite --> WriteAddr SysRegWrite --> WriteValue WriteAddr --> ArchHandler WriteValue --> ArchHandler
Sources: src/exit.rs(L98 - L125)
I/O Port Operations
I/O port operations are specific to x86 architectures and handle legacy port-based device communication. These operations use 16-bit port addresses and support the standard x86 I/O instruction set.
Port I/O Characteristics
- Port Range: 16-bit port numbers (0-65535)
- Register Usage: Fixed to
al/ax/eaxregisters - Architecture: x86-specific feature
- Access Widths: Byte, Word, Dword operations
flowchart TD IoOps["I/O Port Operations"] IoRead["IoRead"] IoWrite["IoWrite"] ReadPort["port: u16Port number"] ReadWidth["width: AccessWidthData size"] WritePort["port: u16Port number"] WriteWidth["width: AccessWidthData size"] WriteData["data: u64Output value"] PortEmulation["Port Device Emulation"] LegacyDevices["Legacy PC Devices"] DebugPorts["Debug Interfaces"] ControlPorts["System Control"] IoOps --> IoRead IoOps --> IoWrite IoRead --> ReadPort IoRead --> ReadWidth IoWrite --> WriteData IoWrite --> WritePort IoWrite --> WriteWidth PortEmulation --> ControlPorts PortEmulation --> DebugPorts PortEmulation --> LegacyDevices ReadPort --> PortEmulation ReadWidth --> PortEmulation WriteData --> PortEmulation WritePort --> PortEmulation WriteWidth --> PortEmulation
Sources: src/exit.rs(L126 - L145)
Memory Access Violations
Nested page faults occur when guest memory accesses violate the host's memory protection policies. These exits enable the hypervisor to implement memory management features like demand paging, copy-on-write, and access control.
Nested Page Fault Handling
flowchart TD GuestAccess["Guest Memory Access"] PageTableWalk["Hardware Page Table Walk"] ViolationCheck["Access Violation?"] DirectAccess["Direct Memory Access"] NestedPageFault["NestedPageFault Exit"] FaultAddr["addr: GuestPhysAddrFaulting address"] AccessFlags["access_flags: MappingFlagsAttempted operation"] HypervisorHandler["Hypervisor Memory Manager"] DemandPaging["Demand Paging"] COW["Copy-on-Write"] AccessControl["Access Control"] SwapHandling["Swap Management"] AccessFlags --> HypervisorHandler FaultAddr --> HypervisorHandler GuestAccess --> PageTableWalk HypervisorHandler --> AccessControl HypervisorHandler --> COW HypervisorHandler --> DemandPaging HypervisorHandler --> SwapHandling NestedPageFault --> AccessFlags NestedPageFault --> FaultAddr PageTableWalk --> ViolationCheck ViolationCheck --> DirectAccess ViolationCheck --> NestedPageFault
The NestedPageFault exit provides the faulting guest physical address and the access flags that describe the type of operation that caused the fault (read, write, execute).
Sources: src/exit.rs(L153 - L161)
Integration with Exit Handling System
All memory access and I/O operations are unified under the AxVCpuExitReason enum, providing a consistent interface for the hypervisor to handle different types of virtualization exits.
Exit Type Classification
flowchart TD AxVCpuExitReason["AxVCpuExitReason"] MemoryOps["Memory Operations"] IOOps["I/O Operations"] SystemOps["System Operations"] MmioRead["MmioRead"] MmioWrite["MmioWrite"] NestedPageFault["NestedPageFault"] IoRead["IoRead (x86)"] IoWrite["IoWrite (x86)"] SysRegRead["SysRegRead"] SysRegWrite["SysRegWrite"] AccessWidth["AccessWidth Support"] ArchSpecific["Architecture-Specific Handling"] AxVCpuExitReason --> IOOps AxVCpuExitReason --> MemoryOps AxVCpuExitReason --> SystemOps IOOps --> IoRead IOOps --> IoWrite IoRead --> AccessWidth IoWrite --> AccessWidth MemoryOps --> MmioRead MemoryOps --> MmioWrite MemoryOps --> NestedPageFault MmioRead --> AccessWidth MmioWrite --> AccessWidth SysRegRead --> ArchSpecific SysRegWrite --> ArchSpecific SystemOps --> SysRegRead SystemOps --> SysRegWrite
This unified approach enables the hypervisor to implement comprehensive device emulation, memory management, and system control through a single exit handling interface.
Sources: src/exit.rs(L68 - L210)
Implementation Details
Relevant source files
This document covers the core implementation systems that enable VCPU functionality in the axvcpu crate, focusing on the supporting infrastructure for virtualization state management and hardware abstraction. This includes the per-CPU virtualization state management system and the hardware abstraction layer that interfaces with the underlying host system.
For detailed information about VCPU lifecycle management and architecture abstraction, see Core VCPU Management. For specific exit handling mechanisms, see Exit Handling System.
Implementation Architecture Overview
The axvcpu implementation is built on two primary supporting systems that work together to provide a foundation for virtualization:
flowchart TD
subgraph subGraph2["Host System Integration"]
ADDRSPACE["axaddrspaceAddress Space Management"]
ERRNO["axerrnoError Handling"]
PERCPU_LIB["percpuPer-CPU Variables"]
end
subgraph subGraph1["Implementation Infrastructure"]
PERCPU["AxPerCpuPer-CPU State Manager"]
HAL["AxVCpuHalHardware Abstraction"]
ARCH_PERCPU["AxArchPerCpuArchitecture Per-CPU Trait"]
end
subgraph subGraph0["VCPU Management Layer"]
VCPU["AxVCpuVCPU Manager"]
EXIT["AxVCpuExitReasonExit Handler"]
end
HAL --> ADDRSPACE
PERCPU --> ARCH_PERCPU
PERCPU --> ERRNO
PERCPU --> PERCPU_LIB
VCPU --> HAL
VCPU --> PERCPU
Implementation Infrastructure Architecture
The implementation consists of two main abstraction layers:
Sources: src/percpu.rs(L1 - L104) src/hal.rs(L1 - L55)
Per-CPU State Management System
The per-CPU state management system provides a generic wrapper around architecture-specific virtualization state, ensuring proper initialization and cleanup across different CPU architectures:
flowchart TD
subgraph subGraph1["Architecture Implementation"]
ARCH_NEW["A::new(cpu_id)"]
ARCH_ENABLE["A::hardware_enable()"]
ARCH_DISABLE["A::hardware_disable()"]
ARCH_CHECK["A::is_enabled()"]
end
subgraph subGraph0["Per-CPU State Lifecycle"]
UNINIT["AxPerCpu::new_uninit()Uninitialized State"]
INIT["init(cpu_id)Initialize Architecture State"]
ENABLED["hardware_enable()Virtualization Active"]
DISABLED["hardware_disable()Cleanup on Drop"]
end
DISABLED --> ARCH_DISABLE
ENABLED --> ARCH_CHECK
ENABLED --> ARCH_ENABLE
ENABLED --> DISABLED
INIT --> ARCH_NEW
INIT --> ENABLED
UNINIT --> INIT
Per-CPU State Management Flow
The AxPerCpu<A> struct provides a type-safe wrapper that:
- Tracks initialization state through the
cpu_idfield src/percpu.rs(L42) - Safely manages uninitialized memory using
MaybeUninit<A>src/percpu.rs(L44) - Provides checked access methods that prevent use before initialization src/percpu.rs(L68 - L79)
- Automatically disables virtualization in the
Dropimplementation src/percpu.rs(L97 - L103)
Sources: src/percpu.rs(L40 - L95)
Hardware Abstraction Layer
The AxVCpuHal trait defines the interface between the VCPU system and the underlying host system, abstracting memory management and interrupt handling:
flowchart TD
subgraph subGraph1["Host System Services"]
FRAME_ALLOC["Frame Allocator"]
ADDR_TRANS["Address Translation"]
IRQ_SYSTEM["Interrupt Controller"]
end
subgraph subGraph0["AxVCpuHal Interface"]
ALLOC["alloc_frame()→ Option"]
DEALLOC["dealloc_frame(paddr)Frame Cleanup"]
P2V["phys_to_virt(paddr)→ HostVirtAddr"]
V2P["virt_to_phys(vaddr)→ HostPhysAddr"]
IRQ_FETCH["irq_fetch()→ usize"]
IRQ_HANDLER["irq_handler()Dispatch to Host"]
end
ALLOC --> FRAME_ALLOC
DEALLOC --> FRAME_ALLOC
IRQ_FETCH --> IRQ_SYSTEM
IRQ_HANDLER --> IRQ_SYSTEM
P2V --> ADDR_TRANS
V2P --> ADDR_TRANS
Hardware Abstraction Layer Interface
The HAL provides essential services for VCPU operation:
| Function | Return Type | Purpose |
|---|---|---|
| alloc_frame() | Option | Allocate physical memory frame |
| dealloc_frame(paddr) | () | Release physical memory frame |
| phys_to_virt(paddr) | HostVirtAddr | Convert physical to virtual address |
| virt_to_phys(vaddr) | HostPhysAddr | Convert virtual to physical address |
| irq_fetch() | usize | Get current interrupt number |
| irq_handler() | () | Dispatch interrupt to host |
Sources: src/hal.rs(L4 - L54)
Integration and Usage Patterns
The implementation components work together through well-defined integration patterns:
sequenceDiagram
participant Hypervisor as "Hypervisor"
participant AxPerCpuA as "AxPerCpu<A>"
participant AxArchPerCpuImpl as "AxArchPerCpu Impl"
participant AxVCpuHal as "AxVCpuHal"
participant HostSystem as "Host System"
Note over Hypervisor,HostSystem: Initialization Phase
Hypervisor ->> AxPerCpuA: new_uninit()
Hypervisor ->> AxPerCpuA: init(cpu_id)
AxPerCpuA ->> AxArchPerCpuImpl: new(cpu_id)
AxArchPerCpuImpl ->> AxVCpuHal: alloc_frame() (if needed)
AxVCpuHal ->> HostSystem: allocate memory
AxPerCpuA ->> AxArchPerCpuImpl: hardware_enable()
Note over Hypervisor,HostSystem: Runtime Phase
Hypervisor ->> AxPerCpuA: is_enabled()
AxPerCpuA ->> AxArchPerCpuImpl: is_enabled()
Hypervisor ->> AxVCpuHal: phys_to_virt(addr)
AxVCpuHal ->> HostSystem: address translation
Note over Hypervisor,HostSystem: Cleanup Phase
Hypervisor ->> AxPerCpuA: Drop
AxPerCpuA ->> AxArchPerCpuImpl: hardware_disable()
AxArchPerCpuImpl ->> AxVCpuHal: dealloc_frame() (if needed)
AxVCpuHal ->> HostSystem: release memory
Implementation Integration Sequence
The recommended usage pattern involves:
- Static Definition: Using the
percpucrate to define per-CPU state src/percpu.rs(L27 - L28) - Initialization: Calling
init()andhardware_enable()during hypervisor startup src/percpu.rs(L34 - L38) - Runtime Access: Using
arch_checked()andarch_checked_mut()for safe access src/percpu.rs(L68 - L79) - Automatic Cleanup: Relying on the
Dropimplementation for proper shutdown src/percpu.rs(L97 - L103)
Sources: src/percpu.rs(L21 - L39) src/hal.rs(L1 - L55)
Per-CPU Virtualization State
Relevant source files
This document covers the per-CPU virtualization state management system in axvcpu, which provides a safe abstraction for initializing, enabling, and managing hardware virtualization features on each CPU core. This system ensures that virtualization capabilities are properly configured across all CPUs in the hypervisor and provides architecture-independent lifecycle management with architecture-specific implementations.
For information about the overall VCPU management and state machine, see VCPU State Machine and Lifecycle. For details about the hardware abstraction layer, see Hardware Abstraction Layer.
Architecture Abstraction
The per-CPU virtualization state system is built around the AxArchPerCpu trait, which defines the interface that architecture-specific implementations must provide. This trait abstracts the hardware-specific operations needed to manage virtualization on a per-CPU basis.
AxArchPerCpu Trait Interface
The AxArchPerCpu trait defines four core operations for managing per-CPU virtualization state:
| Method | Purpose | Return Type |
|---|---|---|
| new(cpu_id: usize) | Create new per-CPU state for specified CPU | AxResult |
| is_enabled(&self) | Check if hardware virtualization is enabled | bool |
| hardware_enable(&mut self) | Enable hardware virtualization on current CPU | AxResult |
| hardware_disable(&mut self) | Disable hardware virtualization on current CPU | AxResult |
Each architecture (x86_64, ARM64, RISC-V) provides its own implementation of this trait to handle platform-specific virtualization setup and control.
AxArchPerCpu Trait Architecture
flowchart TD
subgraph subGraph3["RISC-V Implementation"]
RISCV_IMPL["RiscVPerCpu"]
RISCV_H["H Extension"]
RISCV_CSR["CSR Configuration"]
end
subgraph subGraph2["ARM64 Implementation"]
ARM_IMPL["ArmPerCpu"]
ARM_EL2["EL2 Setup"]
ARM_HYP["Hypervisor Mode"]
end
subgraph subGraph1["x86_64 Implementation"]
X86_IMPL["X86PerCpu"]
X86_VMX["VMX Setup"]
X86_MSR["MSR Configuration"]
end
subgraph subGraph0["Architecture Abstraction"]
TRAIT["AxArchPerCpu"]
TRAIT_NEW["new(cpu_id: usize)"]
TRAIT_ENABLED["is_enabled()"]
TRAIT_ENABLE["hardware_enable()"]
TRAIT_DISABLE["hardware_disable()"]
end
ARM_IMPL --> ARM_EL2
ARM_IMPL --> ARM_HYP
RISCV_IMPL --> RISCV_CSR
RISCV_IMPL --> RISCV_H
TRAIT --> TRAIT_DISABLE
TRAIT --> TRAIT_ENABLE
TRAIT --> TRAIT_ENABLED
TRAIT --> TRAIT_NEW
TRAIT_NEW --> ARM_IMPL
TRAIT_NEW --> RISCV_IMPL
TRAIT_NEW --> X86_IMPL
X86_IMPL --> X86_MSR
X86_IMPL --> X86_VMX
Sources: src/percpu.rs(L5 - L19)
Per-CPU State Container
The AxPerCpu<A> struct serves as a safe wrapper around architecture-specific per-CPU state, providing initialization checking, lifecycle management, and automatic cleanup.
Structure and Fields
The AxPerCpu struct contains two key fields:
cpu_id: Option<usize>- Tracks the CPU ID and serves as an initialization flagarch: MaybeUninit<A>- Stores the architecture-specific state in an uninitialized container
This design ensures that the architecture-specific state is only accessed after proper initialization and provides compile-time safety through the type system.
Initialization and Lifecycle
The per-CPU state follows a strict initialization pattern:
- Creation:
new_uninit()creates an uninitialized state - Initialization:
init(cpu_id)initializes the architecture-specific state - Usage: Methods check initialization before accessing architecture state
- Cleanup:
Dropimplementation automatically disables virtualization
AxPerCpu Lifecycle State Machine
Sources: src/percpu.rs(L40 - L95)
Safety and Error Handling
The AxPerCpu implementation provides several safety mechanisms:
Initialization Checking
All methods that access the architecture-specific state use arch_checked() and arch_checked_mut(), which verify that initialization has occurred before accessing the underlying state:
- Panics if
cpu_idisNone(not initialized) - Uses
unsafecode only after verification that initialization occurred - Provides both immutable and mutable access patterns
Automatic Cleanup
The Drop implementation ensures that hardware virtualization is properly disabled when the per-CPU state is destroyed, preventing resource leaks and ensuring system stability.
Error Propagation
Methods return AxResult to propagate architecture-specific errors up to the hypervisor, allowing for proper error handling and recovery.
Sources: src/percpu.rs(L67 - L79) src/percpu.rs(L97 - L103)
Usage Patterns
The documentation provides a recommended usage pattern for integrating per-CPU state into a hypervisor:
Static Per-CPU Declaration
#[percpu::def_percpu]
pub static AXVM_PER_CPU: AxPerCpu<MyArchPerCpuImpl> = AxPerCpu::new_uninit();
Initialization and Enablement
let percpu = unsafe { AXVM_PER_CPU.current_ref_mut_raw() };
percpu.init(0).expect("Failed to initialize percpu state");
percpu.hardware_enable().expect("Failed to enable virtualization");
This pattern leverages the percpu crate to manage per-CPU variables and ensures that each CPU core has its own isolated virtualization state.
Per-CPU Integration Pattern
flowchart TD
subgraph subGraph2["Lifecycle Operations"]
INIT_CALL["init(cpu_id)"]
ENABLE_CALL["hardware_enable()"]
RUNTIME["Runtime Operations"]
DISABLE_CALL["hardware_disable()"]
end
subgraph subGraph1["Per-CPU Variables"]
STATIC_VAR["AXVM_PER_CPU"]
PERCPU_LIB["percpu crate"]
CPU0["CPU 0 Instance"]
CPU1["CPU 1 Instance"]
CPUN["CPU N Instance"]
end
subgraph subGraph0["Hypervisor Initialization"]
BOOT["Boot Process"]
CPU_ENUM["CPU Enumeration"]
PERCPU_INIT["Per-CPU Init"]
end
BOOT --> CPU_ENUM
CPU0 --> INIT_CALL
CPU_ENUM --> PERCPU_INIT
ENABLE_CALL --> RUNTIME
INIT_CALL --> ENABLE_CALL
PERCPU_INIT --> STATIC_VAR
PERCPU_LIB --> CPU0
PERCPU_LIB --> CPU1
PERCPU_LIB --> CPUN
RUNTIME --> DISABLE_CALL
STATIC_VAR --> PERCPU_LIB
Sources: src/percpu.rs(L23 - L39)
Integration with VCPU System
The per-CPU virtualization state system provides the foundation for VCPU operations by ensuring that each physical CPU has the necessary hardware virtualization features enabled. This state is checked and managed independently of individual VCPU instances, allowing multiple VCPUs to share the same physical CPU infrastructure while maintaining isolation.
The is_enabled() method provides a quick check for whether a CPU is ready to run VCPUs, while the enable/disable methods allow for dynamic power management and system reconfiguration.
Sources: src/percpu.rs(L81 - L94)
Hardware Abstraction Layer
Relevant source files
Purpose and Scope
The Hardware Abstraction Layer (HAL) provides a standardized interface for the VCPU system to interact with the underlying host environment, whether it's a kernel or hypervisor. The AxVCpuHal trait abstracts essential operations including memory management, address translation, and interrupt handling, enabling the VCPU implementation to remain portable across different host systems.
For information about architecture-specific VCPU implementations, see Architecture Abstraction Layer. For details about per-CPU state management, see Per-CPU Virtualization State.
HAL Interface Overview
The AxVCpuHal trait defines the contract between the VCPU system and the host environment:
flowchart TD
subgraph subGraph2["Host Environment"]
KERNEL["Host Kernel/Hypervisor"]
FRAME["Frame Allocator"]
MMU["MMU/Page Tables"]
INTC["Interrupt Controller"]
end
subgraph subGraph1["AxVCpuHal Interface"]
HAL["AxVCpuHal trait"]
MEM["Memory Operations"]
ADDR["Address Translation"]
IRQ["Interrupt Handling"]
end
subgraph subGraph0["VCPU Core System"]
VCPU["AxVCpu"]
ARCH["AxArchVCpu implementations"]
end
ADDR --> MMU
ARCH --> HAL
FRAME --> KERNEL
HAL --> ADDR
HAL --> IRQ
HAL --> MEM
INTC --> KERNEL
IRQ --> INTC
MEM --> FRAME
MMU --> KERNEL
VCPU --> HAL
HAL Trait Methods and Host System Integration
Sources: src/hal.rs(L1 - L54)
Memory Management Functions
The HAL provides frame allocation and deallocation services required by the VCPU system for managing guest memory and virtualization structures:
| Function | Purpose | Parameters | Returns |
|---|---|---|---|
| alloc_frame | Allocates a physical memory frame | None | Option |
| dealloc_frame | Deallocates a physical memory frame | paddr: HostPhysAddr | None |
The alloc_frame function returns None when allocation fails, allowing the VCPU system to handle memory pressure gracefully. These functions work with HostPhysAddr types from the axaddrspace crate to maintain type safety for physical address operations.
sequenceDiagram
participant VCPUSystem as "VCPU System"
participant AxVCpuHal as "AxVCpuHal"
participant HostKernel as "Host Kernel"
VCPUSystem ->> AxVCpuHal: "alloc_frame()"
AxVCpuHal ->> HostKernel: "allocate physical frame"
HostKernel -->> AxVCpuHal: "physical address or error"
AxVCpuHal -->> VCPUSystem: "Option<HostPhysAddr>"
Note over VCPUSystem: "Use allocated frame for guest memory"
VCPUSystem ->> AxVCpuHal: "dealloc_frame(paddr)"
AxVCpuHal ->> HostKernel: "free physical frame"
HostKernel -->> AxVCpuHal: "completion"
AxVCpuHal -->> VCPUSystem: "return"
Memory Frame Allocation and Deallocation Flow
Sources: src/hal.rs(L5 - L17)
Address Translation Functions
Address translation is essential for converting between host physical and virtual addresses, supporting operations like accessing virtualization control structures and guest memory:
| Function | Purpose | Parameters | Returns |
|---|---|---|---|
| phys_to_virt | Convert physical to virtual address | paddr: HostPhysAddr | HostVirtAddr |
| virt_to_phys | Convert virtual to physical address | vaddr: HostVirtAddr | HostPhysAddr |
These functions enable the VCPU system to work with both physical addresses (for hardware configuration) and virtual addresses (for software access to data structures). The implementation relies on the host system's memory management unit and page table configuration.
Sources: src/hal.rs(L19 - L39)
Interrupt Handling Functions
The HAL provides basic interrupt handling capabilities, though the current implementation includes placeholder functionality:
| Function | Purpose | Default Behavior | Status |
|---|---|---|---|
| irq_fetch | Get current IRQ number | Returns0 | Default implementation |
| irq_hanlder | Dispatch IRQ to host | unimplemented!() | Requires implementation |
The irq_fetch function provides a default implementation returning 0, while irq_hanlder (note the typo in the function name) requires implementation by the host system. This design allows basic interrupt functionality while requiring host-specific interrupt dispatch logic.
Sources: src/hal.rs(L41 - L53)
Integration with Core Systems
The HAL integrates with several key components of the VCPU system and external dependencies:
flowchart TD
subgraph subGraph2["VCPU Core Components"]
VCPU["AxVCpu"]
ARCH["Architecture implementations"]
PERCPU["Per-CPU state"]
end
subgraph subGraph1["AxVCpuHal Implementation"]
TRAIT["AxVCpuHal trait"]
ALLOC["alloc_frame()"]
DEALLOC["dealloc_frame()"]
P2V["phys_to_virt()"]
V2P["virt_to_phys()"]
FETCH["irq_fetch()"]
HANDLER["irq_hanlder()"]
end
subgraph subGraph0["External Dependencies"]
AXADDR["axaddrspace"]
HOSTPHYS["HostPhysAddr"]
HOSTVIRT["HostVirtAddr"]
end
ARCH --> TRAIT
AXADDR --> HOSTPHYS
AXADDR --> HOSTVIRT
HOSTPHYS --> ALLOC
HOSTPHYS --> DEALLOC
HOSTPHYS --> P2V
HOSTVIRT --> V2P
PERCPU --> TRAIT
TRAIT --> ALLOC
TRAIT --> DEALLOC
TRAIT --> FETCH
TRAIT --> HANDLER
TRAIT --> P2V
TRAIT --> V2P
VCPU --> TRAIT
HAL Dependencies and Component Integration
The HAL serves as the bridge between architecture-agnostic VCPU operations and host-specific system services. By implementing the AxVCpuHal trait, different host environments can provide their own memory management and interrupt handling strategies while maintaining compatibility with the VCPU abstraction layer.
Sources: src/hal.rs(L1 - L4) src/hal.rs(L46 - L53)
Development and Build System
Relevant source files
This document covers the build system, dependency management, continuous integration pipeline, and development workflow for the axvcpu crate. It provides guidance for developers on how to build, test, and contribute to the project.
For information about the crate's external dependencies and integration with the ArceOS ecosystem, see Dependencies and Integration. For details about the testing strategy and CI/CD pipeline, see Testing and Continuous Integration.
Build System Overview
The axvcpu crate uses Cargo as its build system with a standard Rust library configuration. The project targets multiple architectures and runtime environments, from hosted Linux environments to bare-metal embedded systems.
Package Configuration
The crate is configured as a standard Rust library with Rust Edition 2024 support. The package metadata is defined in Cargo.toml(L1 - L5) with the package name axvcpu and version 0.1.0.
Build System Architecture
flowchart TD
subgraph subGraph2["Target Architectures"]
X86_64_LINUX["x86_64-unknown-linux-gnu"]
X86_64_NONE["x86_64-unknown-none"]
RISCV64["riscv64gc-unknown-none-elf"]
AARCH64["aarch64-unknown-none-softfloat"]
end
subgraph Dependencies["Dependencies"]
AXERRNO["axerrno 0.1.0"]
MEMADDR["memory_addr 0.3.1"]
PERCPU["percpu 0.2.0"]
AXADDRSPACE["axaddrspace (git)"]
end
subgraph subGraph0["Build System"]
CARGO["cargo build"]
TOML["Cargo.toml"]
SRC["src/"]
end
CARGO --> AARCH64
CARGO --> RISCV64
CARGO --> TOML
CARGO --> X86_64_LINUX
CARGO --> X86_64_NONE
SRC --> CARGO
TOML --> AXADDRSPACE
TOML --> AXERRNO
TOML --> MEMADDR
TOML --> PERCPU
Sources: Cargo.toml(L1 - L12) .github/workflows/ci.yml(L12)
Dependency Management
The crate has minimal external dependencies, focusing on core virtualization functionality:
| Dependency | Version | Purpose |
|---|---|---|
| axerrno | 0.1.0 | Error code definitions and handling |
| memory_addr | 0.3.1 | Memory address abstractions |
| percpu | 0.2.0 | Per-CPU variable management |
| axaddrspace | git | Address space management (ArceOS ecosystem) |
The axaddrspace dependency is pulled from the ArceOS hypervisor ecosystem via Git, indicating tight integration with the broader hypervisor framework Cargo.toml(L12)
Sources: Cargo.toml(L7 - L12)
Continuous Integration Pipeline
The CI system is implemented using GitHub Actions and supports multiple Rust toolchains and target architectures to ensure compatibility across different deployment scenarios.
CI/CD Pipeline Structure
flowchart TD
subgraph subGraph3["Documentation Job"]
DOCBUILD["cargo doc --no-deps"]
DEPLOY["JamesIves/github-pages-deploy-action@v4"]
end
subgraph subGraph2["CI Steps"]
CHECKOUT["actions/checkout@v4"]
RUSTUP["dtolnay/rust-toolchain@nightly"]
FORMAT["cargo fmt --check"]
CLIPPY["cargo clippy"]
BUILD["cargo build"]
TEST["cargo test"]
end
subgraph subGraph1["CI Job Matrix"]
TOOLCHAIN1["nightly-2024-12-25"]
TOOLCHAIN2["nightly"]
TARGET1["x86_64-unknown-linux-gnu"]
TARGET2["x86_64-unknown-none"]
TARGET3["riscv64gc-unknown-none-elf"]
TARGET4["aarch64-unknown-none-softfloat"]
end
subgraph subGraph0["Trigger Events"]
PUSH["push"]
PR["pull_request"]
end
BUILD --> TEST
CHECKOUT --> RUSTUP
CLIPPY --> BUILD
DOCBUILD --> DEPLOY
FORMAT --> CLIPPY
PR --> TOOLCHAIN1
PR --> TOOLCHAIN2
PUSH --> DOCBUILD
PUSH --> TOOLCHAIN1
PUSH --> TOOLCHAIN2
RUSTUP --> FORMAT
TARGET1 --> CHECKOUT
TOOLCHAIN1 --> TARGET1
TOOLCHAIN1 --> TARGET2
TOOLCHAIN1 --> TARGET3
TOOLCHAIN1 --> TARGET4
Sources: .github/workflows/ci.yml(L1 - L58)
Multi-Architecture Testing
The CI pipeline tests across four target architectures:
x86_64-unknown-linux-gnu: Hosted Linux environment for development and unit testingx86_64-unknown-none: Bare-metal x86_64 for hypervisor deploymentsriscv64gc-unknown-none-elf: Bare-metal RISC-V with GC extensionsaarch64-unknown-none-softfloat: Bare-metal ARM64 with software floating point
Unit tests are only executed on the x86_64-unknown-linux-gnu target .github/workflows/ci.yml(L29 - L30) since it's the only hosted environment that supports standard library features.
Code Quality Enforcement
The CI pipeline enforces code quality through multiple checks:
- Format checking:
cargo fmt --all -- --check.github/workflows/ci.yml(L23) - Linting:
cargo clippywith custom allow rules .github/workflows/ci.yml(L25) - Build verification: Full compilation for all target architectures .github/workflows/ci.yml(L27)
- Documentation:
cargo docwith broken link detection .github/workflows/ci.yml(L49)
Sources: .github/workflows/ci.yml(L22 - L30)
Documentation Generation
The project includes automated documentation generation and deployment:
Documentation Build Process
Documentation is built using cargo doc --no-deps --all-features .github/workflows/ci.yml(L49) with strict link checking enabled through RUSTDOCFLAGS .github/workflows/ci.yml(L40) The build process:
- Generates API documentation for all public interfaces
- Creates an index redirect page pointing to the main crate documentation
- Validates all intra-document links are functional
- Deploys to GitHub Pages on the default branch
GitHub Pages Deployment
Documentation is automatically deployed to GitHub Pages using the JamesIves/github-pages-deploy-action@v4 action .github/workflows/ci.yml(L53) The deployment occurs only on pushes to the default branch, ensuring stable documentation reflects the latest released version.
Sources: .github/workflows/ci.yml(L32 - L57)
Development Workflow
Local Development Setup
Developers should use Rust nightly toolchain nightly-2024-12-25 or later with the following components:
rust-src: For bare-metal target compilationclippy: For lintingrustfmt: For code formatting
Build Commands
Standard development commands:
# Format code
cargo fmt --all
# Run linting
cargo clippy --all-features
# Build for specific target
cargo build --target x86_64-unknown-none --all-features
# Run tests (hosted target only)
cargo test --target x86_64-unknown-linux-gnu
Version Control
The project maintains a .gitignore configuration .gitignore(L1 - L18) that excludes:
- Build artifacts (
/target,*.asm,*.img,*.bin,*.elf) - IDE settings (
/.vscode) - Platform-specific files (
.DS_Store) Cargo.locksinceaxvcpuis a library crate
Sources: .github/workflows/ci.yml(L15 - L27) .gitignore(L1 - L18)
Dependencies and Integration
Relevant source files
This document explains the axvcpu crate's external dependencies and how it integrates with the ArceOS hypervisor ecosystem. It covers the four core dependencies (axerrno, memory_addr, percpu, axaddrspace) and describes the integration patterns that enable axvcpu to function as a virtualization abstraction layer.
For information about the actual VCPU management functionality, see Core VCPU Management. For details about testing and CI processes, see Testing and Continuous Integration.
Dependency Architecture
The axvcpu crate relies on four core dependencies that provide foundational abstractions for error handling, memory management, per-CPU operations, and address space management.
Dependency Graph
flowchart TD
subgraph subGraph1["axvcpu Core Modules"]
lib_rs["lib.rs"]
vcpu_rs["vcpu.rs"]
exit_rs["exit.rs"]
arch_vcpu_rs["arch_vcpu.rs"]
hal_rs["hal.rs"]
percpu_rs["percpu.rs"]
end
subgraph subGraph0["External Crates"]
axerrno["axerrnov0.1.0"]
memory_addr["memory_addrv0.3.1"]
percpu_crate["percpuv0.2.0"]
axaddrspace["axaddrspacegit dependency"]
end
axaddrspace --> lib_rs
axerrno --> lib_rs
hal_rs --> axaddrspace
lib_rs --> arch_vcpu_rs
lib_rs --> exit_rs
lib_rs --> hal_rs
lib_rs --> percpu_rs
lib_rs --> vcpu_rs
memory_addr --> lib_rs
percpu_crate --> lib_rs
percpu_rs --> percpu_crate
vcpu_rs --> arch_vcpu_rs
vcpu_rs --> exit_rs
Sources: Cargo.toml(L7 - L12)
Core Dependencies Analysis
Error Handling Foundation - axerrno
The axerrno crate provides standardized error handling across the ArceOS ecosystem. axvcpu uses this for:
| Component | Usage Pattern | Error Types |
|---|---|---|
| VCPU Operations | State transition failures | AxError::InvalidArg |
| Exit Processing | Hardware abstraction errors | AxError::Unsupported |
| Memory Management | Address space operations | AxError::NoMemory |
| Architecture Interface | Platform-specific failures | AxError::BadState |
Memory Abstractions - memory_addr
The memory_addr crate provides type-safe memory address abstractions used throughout the VCPU management system:
flowchart TD
subgraph subGraph1["axvcpu Usage"]
exit_handling["Exit Handling"]
mmio_ops["MMIO Operations"]
guest_memory["Guest Memory"]
host_memory["Host Memory"]
end
subgraph subGraph0["memory_addr Types"]
VirtAddr["VirtAddr"]
PhysAddr["PhysAddr"]
Page["Page"]
Frame["Frame"]
end
Frame --> host_memory
Page --> guest_memory
PhysAddr --> guest_memory
PhysAddr --> host_memory
VirtAddr --> exit_handling
VirtAddr --> mmio_ops
Sources: Cargo.toml(L9)
Per-CPU State Management - percpu
The percpu crate enables efficient per-CPU variable management, critical for hypervisor operations where each physical CPU core needs isolated virtualization state:
| Per-CPU Component | Purpose | Access Pattern |
|---|---|---|
| VCPU Binding State | Track which VCPU runs on which CPU | Read/Write during scheduling |
| Hardware Context | Store virtualization hardware state | Save/Restore on context switch |
| Exit Statistics | Performance monitoring data | Increment on VM exits |
Address Space Integration - axaddrspace
The axaddrspace dependency is a git-based dependency providing address space management abstractions specifically designed for the ArceOS hypervisor:
flowchart TD
subgraph subGraph1["axvcpu Integration Points"]
AxVCpuHal["AxVCpuHal trait"]
hal_impl["hal.rs implementation"]
guest_mappings["Guest Memory Mappings"]
host_mappings["Host Memory Mappings"]
end
subgraph subGraph0["axaddrspace Abstractions"]
AddrSpace["AddrSpace"]
Mapping["Mapping"]
Permission["Permission"]
MemBackend["MemBackend"]
end
AddrSpace --> AxVCpuHal
AxVCpuHal --> hal_impl
Mapping --> hal_impl
MemBackend --> host_mappings
Permission --> guest_mappings
hal_impl --> guest_mappings
hal_impl --> host_mappings
Sources: Cargo.toml(L12)
Integration Architecture
Trait-Based Integration Pattern
axvcpu uses trait-based abstractions to integrate with its dependencies while maintaining architectural independence:
flowchart TD
subgraph subGraph2["Implementation Layer"]
arch_impl["Architecture implementations"]
hal_impl["HAL implementations"]
percpu_impl["Per-CPU implementations"]
end
subgraph subGraph1["axvcpu Trait Definitions"]
AxArchVCpu["AxArchVCpu trait"]
AxVCpuHal["AxVCpuHal trait"]
AxPerCpu["AxPerCpu trait"]
end
subgraph subGraph0["Dependency Traits"]
ErrorTrait["axerrno::AxResult"]
AddrTrait["memory_addr::VirtAddr"]
PerCpuTrait["percpu::PerCpu"]
SpaceTrait["axaddrspace::AddrSpace"]
end
AddrTrait --> AxArchVCpu
AxArchVCpu --> arch_impl
AxPerCpu --> percpu_impl
AxVCpuHal --> hal_impl
ErrorTrait --> AxArchVCpu
ErrorTrait --> AxVCpuHal
PerCpuTrait --> AxPerCpu
SpaceTrait --> AxVCpuHal
Sources: Cargo.toml(L7 - L12)
Build System Integration
The Cargo.toml configuration establishes the integration foundation:
| Dependency Type | Version Strategy | Integration Method |
|---|---|---|
| axerrno | Semantic versioning (0.1.0) | Standard crates.io dependency |
| memory_addr | Semantic versioning (0.3.1) | Standard crates.io dependency |
| percpu | Semantic versioning (0.2.0) | Standard crates.io dependency |
| axaddrspace | Git dependency | ArceOS ecosystem integration |
The git dependency on axaddrspace indicates tight coupling with the ArceOS hypervisor ecosystem, while the semantic versioned dependencies provide stable foundations.
Ecosystem Integration Patterns
ArceOS Hypervisor Integration
flowchart TD
subgraph subGraph2["Supporting Ecosystem"]
axaddrspace_sys["axaddrspace system"]
error_system["axerrno error system"]
percpu_system["percpu management"]
end
subgraph subGraph1["axvcpu Interface"]
AxVCpu["AxVCpu"]
AxVCpuExitReason["AxVCpuExitReason"]
vcpu_state["VCpu state machine"]
end
subgraph subGraph0["ArceOS Hypervisor"]
hypervisor["Hypervisor Core"]
vm_manager["VM Manager"]
scheduler["VCPU Scheduler"]
end
AxVCpu --> AxVCpuExitReason
AxVCpu --> axaddrspace_sys
AxVCpu --> error_system
AxVCpu --> percpu_system
AxVCpu --> vcpu_state
hypervisor --> AxVCpu
scheduler --> AxVCpu
vm_manager --> AxVCpu
Multi-Architecture Support Integration
The dependency structure enables cross-platform virtualization support:
| Architecture | Integration Pattern | Dependency Usage |
|---|---|---|
| x86_64 | VMX/VT-x hardware abstraction | memory_addrfor guest physical addresses |
| ARM64 | EL2 virtualization | percpufor exception level state |
| RISC-V | H-extension support | axaddrspacefor guest address translation |
Sources: Cargo.toml(L1 - L12)
Version Management and Compatibility
The dependency version strategy ensures compatibility across the ArceOS ecosystem:
- Stable dependencies:
axerrno,memory_addr,percpuuse semantic versioning for API stability - Ecosystem dependency:
axaddrspaceuses git dependency for coordinated development - Edition alignment: All components target Rust 2024 edition for language feature consistency
This integration architecture enables axvcpu to provide a stable virtualization abstraction while leveraging specialized components for error handling, memory management, and per-CPU operations within the broader ArceOS hypervisor ecosystem.
Sources: Cargo.toml(L1 - L12)
Testing and Continuous Integration
Relevant source files
This page documents the comprehensive testing and continuous integration strategy for the axvcpu crate, including the CI/CD pipeline architecture, multi-architecture testing matrix, documentation generation, and development workflows. The testing framework ensures code quality and compatibility across multiple CPU architectures and Rust toolchain versions.
For information about build dependencies and external integrations, see Dependencies and Integration.
CI/CD Pipeline Overview
The axvcpu crate employs a GitHub Actions-based CI/CD pipeline that validates code quality, builds across multiple target architectures, and automatically generates and deploys documentation. The pipeline is configured to run on both push events and pull requests, ensuring comprehensive validation of all code changes.
CI Pipeline Architecture
flowchart TD A["GitHub Push/PR"] B["CI Job Matrix"] C["Documentation Job"] D["rust-toolchain Matrix"] E["targets Matrix"] F["nightly-2024-12-25"] G["nightly"] H["x86_64-unknown-linux-gnu"] I["x86_64-unknown-none"] J["riscv64gc-unknown-none-elf"] K["aarch64-unknown-none-softfloat"] L["Code Quality Checks"] M["cargo fmt --check"] N["cargo clippy"] O["cargo build"] P["cargo test"] Q["Unit Tests Enabled"] R["Build Only"] S["cargo doc --no-deps"] T["GitHub Pages Deploy"] U["target/doc Output"] A --> B A --> C B --> D B --> E C --> S C --> T D --> F D --> G E --> H E --> I E --> J E --> K F --> L G --> L H --> Q I --> R J --> R K --> R L --> M L --> N L --> O L --> P O --> R P --> Q S --> U U --> T
Sources: .github/workflows/ci.yml(L1 - L58)
The pipeline consists of two primary jobs that run in parallel, ensuring both functional correctness and documentation quality.
Multi-Architecture Testing Matrix
The CI system validates axvcpu across multiple CPU architectures and Rust toolchain versions to ensure broad compatibility with the ArceOS hypervisor ecosystem.
Target Architecture Configuration
| Target Architecture | Purpose | Testing Level |
|---|---|---|
| x86_64-unknown-linux-gnu | Host development and unit testing | Full testing including unit tests |
| x86_64-unknown-none | Bare-metal x86_64 hypervisor | Build validation and static analysis |
| riscv64gc-unknown-none-elf | RISC-V hypervisor support | Build validation and static analysis |
| aarch64-unknown-none-softfloat | ARM64 hypervisor support | Build validation and static analysis |
Rust Toolchain Matrix
The pipeline tests against two Rust toolchain configurations:
nightly-2024-12-25: Pinned stable nightly for reproducible buildsnightly: Latest nightly for early detection of compatibility issues
Both toolchains include essential components: rust-src, clippy, and rustfmt.
Sources: .github/workflows/ci.yml(L11 - L12) .github/workflows/ci.yml(L16 - L19)
Code Quality and Validation Pipeline
Static Analysis and Formatting
flowchart TD A["Source Code"] B["rustfmt Check"] C["clippy Analysis"] D["Compilation"] E["Unit Tests"] F["Format Validation"] G["Lint Analysis"] H["Multi-Arch Builds"] I["Test Execution"] J["CI Pass/Fail"] A --> B B --> C B --> F C --> D C --> G D --> E D --> H E --> I F --> J G --> J H --> J I --> J
Sources: .github/workflows/ci.yml(L22 - L30)
The validation pipeline enforces code quality through multiple stages:
Format Checking
- Command:
cargo fmt --all -- --check - Purpose: Ensures consistent code formatting across the entire codebase
- Failure Mode: Pipeline fails if any files are not properly formatted
Static Analysis
- Command:
cargo clippy --target ${{ matrix.targets }} --all-features -- -A clippy::new_without_default - Configuration: Allows
new_without_defaultlint to accommodate API design patterns - Scope: Runs across all target architectures with full feature enablement
Build Validation
- Command:
cargo build --target ${{ matrix.targets }} --all-features - Coverage: All target architectures in the matrix
- Features: Full feature set enabled to validate complete functionality
Unit Testing
- Command:
cargo test --target ${{ matrix.targets }} -- --nocapture - Restriction: Only runs on
x86_64-unknown-linux-gnutarget - Output: Detailed test output with
--nocaptureflag
Sources: .github/workflows/ci.yml(L23 - L30)
Documentation Generation and Deployment
Documentation Pipeline
flowchart TD A["Documentation Job"] B["Rust Toolchain Setup"] C["cargo doc Build"] D["Index Page Generation"] E["Branch Check"] F["Default Branch?"] G["Deploy to gh-pages"] H["Build Only"] I["RUSTDOCFLAGS Validation"] J["Broken Links Check"] K["Missing Docs Check"] L["GitHub Pages Site"] M["Pipeline Failure"] A --> B B --> C C --> D C --> I D --> E E --> F F --> G F --> H G --> L I --> J I --> K J --> M K --> M
Sources: .github/workflows/ci.yml(L32 - L58)
Documentation Configuration
The documentation build process includes strict validation and automatic deployment:
Rustdoc Validation
- Environment:
RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs - Validation: Treats broken internal links and missing documentation as errors
- Purpose: Ensures comprehensive and accurate API documentation
Build Process
- Command:
cargo doc --no-deps --all-features - Scope: Generates documentation without external dependencies
- Features: Includes all feature flags for complete API coverage
Index Generation
printf '<meta http-equiv="refresh" content="0;url=%s/index.html">' $(cargo tree | head -1 | cut -d' ' -f1) > target/doc/index.html
This automatically creates a redirect from the root documentation page to the main crate documentation.
Deployment Strategy
- Target: GitHub Pages via
gh-pagesbranch - Trigger: Only on default branch pushes
- Method: Single-commit deployment using
JamesIves/github-pages-deploy-action@v4 - Source:
target/docdirectory
Sources: .github/workflows/ci.yml(L40) .github/workflows/ci.yml(L48 - L57)
Development Environment and Build Artifacts
Ignored Build Artifacts
The development environment excludes specific build artifacts from version control:
| Artifact Type | Pattern | Purpose |
|---|---|---|
| Build Output | /target | Rust compilation artifacts |
| Assembly Files | *.asm | Generated assembly code |
| Binary Images | .img,.bin,*.elf | Bootable images and binaries |
| Test Output | actual.out,qemu.log | Runtime test artifacts |
| Lock Files | Cargo.lock | Library crates exclude lock files |
| IDE Settings | /.vscode,.DS_Store | Development environment files |
Sources: .gitignore(L2 - L18)
Testing Strategy and Coverage
The testing approach balances comprehensive validation with practical constraints:
Architecture-Specific Testing
- Full Testing: Limited to
x86_64-unknown-linux-gnufor unit test execution - Build Validation: All target architectures undergo compilation and static analysis
- Rationale: Bare-metal targets cannot execute standard unit tests
Error Handling and Validation
- Continue on Error: Documentation builds continue on non-default branches
- Fail Fast: CI jobs use
fail-fast: falseto complete all matrix combinations - Branch Protection: Only default branch builds trigger documentation deployment
The testing framework ensures that axvcpu maintains compatibility across the supported architectures while providing comprehensive validation of core functionality through the Linux-based test environment.
Sources: .github/workflows/ci.yml(L8 - L9) .github/workflows/ci.yml(L29 - L30) .github/workflows/ci.yml(L47)
Licensing and Legal
Relevant source files
This document outlines the multiple licensing options available for the axvcpu crate and provides guidance on legal compliance requirements for users, contributors, and distributors. The axvcpu crate implements a unique multi-license approach that offers flexibility for different use cases and jurisdictions.
For information about building and dependency management, see Dependencies and Integration. For details about the CI/CD pipeline and testing procedures, see Testing and Continuous Integration.
Multi-License Framework
The axvcpu crate is distributed under four different open-source licenses, allowing users to choose the most appropriate licensing terms for their specific use case. This approach maximizes compatibility with different projects and jurisdictions while maintaining open-source principles.
License Structure Overview
flowchart TD
subgraph subGraph1["License Categories"]
PERMISSIVE["Permissive Licenses"]
COPYLEFT["Copyleft Licenses"]
REGIONAL["Regional Variants"]
end
subgraph subGraph0["AxVCpu Multi-License Options"]
AXVCPU["axvcpu Crate"]
APACHE["Apache License 2.0LICENSE.Apache2"]
GPL["GNU GPL v3LICENSE.GPLv3"]
MULAN_PSL["Mulan PSL v2LICENSE.MulanPSL2"]
MULAN_PUB["Mulan PubL v2LICENSE.MulanPubL2"]
end
APACHE --> PERMISSIVE
AXVCPU --> APACHE
AXVCPU --> GPL
AXVCPU --> MULAN_PSL
AXVCPU --> MULAN_PUB
GPL --> COPYLEFT
MULAN_PSL --> PERMISSIVE
MULAN_PSL --> REGIONAL
MULAN_PUB --> COPYLEFT
MULAN_PUB --> REGIONAL
Sources: LICENSE.Apache2(L1 - L202) LICENSE.GPLv3(L1 - L675) LICENSE.MulanPSL2(L1 - L128) LICENSE.MulanPubL2(L1 - L184)
License Selection Matrix
| License | Type | Patent Grant | Attribution Required | Source Disclosure | Network Copyleft |
|---|---|---|---|---|---|
| Apache 2.0 | Permissive | Yes | Yes | No | No |
| GPL v3 | Copyleft | Yes | Yes | Yes | No |
| Mulan PSL v2 | Permissive | Yes | Yes | No | No |
| Mulan PubL v2 | Network Copyleft | Yes | Yes | Yes | Yes |
Individual License Summaries
Apache License 2.0
The Apache License 2.0 is a permissive license that allows broad usage rights while requiring attribution and patent grant protections.
Key Requirements:
- Retain copyright notices and license text
- Include copy of license with distributions
- State changes made to files
- Provide patent grant termination on patent litigation
Key Permissions:
- Commercial use, distribution, modification
- Private use, patent use
- Sublicensing under different terms
Sources: LICENSE.Apache2(L66 - L88) LICENSE.Apache2(L189 - L201)
GNU General Public License v3
GPL v3 is a strong copyleft license ensuring derivative works remain open source.
Key Requirements:
- Provide source code for distributed binaries
- License entire work under GPL v3
- Include installation information for user products
- No additional restrictions on recipients
Key Features:
- Anti-tivoization provisions
- Patent retaliation clauses
- International scope considerations
Sources: LICENSE.GPLv3(L154 - L177) LICENSE.GPLv3(L245 - L295)
Mulan Permissive Software License v2
Mulan PSL v2 is a permissive license designed for Chinese jurisdiction with bilingual terms.
Key Features:
- Perpetual, worldwide, royalty-free rights
- Patent grant with defensive termination
- No trademark license granted
- Chinese/English bilingual text with Chinese precedence
Network Service Provision: Unlike traditional software distribution, includes provisions for providing services over networks.
Sources: LICENSE.MulanPSL2(L84 - L90) LICENSE.MulanPSL2(L104 - L106)
Mulan Public License v2
Mulan PubL v2 extends copyleft requirements to network services, similar to AGPL v3.
Key Requirements:
- Source code provision for network services
- Copyleft applies to derivative works
- AGPLv3 compatibility provisions
- Chinese jurisdiction optimizations
Distribution Mechanisms:
- Direct source code provision
- Download link (minimum 3 years)
- Written offer for source code
Sources: LICENSE.MulanPubL2(L118 - L143) LICENSE.MulanPubL2(L154 - L156)
License Selection Guidance
Decision Flow for License Choice
flowchart TD START["Choosing License for AxVCpu Usage"] JURISDICTION["PrimaryJurisdiction?"] COPYLEFT["RequireCopyleft?"] NETWORK["NetworkServices?"] PATENT["PatentConcerns?"] MULAN_REGION["Consider Mulan Licenses"] WESTERN_REGION["Consider Apache/GPL"] PERMISSIVE_CHOICE["Apache 2.0 or Mulan PSL v2"] MULAN_PUB["Mulan PubL v2"] GPL["GPL v3"] APACHE["Apache 2.0(Explicit Patent Grant)"] EITHER_PERMISSIVE["Apache 2.0 orMulan PSL v2"] COPYLEFT --> NETWORK COPYLEFT --> PERMISSIVE_CHOICE JURISDICTION --> MULAN_REGION JURISDICTION --> WESTERN_REGION MULAN_REGION --> COPYLEFT NETWORK --> GPL NETWORK --> MULAN_PUB PATENT --> APACHE PATENT --> EITHER_PERMISSIVE PERMISSIVE_CHOICE --> PATENT START --> JURISDICTION WESTERN_REGION --> COPYLEFT
Sources: LICENSE.Apache2(L73 - L87) LICENSE.GPLv3(L471 - L490) LICENSE.MulanPSL2(L88 - L90) LICENSE.MulanPubL2(L122 - L124)
Use Case Recommendations
| Use Case | Recommended License | Rationale |
|---|---|---|
| Commercial hypervisor products | Apache 2.0 | Permissive, well-understood patent grants |
| Open source hypervisor projects | GPL v3 | Ensures derivative works remain open |
| Cloud service platforms | Mulan PubL v2 | Network copyleft prevents service exploitation |
| Research and academia | Apache 2.0 or Mulan PSL v2 | Maximum flexibility for derivative research |
| Chinese market deployment | Mulan PSL v2 or Mulan PubL v2 | Jurisdiction-optimized terms |
Compliance Requirements
Attribution and Notice Requirements
All licenses require preservation of copyright notices and license attribution:
flowchart TD
subgraph subGraph1["Distribution Formats"]
SOURCE["Source Distribution"]
BINARY["Binary Distribution"]
SERVICE["Network Service"]
end
subgraph subGraph0["Required Attribution Elements"]
COPYRIGHT["Copyright Notices"]
LICENSE_TEXT["License Text Copy"]
CHANGES["Modification Notices"]
PATENT_NOTICE["Patent Disclaimers"]
TRADEMARK["Trademark Disclaimers"]
end
CHANGES --> SOURCE
COPYRIGHT --> BINARY
COPYRIGHT --> SERVICE
COPYRIGHT --> SOURCE
LICENSE_TEXT --> BINARY
LICENSE_TEXT --> SOURCE
PATENT_NOTICE --> BINARY
TRADEMARK --> SERVICE
Sources: LICENSE.Apache2(L94 - L105) LICENSE.GPLv3(L197 - L203) LICENSE.MulanPSL2(L98) LICENSE.MulanPubL2(L134 - L135)
Source Code Disclosure Requirements
For copyleft licenses (GPL v3, Mulan PubL v2), source code must be provided:
| Scenario | GPL v3 Requirement | Mulan PubL v2 Requirement |
|---|---|---|
| Binary distribution | Corresponding source | Corresponding source code |
| Network services | No requirement | Source for service platform |
| Derivative works | Full source under GPL | Source under Mulan PubL |
| Installation info | User products only | Not specified |
| Duration | As long as distributed | Minimum 3 years |
Sources: LICENSE.GPLv3(L245 - L295) LICENSE.MulanPubL2(L136 - L142)
Legal Disclaimers and Limitations
Warranty Disclaimers
All four licenses disclaim warranties and limit liability:
Common Disclaimers:
- No warranties of any kind (express or implied)
- No guarantee of fitness for particular purpose
- No liability for damages from use or inability to use
- "AS IS" basis provision
Patent Litigation Termination: All licenses include patent retaliation clauses that terminate patent grants if the licensee initiates patent litigation against contributors.
Compliance Monitoring
flowchart TD
subgraph subGraph0["Compliance Checklist"]
CHECK_COPYRIGHT["✓ Copyright notices preserved"]
CHECK_LICENSE["✓ License text included"]
CHECK_CHANGES["✓ Changes documented"]
CHECK_SOURCE["✓ Source available (if required)"]
CHECK_PATENTS["✓ Patent grants understood"]
end
USAGE["AxVCpu Usage"]
LICENSE_CHOICE["Select Applicable License"]
TRACK_CHANGES["Track Modifications"]
ATTRIBUTION["Maintain Attribution"]
SOURCE_MGMT["Source Code Management"]
ATTRIBUTION --> SOURCE_MGMT
LICENSE_CHOICE --> TRACK_CHANGES
SOURCE_MGMT --> CHECK_CHANGES
SOURCE_MGMT --> CHECK_COPYRIGHT
SOURCE_MGMT --> CHECK_LICENSE
SOURCE_MGMT --> CHECK_PATENTS
SOURCE_MGMT --> CHECK_SOURCE
TRACK_CHANGES --> ATTRIBUTION
USAGE --> LICENSE_CHOICE
Sources: LICENSE.Apache2(L143 - L152) LICENSE.GPLv3(L591 - L598) LICENSE.MulanPSL2(L100 - L102) LICENSE.MulanPubL2(L158 - L160)
International Considerations
The inclusion of Mulan licenses provides specific advantages for users in Chinese jurisdiction, as these licenses are designed with Chinese legal framework considerations and include authoritative Chinese text alongside English translations.
Language Precedence:
- Mulan licenses: Chinese version takes precedence in case of conflicts
- Apache/GPL: English versions are authoritative
Sources: LICENSE.MulanPSL2(L104 - L106) LICENSE.MulanPubL2(L162 - L164)
Overview
Relevant source files
The axaddrspace crate provides guest virtual machine address space management capabilities for the ArceOS-Hypervisor system. This module handles the translation between guest physical addresses and host physical addresses through nested page tables, supports multiple memory mapping strategies, and provides a hardware abstraction layer for cross-platform virtualization support.
The crate enables hypervisors to manage guest VM memory efficiently while maintaining isolation between guest and host address spaces. For specific details about nested page table implementations, see Nested Page Tables. For information about memory mapping strategies, see Memory Mapping Backends.
Purpose and Scope
The axaddrspace crate serves as the core memory management component for guest VMs in the ArceOS-Hypervisor ecosystem. It abstracts the complexities of address translation, page table management, and platform-specific virtualization extensions into a unified API.
Core Responsibilities:
- Guest-to-host address translation through nested page tables
- Memory region management with configurable mapping backends
- Hardware abstraction for memory operations across architectures
- Page fault handling for dynamic memory allocation
- Device address space management
Sources: Cargo.toml(L1 - L25) src/lib.rs(L1 - L5)
System Architecture
The following diagram illustrates the high-level architecture and key code entities within the axaddrspace crate:
System Component Overview
flowchart TD
subgraph subGraph5["Device Support"]
Device["device/mod.rsdevice_addr.rs"]
end
subgraph subGraph4["Hardware Abstraction"]
HAL["hal.rsAxMmHal"]
Frame["frame.rsPhysFrame<H>"]
end
subgraph subGraph3["Nested Page Tables"]
NptMod["npt/mod.rsNestedPageTable<H>"]
ArchSel["npt/arch/mod.rs"]
X86NPT["npt/arch/x86_64.rsExtendedPageTable"]
ArmNPT["npt/arch/aarch64.rsNestedPageTable"]
RiscvNPT["npt/arch/riscv.rsNestedPageTable"]
end
subgraph subGraph2["Memory Backends"]
Backend["backend/mod.rsBackend<H>"]
Linear["backend/linear.rsLinear"]
Alloc["backend/alloc.rsAlloc"]
end
subgraph subGraph1["Address Management"]
AddrMod["addr.rsGuestPhysAddrHostPhysAddrHostVirtAddr"]
AddrSpace["address_space/AddrSpace<H>MemorySet"]
end
subgraph subGraph0["Core API Layer"]
LibRS["lib.rsNestedPageFaultInfoAxMmHalPhysFrame"]
end
AddrSpace --> Backend
AddrSpace --> NptMod
Alloc --> HAL
ArchSel --> ArmNPT
ArchSel --> RiscvNPT
ArchSel --> X86NPT
Backend --> Alloc
Backend --> Linear
Frame --> HAL
LibRS --> AddrMod
LibRS --> AddrSpace
LibRS --> Device
LibRS --> Frame
LibRS --> HAL
NptMod --> ArchSel
Sources: src/lib.rs(L10 - L22) Cargo.toml(L1 - L25)
Key Components
Address Translation System
The crate implements a two-stage address translation system where guest virtual addresses are first translated to guest physical addresses by the guest OS, then guest physical addresses are translated to host physical addresses by the hypervisor's nested page tables.
Address Type Hierarchy
flowchart TD
subgraph subGraph2["Translation Mechanisms"]
GuestPT["Guest Page Tables(Guest OS managed)"]
NPT["NestedPageTable<H>(Hypervisor managed)"]
HAL["AxMmHalphys_to_virt()virt_to_phys()"]
end
subgraph subGraph1["Host Address Space"]
HPA["HostPhysAddr"]
HVA["HostVirtAddr"]
end
subgraph subGraph0["Guest Address Space"]
GVA["Guest Virtual Address"]
GPA["GuestPhysAddr"]
end
GPA --> NPT
GVA --> GuestPT
GuestPT --> GPA
HAL --> HVA
HPA --> HAL
NPT --> HPA
Sources: src/lib.rs(L17) src/lib.rs(L26 - L33)
Memory Management Framework
The AddrSpace<H> struct serves as the central coordinator for memory management operations, utilizing different backend strategies and maintaining nested page table state.
| Component | Type | Purpose |
|---|---|---|
| AddrSpace | Main struct | Coordinates memory operations and page table management |
| MemorySet<Backend | Memory regions | Manages collections of mapped memory areas |
| Backend | Strategy enum | Selects between Linear and Alloc mapping strategies |
| NestedPageTable | Page table | Architecture-specific guest-to-host translation |
| NestedPageFaultInfo | Fault handler | Provides context for page fault resolution |
Sources: src/lib.rs(L18) src/lib.rs(L26 - L33)
Hardware Abstraction Layer
The AxMmHal trait provides platform-independent memory operations that must be implemented by the underlying hypervisor platform. The PhysFrame<H> type implements RAII semantics for physical memory frame management.
Sources: src/lib.rs(L20 - L21)
Error Handling
The crate integrates with the ArceOS error handling system through the mapping_err_to_ax_err function, which converts MappingError types from the memory_set crate into AxError types.
Sources: src/lib.rs(L23 - L24) src/lib.rs(L35 - L42)
Dependencies and Integration
The crate builds upon several foundational ArceOS modules:
memory_addr: Provides base address type definitionsmemory_set: Supplies memory region management primitivespage_table_entry: Offers page table entry abstractionspage_table_multiarch: Enables multi-architecture page table supportaxerrno: Provides standardized error types
Sources: Cargo.toml(L19 - L24)
Feature Configuration
The crate supports architecture-specific features through Cargo feature flags:
arm-el2: Enables ARM EL2 (hypervisor) mode support (default)
Sources: Cargo.toml(L7 - L9)
Core Architecture
Relevant source files
This document explains the fundamental architectural components of the axaddrspace crate and how they work together to provide address space management for guest VMs in ArceOS-Hypervisor. The core architecture handles the translation between guest and host address spaces, manages memory mappings with different backend strategies, and provides hardware-abstracted nested page table support.
For detailed information about specific address types and their usage, see Address Types and Spaces. For implementation details of nested page tables, see Nested Page Tables. For memory mapping strategies, see Memory Mapping Backends.
System Overview
The axaddrspace crate is organized around several key architectural components that work together to provide comprehensive guest address space management:
Core System Architecture
flowchart TD
subgraph subGraph4["Memory Backends"]
Backend["Backend implementations"]
end
subgraph subGraph3["Nested Page Tables"]
NPT["npt/"]
end
subgraph subGraph2["Hardware Abstraction"]
HAL["hal.rs (AxMmHal)"]
Frame["frame.rs (PhysFrame)"]
end
subgraph subGraph1["Address Management"]
AddrMod["addr.rs"]
AddrSpace["address_space/"]
end
subgraph subGraph0["Core Exports (lib.rs)"]
LibCore["lib.rs"]
NPTFault["NestedPageFaultInfo"]
end
subgraph subGraph5["Device Support"]
Device["device/"]
end
AddrMod --> AddrSpace
AddrMod --> NPT
AddrSpace --> Backend
AddrSpace --> NPT
Backend --> HAL
Frame --> HAL
LibCore --> AddrMod
LibCore --> AddrSpace
LibCore --> Frame
LibCore --> HAL
LibCore --> NPTFault
Sources: src/lib.rs(L1 - L43)
Core Components
Address Type System
The foundation of the architecture is a comprehensive address type system that distinguishes between different address spaces and contexts:
| Address Type | Purpose | Definition |
|---|---|---|
| HostVirtAddr | Host virtual addresses | Type alias toVirtAddr |
| HostPhysAddr | Host physical addresses | Type alias toPhysAddr |
| GuestVirtAddr | Guest virtual addresses | Custom usize-based type |
| GuestPhysAddr | Guest physical addresses | Custom usize-based type |
Address Type Relationships
flowchart TD
subgraph subGraph2["Address Ranges"]
GVAR["GuestVirtAddrRange"]
GPAR["GuestPhysAddrRange"]
end
subgraph subGraph1["Guest Address Space"]
GVA["GuestVirtAddr"]
GPA["GuestPhysAddr"]
end
subgraph subGraph0["Host Address Space"]
HVA["HostVirtAddr"]
HPA["HostPhysAddr"]
end
GPA --> GPAR
GPA --> HPA
GVA --> GPA
GVA --> GVAR
HVA --> HPA
Sources: src/addr.rs(L1 - L31)
Hardware Abstraction Layer
The AxMmHal trait provides a hardware-abstracted interface for memory management operations, enabling platform-independent code while supporting architecture-specific optimizations.
HAL and Frame Management Integration
flowchart TD
subgraph subGraph2["Backend Integration"]
AllocBackend["Alloc Backend"]
LinearBackend["Linear Backend"]
end
subgraph subGraph1["PhysFrame RAII Management"]
Frame["PhysFrame<H>"]
FrameAlloc["alloc() / alloc_zero()"]
FrameDrop["Drop implementation"]
end
subgraph subGraph0["AxMmHal Trait Interface"]
HAL["AxMmHal<H>"]
AllocFrame["alloc_frame()"]
DeallocFrame["dealloc_frame()"]
PhysToVirt["phys_to_virt()"]
VirtToPhys["virt_to_phys()"]
end
AllocBackend --> Frame
Frame --> FrameAlloc
Frame --> FrameDrop
FrameAlloc --> AllocFrame
FrameDrop --> DeallocFrame
HAL --> AllocFrame
HAL --> DeallocFrame
HAL --> PhysToVirt
HAL --> VirtToPhys
LinearBackend --> PhysToVirt
Sources: src/lib.rs(L20 - L21) src/hal.rs src/frame.rs
Nested Page Fault Handling
The architecture includes a structured approach to handling nested page faults that occur during guest memory access:
pub struct NestedPageFaultInfo {
pub access_flags: MappingFlags,
pub fault_guest_paddr: GuestPhysAddr,
}
This structure captures the essential information needed to resolve page faults, including the type of memory access that caused the fault and the guest physical address that was being accessed.
Sources: src/lib.rs(L26 - L33)
Component Interaction Flow
The core architecture supports a layered interaction model where each component has clearly defined responsibilities:
Memory Management Flow
flowchart TD
subgraph subGraph4["Fault Handling"]
PageFault["NestedPageFaultInfo"]
FaultHandler["Backend-specific Handler"]
end
subgraph subGraph3["Hardware Layer"]
HAL["AxMmHal<H>"]
Frame["PhysFrame<H>"]
HPA["HostPhysAddr"]
end
subgraph subGraph2["Page Table Layer"]
NPT["NestedPageTable"]
Translation["GPA → HPA Translation"]
end
subgraph subGraph1["Address Space Layer"]
AddrSpace["AddrSpace<H>"]
Backend["Backend Strategy"]
end
subgraph subGraph0["Guest VM Access"]
GuestAccess["Guest Memory Access"]
GPA["GuestPhysAddr"]
end
AddrSpace --> Backend
AddrSpace --> NPT
Backend --> Frame
Backend --> HAL
FaultHandler --> Backend
GPA --> AddrSpace
GuestAccess --> GPA
GuestAccess --> PageFault
HAL --> HPA
NPT --> Translation
PageFault --> FaultHandler
Translation --> HPA
Sources: src/lib.rs(L26 - L33) src/address_space/ src/npt/
Error Handling Architecture
The system includes a centralized error mapping mechanism that converts internal mapping errors to standardized AxError types:
#![allow(unused)] fn main() { fn mapping_err_to_ax_err(err: MappingError) -> AxError { match err { MappingError::InvalidParam => AxError::InvalidInput, MappingError::AlreadyExists => AxError::AlreadyExists, MappingError::BadState => AxError::BadState, } } }
This provides consistent error handling across all address space operations while maintaining compatibility with the broader ArceOS error handling framework.
Sources: src/lib.rs(L35 - L42)
Module Organization
The crate is organized into focused modules that each handle specific aspects of address space management:
| Module | Primary Responsibility | Key Types |
|---|---|---|
| addr | Address type definitions | GuestPhysAddr,HostPhysAddr, address ranges |
| address_space | Virtual memory management | AddrSpace, memory mapping strategies |
| hal | Hardware abstraction | AxMmHaltrait |
| frame | Physical frame management | PhysFrameRAII wrapper |
| npt | Nested page tables | Architecture-specific page table implementations |
| device | Device support | Device address abstractions |
Sources: src/lib.rs(L10 - L22)
The architecture provides a clean separation of concerns while enabling efficient guest-to-host address translation and memory management for hypervisor use cases.
Address Types and Spaces
Relevant source files
This document explains the fundamental address types and memory spaces used throughout the axaddrspace crate. These types form the foundation for all virtual memory management and address translation operations in the ArceOS-Hypervisor system.
The address type system distinguishes between guest and host address spaces, and between virtual and physical addressing within each space. For information about how these addresses are used in page table implementations, see Architecture-Specific Implementations. For details on how address spaces are managed at the system level, see Address Space Management.
Address Type Hierarchy
The codebase defines four primary address types that represent different addressing contexts within the hypervisor environment:
Address Type Definitions
| Type | Definition | Purpose | Source |
|---|---|---|---|
| HostVirtAddr | VirtAddralias | Host kernel virtual addresses | src/addr.rs4 |
| HostPhysAddr | PhysAddralias | Host machine physical addresses | src/addr.rs6 |
| GuestVirtAddr | Customusizetype | Guest virtual addresses (GVA) | src/addr.rs10 |
| GuestPhysAddr | Customusizetype | Guest physical addresses (GPA) | src/addr.rs12 |
Sources: src/addr.rs(L1 - L31)
Guest vs Host Address Spaces
The hypervisor operates with two distinct address spaces, each serving different roles in the virtualization stack:
flowchart TD
subgraph subGraph2["Translation Mechanisms"]
GPT["Guest Page Tables(GVA → GPA)"]
NPT["Nested Page Tables(GPA → HPA)"]
HMM["Host MMU(HVA → HPA)"]
end
subgraph subGraph1["Host Hypervisor Perspective"]
HVA["Host VirtualAddress Space(HostVirtAddr)"]
HPA["Host PhysicalAddress Space(HostPhysAddr)"]
end
subgraph subGraph0["Guest VM Perspective"]
GVA["Guest VirtualAddress Space(GuestVirtAddr)"]
GPA["Guest PhysicalAddress Space(GuestPhysAddr)"]
end
GPA --> NPT
GPT --> GPA
GVA --> GPT
HMM --> HPA
HVA --> HMM
NPT --> HPA
Address Space Characteristics
- Guest Address Space: Managed by the guest operating system, providing the illusion of direct hardware access
- Host Address Space: Managed by the hypervisor, representing actual physical hardware resources
- Translation Chain: Guest virtual → Guest physical → Host physical addresses
Sources: src/addr.rs(L8 - L13)
Address Type Implementation Details
The guest address types are implemented using specialized macros that provide type safety and debugging support:
flowchart TD
subgraph subGraph2["Display Formatting"]
GVAFmt["GVA:{} format"]
GPAFmt["GPA:{} format"]
end
subgraph subGraph1["Generated Address Types"]
GuestVirtImpl["GuestVirtAddrstruct"]
GuestPhysImpl["GuestPhysAddrstruct"]
end
subgraph subGraph0["Macro-Generated Types"]
DefAddr["def_usize_addr!macro"]
DefFmt["def_usize_addr_formatter!macro"]
end
DefAddr --> GuestPhysImpl
DefAddr --> GuestVirtImpl
DefFmt --> GPAFmt
DefFmt --> GVAFmt
GuestPhysImpl --> GPAFmt
GuestVirtImpl --> GVAFmt
Type Generation and Formatting
The def_usize_addr! macro creates strongly-typed wrappers around usize values, while def_usize_addr_formatter! provides custom debug formatting:
GuestVirtAddrdisplays as"GVA:{address}"GuestPhysAddrdisplays as"GPA:{address}"
This approach ensures type safety while maintaining efficient runtime representation as raw pointer-sized integers.
Sources: src/addr.rs(L8 - L18)
Address Range Abstractions
The system provides range types for representing contiguous memory regions within guest address spaces:
flowchart TD
subgraph subGraph1["Usage Contexts"]
MemRegion["Memory RegionDescriptors"]
MapOps["Mapping OperationsBatch Processing"]
Validation["Address RangeValidation"]
end
subgraph subGraph0["Range Type Definitions"]
AddrRange["AddrRange(from memory_addr crate)"]
GVARange["GuestVirtAddrRangetype alias"]
GPARange["GuestPhysAddrRangetype alias"]
end
AddrRange --> GPARange
AddrRange --> GVARange
GPARange --> MapOps
GPARange --> MemRegion
GPARange --> Validation
GVARange --> MapOps
GVARange --> MemRegion
GVARange --> Validation
Range Operations
Address ranges support standard operations for memory region management:
- Start and end address access
- Size calculation
- Overlap detection
- Alignment validation
Sources: src/addr.rs(L20 - L23)
Architecture-Specific Integration
The address types integrate with architecture-specific page table implementations through trait implementations:
flowchart TD
subgraph subGraph2["Target Architectures"]
RISCV32["riscv32"]
RISCV64["riscv64"]
end
subgraph Implementation["Implementation"]
GPAImpl["GuestPhysAddrSvVirtAddr impl"]
TODOFlush["todo!() placeholder"]
end
subgraph subGraph0["Architecture Traits"]
SvVirtAddr["SvVirtAddr trait(RISC-V specific)"]
FlushTLB["flush_tlb() method"]
end
FlushTLB --> TODOFlush
GPAImpl --> SvVirtAddr
RISCV32 --> GPAImpl
RISCV64 --> GPAImpl
SvVirtAddr --> FlushTLB
RISC-V Integration
For RISC-V architectures, GuestPhysAddr implements the SvVirtAddr trait required by the page table implementation. The flush_tlb() method is currently unimplemented, indicating this functionality is still under development.
Sources: src/addr.rs(L25 - L30)
Address Space Management
Relevant source files
This document covers the AddrSpace<H> system that manages guest virtual memory spaces in the axaddrspace crate. The address space manager coordinates memory regions, page tables, and mapping backends to provide virtualized memory management for guest systems.
For information about the underlying nested page table implementations, see Nested Page Tables. For details about the hardware abstraction layer, see Hardware Abstraction Layer. For specific backend implementations, see Memory Mapping Backends.
Purpose and Core Structure
The AddrSpace<H> struct serves as the central coordinator for guest virtual memory management. It maintains a collection of memory areas with different mapping strategies and uses nested page tables for address translation.
AddrSpace Components
The following diagram shows the core components of the address space management system:
flowchart TD
subgraph subGraph2["Backend Types"]
Linear["Linear { pa_va_offset: usize }"]
Alloc["Alloc { populate: bool }"]
end
subgraph subGraph1["MemorySet Structure"]
MS["MemorySet>"]
MA["MemoryArea"]
BE["Backend"]
end
subgraph AddrSpace<H>["AddrSpace"]
AS["AddrSpace"]
VARange["va_range: GuestPhysAddrRange"]
Areas["areas: MemorySet>"]
PT["pt: PageTable"]
end
AS --> Areas
AS --> PT
AS --> VARange
Areas --> MS
BE --> Alloc
BE --> Linear
MA --> BE
MS --> MA
Sources: src/address_space/mod.rs(L17 - L22) src/address_space/backend/mod.rs(L19 - L41)
Memory Mapping Workflow
The address space manager supports two primary mapping operations through different backends:
Mapping Operations Flow
flowchart TD
subgraph subGraph0["Backend Dispatch"]
LinearMap["Linear: map_linear()"]
AllocMap["Alloc: map_alloc()"]
end
MapReq["map_linear() or map_alloc()"]
Validate["Validate address range and alignment"]
CreateArea["Create MemoryArea with Backend"]
MapToPageTable["areas.map(area, &mut pt, false)"]
BackendMap["Backend.map() implementation"]
BackendMap --> AllocMap
BackendMap --> LinearMap
CreateArea --> MapToPageTable
MapReq --> Validate
MapToPageTable --> BackendMap
Validate --> CreateArea
Sources: src/address_space/mod.rs(L70 - L89) src/address_space/mod.rs(L97 - L119) src/address_space/backend/mod.rs(L60 - L79)
Address Space Operations
The AddrSpace<H> provides the following key operations:
| Operation | Method | Purpose |
|---|---|---|
| Linear Mapping | map_linear() | Creates fixed-offset mappings between guest and host addresses |
| Dynamic Mapping | map_alloc() | Creates dynamically allocated mappings with optional population |
| Unmapping | unmap() | Removes existing mappings from specified ranges |
| Translation | translate() | Converts guest virtual addresses to host physical addresses |
| Page Fault Handling | handle_page_fault() | Processes page faults for lazy allocation scenarios |
Sources: src/address_space/mod.rs(L70 - L161)
Backend Strategy Selection
The Backend<H> enum implements the MappingBackend trait to provide different memory mapping strategies:
Backend Implementation Details
flowchart TD
subgraph subGraph2["Implementation Strategy"]
LinearImpl["map_linear() / unmap_linear()"]
AllocImpl["map_alloc() / unmap_alloc()"]
end
subgraph subGraph1["MappingBackend Trait"]
MapMethod["map(start, size, flags, pt)"]
UnmapMethod["unmap(start, size, pt)"]
ProtectMethod["protect(start, size, flags, pt)"]
end
subgraph subGraph0["Backend Enum"]
Backend["Backend"]
LinearVariant["Linear { pa_va_offset }"]
AllocVariant["Alloc { populate, _phantom }"]
end
Backend --> AllocVariant
Backend --> LinearVariant
Backend --> MapMethod
Backend --> ProtectMethod
Backend --> UnmapMethod
MapMethod --> AllocImpl
MapMethod --> LinearImpl
Sources: src/address_space/backend/mod.rs(L55 - L90)
Backend Characteristics
| Backend Type | Address Translation | Memory Allocation | Use Case |
|---|---|---|---|
| Linear | Fixed offset (vaddr - pa_va_offset) | Pre-allocated contiguous frames | Device memory, kernel mappings |
| Alloc | Dynamic allocation | Global allocator (eager/lazy) | General purpose, user memory |
Sources: src/address_space/backend/mod.rs(L19 - L41)
Page Fault Management
The address space manager handles page faults through a coordinated approach between the memory areas and their backends:
Page Fault Handling Flow
sequenceDiagram
participant Caller as Caller
participant AddrSpaceH as "AddrSpace<H>"
participant MemorySet as "MemorySet"
participant BackendH as "Backend<H>"
participant PageTableH as "PageTable<H>"
Caller ->> AddrSpaceH: handle_page_fault(vaddr, access_flags)
AddrSpaceH ->> AddrSpaceH: Check va_range.contains(vaddr)
AddrSpaceH ->> MemorySet: find(vaddr)
MemorySet -->> AddrSpaceH: Some(area) or None
alt Area found
AddrSpaceH ->> AddrSpaceH: Check orig_flags.contains(access_flags)
alt Flags compatible
AddrSpaceH ->> BackendH: handle_page_fault(vaddr, orig_flags, pt)
BackendH ->> BackendH: Match backend type
alt Linear backend
BackendH -->> AddrSpaceH: false (no page faults)
else Alloc backend
BackendH ->> PageTableH: Allocate and map frame
BackendH -->> AddrSpaceH: true (fault handled)
end
else Flags incompatible
AddrSpaceH -->> Caller: false
end
else Area not found
AddrSpaceH -->> Caller: false
end
Sources: src/address_space/mod.rs(L147 - L161) src/address_space/backend/mod.rs(L93 - L106)
Address Translation Services
The address space provides multiple translation interfaces for different use cases:
Translation Methods
| Method | Return Type | Use Case |
|---|---|---|
| translate() | Option | Simple address translation |
| translate_and_get_limit() | Option<(PhysAddr, usize)> | Translation with area size information |
| translated_byte_buffer() | Option<Vec<&'static mut [u8]>> | Direct memory access through page table |
Translation Implementation
flowchart TD TranslateReq["translate(vaddr)"] CheckRange["Check va_range.contains(vaddr)"] PageTableQuery["pt.query(vaddr)"] ReturnResult["Return (phys_addr, flags, page_size)"] ReturnNone["Return None"] CheckRange --> PageTableQuery CheckRange --> ReturnNone PageTableQuery --> ReturnResult TranslateReq --> CheckRange
Sources: src/address_space/mod.rs(L166 - L177) src/address_space/mod.rs(L234 - L246)
Lifecycle Management
The AddrSpace<H> implements proper resource cleanup through the Drop trait:
- Automatic cleanup: The
drop()implementation callsclear()to remove all mappings - Explicit cleanup: The
clear()method removes all memory areas and their page table entries - Memory safety: Physical frames are automatically deallocated through the backend implementations
Sources: src/address_space/mod.rs(L259 - L263) src/address_space/mod.rs(L137 - L139)
Hardware Abstraction Layer
Relevant source files
Purpose and Scope
The Hardware Abstraction Layer (HAL) provides a platform-independent interface for memory management operations within the axaddrspace crate. It abstracts low-level memory allocation, deallocation, and address translation functions that are specific to the underlying hypervisor or operating system implementation.
The HAL consists of two primary components: the AxMmHal trait that defines the interface for hardware-specific operations, and the PhysFrame struct that provides RAII-based physical memory frame management. For information about how address types are defined and used throughout the system, see Address Types and Spaces. For details on how the HAL integrates with memory mapping strategies, see Linear Backend and Allocation Backend.
AxMmHal Trait Interface
The AxMmHal trait defines the core hardware abstraction interface for memory management operations. This trait must be implemented by the host system to provide concrete implementations of memory allocation and address translation.
AxMmHal Trait Methods
flowchart TD
subgraph subGraph1["Input/Output Types"]
hpa["HostPhysAddr"]
hva["HostVirtAddr"]
opt["Option<HostPhysAddr>"]
end
subgraph subGraph0["AxMmHal Trait Interface"]
trait["AxMmHal"]
alloc["alloc_frame()"]
dealloc["dealloc_frame(paddr)"]
p2v["phys_to_virt(paddr)"]
v2p["virt_to_phys(vaddr)"]
end
alloc --> opt
dealloc --> hpa
p2v --> hpa
p2v --> hva
trait --> alloc
trait --> dealloc
trait --> p2v
trait --> v2p
v2p --> hpa
v2p --> hva
The trait provides four essential operations:
| Method | Purpose | Parameters | Return Type |
|---|---|---|---|
| alloc_frame() | Allocates a physical memory frame | None | Option |
| dealloc_frame() | Deallocates a physical memory frame | paddr: HostPhysAddr | () |
| phys_to_virt() | Converts physical to virtual address | paddr: HostPhysAddr | HostVirtAddr |
| virt_to_phys() | Converts virtual to physical address | vaddr: HostVirtAddr | HostPhysAddr |
Sources: src/hal.rs(L4 - L40)
PhysFrame RAII Management
The PhysFrame<H: AxMmHal> struct provides automatic memory management for physical memory frames using the RAII (Resource Acquisition Is Initialization) pattern. It ensures that allocated frames are automatically deallocated when the frame goes out of scope.
PhysFrame Lifecycle
flowchart TD
subgraph Cleanup["Cleanup"]
drop["Drop::drop()"]
hal_dealloc["H::dealloc_frame()"]
end
subgraph Operations["Operations"]
start["start_paddr()"]
ptr["as_mut_ptr()"]
fill["fill(byte)"]
end
subgraph subGraph1["PhysFrame State"]
frame["PhysFrame<H>"]
paddr["start_paddr: Option<HostPhysAddr>"]
marker["_marker: PhantomData<H>"]
end
subgraph subGraph0["Allocation Methods"]
alloc["PhysFrame::alloc()"]
alloc_zero["PhysFrame::alloc_zero()"]
uninit["PhysFrame::uninit()"]
end
alloc --> frame
alloc_zero --> frame
drop --> hal_dealloc
frame --> drop
frame --> fill
frame --> marker
frame --> paddr
frame --> ptr
frame --> start
uninit --> frame
Frame Allocation and Management
The PhysFrame struct provides several allocation methods:
alloc(): Allocates a new frame usingH::alloc_frame()and validates the returned address is non-zeroalloc_zero(): Allocates a frame and fills it with zeros using thefill()methoduninit(): Creates an uninitialized frame for placeholder use (unsafe)
Frame operations include:
start_paddr(): Returns the starting physical address of the frameas_mut_ptr(): Provides a mutable pointer to the frame content viaH::phys_to_virt()fill(byte): Fills the entire frame with a specified byte value (assumes 4KiB frame size)
The automatic cleanup is handled by the Drop implementation, which calls H::dealloc_frame() when the frame is dropped.
Sources: src/frame.rs(L14 - L74)
Integration with System Components
The HAL serves as the foundation for memory management throughout the axaddrspace system, providing the interface between high-level address space management and low-level hardware operations.
HAL Integration Architecture
flowchart TD
subgraph subGraph3["Address Types"]
host_phys["HostPhysAddr"]
host_virt["HostVirtAddr"]
end
subgraph subGraph2["Host Implementation"]
host_impl["Host-specific Implementation"]
frame_alloc["Frame Allocator"]
addr_trans["Address Translation"]
end
subgraph subGraph1["Hardware Abstraction Layer"]
hal_trait["AxMmHal Trait"]
physframe["PhysFrame<H>"]
end
subgraph subGraph0["Memory Backends"]
alloc_backend["Alloc Backend"]
linear_backend["Linear Backend"]
end
alloc_backend --> physframe
hal_trait --> host_impl
hal_trait --> host_phys
hal_trait --> host_virt
host_impl --> addr_trans
host_impl --> frame_alloc
linear_backend --> host_phys
physframe --> hal_trait
Key Integration Points
- Allocation Backend: The allocation backend (see Allocation Backend) uses
PhysFramefor dynamic memory allocation with lazy population strategies. - Address Translation: Both physical-to-virtual and virtual-to-physical address translation operations are abstracted through the HAL, enabling consistent address handling across different host environments.
- Frame Size Abstraction: The HAL abstracts frame size details, though the current implementation assumes 4KiB frames as defined by
PAGE_SIZE_4Kfrom thememory_addrcrate. - Error Handling: Frame allocation failures are handled gracefully through the
Option<HostPhysAddr>return type, with higher-level components responsible for error propagation.
Sources: src/frame.rs(L1 - L7) src/hal.rs(L1 - L2)
The Hardware Abstraction Layer ensures that the axaddrspace crate can operate independently of specific hypervisor implementations while maintaining efficient memory management through RAII patterns and clear separation of concerns between platform-specific and platform-independent code.
Nested Page Tables
Relevant source files
Purpose and Scope
The Nested Page Tables system provides architecture-specific two-stage address translation capabilities for hypervisor virtualization. This system enables guest operating systems to manage their own virtual memory while the hypervisor transparently translates guest physical addresses to host physical addresses through hardware-assisted virtualization features.
This document covers the generic nested page table interface and architecture selection mechanism. For detailed implementation specifics, see Architecture Selection, AArch64 Implementation, x86_64 Implementation, and RISC-V Implementation. For broader address space management context, see Address Space Management.
Architecture Overview
The nested page table system uses a layered architecture that provides a unified interface while supporting multiple hardware virtualization technologies. The system leverages conditional compilation to select the appropriate implementation based on the target architecture.
Nested Page Table Architecture
flowchart TD
subgraph subGraph4["RISC-V Implementation"]
RISCVNPT["NestedPageTable<H>"]
SV39["SV39 Support"]
end
subgraph subGraph3["AArch64 Implementation"]
AArch64NPT["NestedPageTable<H>"]
ARMStage2["ARM Stage 2 Translation"]
end
subgraph subGraph2["x86_64 Implementation"]
EPT["ExtendedPageTable<H>"]
IntelEPT["Intel EPT Support"]
end
subgraph subGraph1["Architecture Selection"]
CfgIf["cfg_if! Conditional Compilation"]
ArchMod["arch/mod.rs"]
end
subgraph subGraph0["Generic Interface Layer"]
NPT["NestedPageTable<H>"]
TypeAlias["Type Alias System"]
end
AArch64NPT --> ARMStage2
ArchMod --> AArch64NPT
ArchMod --> EPT
ArchMod --> RISCVNPT
CfgIf --> ArchMod
EPT --> IntelEPT
NPT --> TypeAlias
RISCVNPT --> SV39
TypeAlias --> CfgIf
Sources: src/npt/mod.rs(L1 - L15) src/npt/arch/mod.rs(L1 - L15)
Type System and Abstraction
The nested page table system uses Rust's conditional compilation features to provide a single NestedPageTable<H> type that resolves to different concrete implementations depending on the target architecture. This approach ensures type safety while maintaining architecture-specific optimizations.
Type Alias Resolution System
flowchart TD
subgraph subGraph2["Concrete Types"]
EPTType["arch::ExtendedPageTable<H>"]
AArch64Type["arch::NestedPageTable<H>"]
RISCVType["arch::NestedPageTable<H>"]
end
subgraph subGraph1["Type Alias Definitions"]
NPTAlias["NestedPageTable<H>"]
end
subgraph subGraph0["Compile Time Selection"]
CfgCheck["cfg_if! Macro"]
TargetArch["target_arch Detection"]
end
CfgCheck --> TargetArch
NPTAlias --> AArch64Type
NPTAlias --> EPTType
NPTAlias --> RISCVType
TargetArch --> NPTAlias
The type resolution follows this pattern in src/npt/mod.rs(L1 - L12) :
| Target Architecture | Type Alias Resolution | Hardware Feature |
|---|---|---|
| x86_64 | arch::ExtendedPageTable | Intel EPT |
| aarch64 | arch::NestedPageTable | ARM Stage 2 Translation |
| riscv32/riscv64 | arch::NestedPageTable | RISC-V SV39 |
Sources: src/npt/mod.rs(L1 - L12)
Architecture Module System
The architecture selection mechanism uses a two-tier conditional compilation system. The top-level module defines type aliases, while the architecture module handles implementation selection and re-exports.
Module Selection Flow
flowchart TD
subgraph subGraph2["Implementation Modules"]
X86Mod["x86_64.rs"]
AArch64Mod["aarch64.rs"]
RISCVMod["riscv.rs"]
end
subgraph npt/arch/mod.rs["npt/arch/mod.rs"]
ArchMod["cfg_if! Module Selection"]
ModImport["Module Import & Re-export"]
end
subgraph npt/mod.rs["npt/mod.rs"]
MainMod["cfg_if! Type Alias Selection"]
NPTDef["NestedPageTable<H> Definition"]
end
AArch64Mod --> ArchMod
ArchMod --> ModImport
MainMod --> NPTDef
ModImport --> AArch64Mod
ModImport --> RISCVMod
ModImport --> X86Mod
NPTDef --> ArchMod
RISCVMod --> ArchMod
X86Mod --> ArchMod
The architecture module in src/npt/arch/mod.rs(L3 - L14) uses pattern matching on target_arch to:
- Import the appropriate architecture-specific module
- Re-export all public items using wildcard imports
- Provide a unified interface for the parent module
Sources: src/npt/arch/mod.rs(L3 - L14)
Integration Points
The nested page table system integrates with several other components in the axaddrspace crate:
- Address Space Management: Used by
AddrSpace<H>for guest-to-host address translation - Hardware Abstraction: Depends on
AxMmHaltrait for memory frame allocation - Address Types: Operates on
GuestPhysAddrandHostPhysAddrtypes - Memory Backends: Provides translation services for both linear and allocation backends
The generic parameter H represents the hardware abstraction layer implementation, ensuring type safety across the entire address space management stack.
Sources: src/npt/mod.rs(L1 - L15) src/npt/arch/mod.rs(L1 - L15)
Architecture Selection
Relevant source files
Purpose and Scope
This page documents the architecture selection mechanism within the nested page table (NPT) system. It explains how axaddrspace uses conditional compilation to select appropriate page table implementations based on the target CPU architecture during build time.
The architecture selection system provides a unified interface while delegating to architecture-specific implementations. For detailed information about individual architecture implementations, see AArch64 Implementation, x86_64 Implementation, and RISC-V Implementation.
Conditional Compilation Strategy
The architecture selection system uses the cfg_if crate to perform conditional compilation based on the target architecture. This approach allows the codebase to include only the relevant architecture-specific code during compilation, reducing binary size and eliminating unused code paths.
Architecture Module Selection
The primary selection mechanism is implemented in src/npt/arch/mod.rs(L3 - L14) which uses cfg_if to conditionally include and re-export architecture-specific modules:
Diagram: Architecture Module Selection Flow
Sources: src/npt/arch/mod.rs(L3 - L14)
Type Alias System
The type alias system in src/npt/mod.rs(L1 - L12) provides a uniform NestedPageTable<H> interface that resolves to different concrete types depending on the target architecture:
| Target Architecture | Type Alias Resolves To | Implementation File |
|---|---|---|
| x86_64 | arch::ExtendedPageTable | arch/x86_64.rs |
| aarch64 | arch::NestedPageTable | arch/aarch64.rs |
| riscv32,riscv64 | arch::NestedPageTable | arch/riscv.rs |
Type Resolution Flow
flowchart TD
subgraph subGraph1["Architecture Implementations"]
X86STRUCT["ExtendedPageTable struct"]
ARMSTRUCT["NestedPageTable struct"]
RISCVSTRUCT["NestedPageTable struct"]
end
subgraph subGraph0["Compilation Time Resolution"]
ALIAS["NestedPageTable<H>"]
X86TYPE["arch::ExtendedPageTable<H>"]
ARMTYPE["arch::NestedPageTable<H>"]
RISCVTYPE["arch::NestedPageTable<H>"]
end
CLIENT["Client Code"]
ALIAS --> ARMTYPE
ALIAS --> RISCVTYPE
ALIAS --> X86TYPE
ARMTYPE --> ARMSTRUCT
CLIENT --> ALIAS
RISCVTYPE --> RISCVSTRUCT
X86TYPE --> X86STRUCT
Diagram: Type Alias Resolution at Compile Time
Sources: src/npt/mod.rs(L1 - L12)
Architecture Support Matrix
The system currently supports three major CPU architectures with their respective virtualization technologies:
| Architecture | Conditional Compilation Target | Virtualization Technology | Type Name |
|---|---|---|---|
| Intel/AMD x86_64 | target_arch = "x86_64" | Extended Page Tables (EPT) | ExtendedPageTable |
| ARM AArch64 | target_arch = "aarch64" | Stage 2 Translation | NestedPageTable |
| RISC-V 32/64-bit | target_arch = "riscv32"ortarget_arch = "riscv64" | H-extension | NestedPageTable |
Module Structure and Dependencies
The architecture selection system creates a clear separation between the generic interface and platform-specific implementations:
Diagram: Architecture Selection Module Structure
Sources: src/npt/mod.rs(L1 - L15) src/npt/arch/mod.rs(L1 - L15)
Build-Time Behavior
The architecture selection occurs entirely at compile time through Rust's conditional compilation features. When building for a specific target:
- The
cfg_ifmacro inarch/mod.rsevaluates the target architecture - Only the matching module is included in compilation
- The appropriate symbols are re-exported via
pub use - The type alias in
mod.rsresolves to the selected implementation - Dead code elimination removes unused architecture implementations
This approach ensures that the final binary contains only the code necessary for the target platform, avoiding runtime overhead and reducing memory footprint.
Sources: src/npt/arch/mod.rs(L3 - L14) src/npt/mod.rs(L1 - L12)
AArch64 Implementation
Relevant source files
This document covers the AArch64-specific implementation of nested page tables within the axaddrspace crate. The AArch64 implementation provides VMSAv8-64 Stage 2 translation support for hypervisor environments, enabling guest physical address to host physical address translation on ARM 64-bit architectures.
This implementation specifically handles ARM's hypervisor-mode address translation. For information about other architecture implementations, see x86_64 Implementation and RISC-V Implementation. For the generic nested page table interface, see Architecture Selection.
Architecture Overview
The AArch64 implementation follows ARM's VMSAv8-64 specification for Stage 2 translation, which is used in hypervisor environments to translate Intermediate Physical Addresses (IPA) from guest virtual machines to Host Physical Addresses (HPA).
flowchart TD
subgraph subGraph3["Address Translation"]
GPA["GuestPhysAddr"]
HPA["HostPhysAddr"]
IPARange["40-bit IPA Space"]
PARange["48-bit PA Space"]
end
subgraph subGraph2["Memory Management"]
MemType["MemType"]
MappingFlags["MappingFlags"]
TLBFlush["TLB Flush Operations"]
end
subgraph subGraph1["Core Components"]
A64PTEHV["A64PTEHV"]
A64Meta["A64HVPagingMetaData"]
DescAttr["DescriptorAttr"]
end
subgraph subGraph0["AArch64 NPT Implementation"]
NPT["NestedPageTable<H>"]
A64PT["PageTable64<A64HVPagingMetaData, A64PTEHV, H>"]
end
A64Meta --> GPA
A64Meta --> IPARange
A64Meta --> PARange
A64Meta --> TLBFlush
A64PT --> A64Meta
A64PT --> A64PTEHV
A64PTEHV --> DescAttr
A64PTEHV --> HPA
A64PTEHV --> MappingFlags
DescAttr --> MemType
NPT --> A64PT
Sources: src/npt/arch/aarch64.rs(L1 - L253)
The implementation provides a 3-level page table structure with:
- 40-bit Intermediate Physical Address (IPA) space for guest addresses
- 48-bit Physical Address (PA) space for host addresses
- VMSAv8-64 Stage 2 translation table format
Page Table Entry Implementation
The A64PTEHV struct represents a single VMSAv8-64 translation table descriptor, implementing the GenericPTE trait to integrate with the generic page table framework.
flowchart TD
subgraph subGraph3["Attribute Management"]
DescriptorAttr["DescriptorAttr"]
ValidBit["VALID"]
NonBlock["NON_BLOCK"]
AccessFlag["AF"]
end
subgraph subGraph2["Physical Address Handling"]
PhysMask["PHYS_ADDR_MASK0x0000_ffff_ffff_f000"]
AddrBits["bits 12..48"]
end
subgraph subGraph1["GenericPTE Implementation"]
NewPage["new_page()"]
NewTable["new_table()"]
GetPAddr["paddr()"]
GetFlags["flags()"]
SetPAddr["set_paddr()"]
SetFlags["set_flags()"]
IsPresent["is_present()"]
IsHuge["is_huge()"]
end
subgraph subGraph0["A64PTEHV Structure"]
PTE["A64PTEHV(u64)"]
PTEBits["Raw 64-bit Value"]
end
DescriptorAttr --> AccessFlag
DescriptorAttr --> NonBlock
DescriptorAttr --> ValidBit
GetFlags --> DescriptorAttr
GetPAddr --> PhysMask
IsHuge --> NonBlock
IsPresent --> ValidBit
NewPage --> DescriptorAttr
NewTable --> DescriptorAttr
PTE --> GetFlags
PTE --> GetPAddr
PTE --> IsHuge
PTE --> IsPresent
PTE --> NewPage
PTE --> NewTable
PTE --> PTEBits
PTE --> SetFlags
PTE --> SetPAddr
PhysMask --> AddrBits
SetFlags --> DescriptorAttr
SetPAddr --> PhysMask
Sources: src/npt/arch/aarch64.rs(L144 - L211)
Key Features
| Feature | Implementation | Purpose |
|---|---|---|
| Physical Address Mask | 0x0000_ffff_ffff_f000 | Extracts bits 12-48 for physical addressing |
| Page vs Block | NON_BLOCKbit | Distinguishes between 4KB pages and large blocks |
| Access Flag | AFbit | Required for all valid entries in ARM architecture |
| Valid Bit | VALIDbit | Indicates if the descriptor is valid for translation |
Memory Attribute Management
The AArch64 implementation uses the DescriptorAttr bitflags structure to manage VMSAv8-64 memory attributes and access permissions.
flowchart TD
subgraph subGraph2["Conversion Logic"]
ToMapping["to MappingFlags"]
FromMapping["from MappingFlags"]
MemTypeConv["Memory Type Conversion"]
end
subgraph subGraph1["Memory Types"]
Device["Device Memory"]
Normal["Normal Memory"]
NormalNC["Normal Non-Cacheable"]
end
subgraph subGraph0["DescriptorAttr Bitflags"]
DescAttr["DescriptorAttr"]
ValidFlag["VALID (bit 0)"]
NonBlockFlag["NON_BLOCK (bit 1)"]
AttrIndex["ATTR (bits 2-5)"]
S2AP_RO["S2AP_RO (bit 6)"]
S2AP_WO["S2AP_WO (bit 7)"]
Shareable["SHAREABLE (bit 9)"]
AF["AF (bit 10)"]
XN["XN (bit 54)"]
end
AttrIndex --> Device
AttrIndex --> Normal
AttrIndex --> NormalNC
DescAttr --> AF
DescAttr --> AttrIndex
DescAttr --> FromMapping
DescAttr --> MemTypeConv
DescAttr --> NonBlockFlag
DescAttr --> S2AP_RO
DescAttr --> S2AP_WO
DescAttr --> Shareable
DescAttr --> ToMapping
DescAttr --> ValidFlag
DescAttr --> XN
Device --> MemTypeConv
Normal --> MemTypeConv
NormalNC --> MemTypeConv
Sources: src/npt/arch/aarch64.rs(L8 - L137)
Memory Type Encoding
The implementation supports three memory types with specific attribute encodings:
| Memory Type | Encoding | Characteristics |
|---|---|---|
| Device | 0b0000 | Device memory with shareability |
| Normal | 0b1111 | Write-back cacheable with shareability |
| NormalNonCache | 0b0111 | Inner cacheable, outer non-cacheable |
Permission Mapping
The AArch64 Stage 2 access permissions are mapped to generic MappingFlags:
- Read: Controlled by
VALIDbit - Write: Controlled by absence of
S2AP_WObit - Execute: Controlled by absence of
XNbit - Device: Determined by memory attribute index
Paging Metadata Configuration
The A64HVPagingMetaData struct provides architecture-specific configuration for the AArch64 hypervisor page tables.
flowchart TD
subgraph subGraph2["EL2 vs EL1 Operations"]
EL2Ops["EL2 Operationsvae2is, alle2is"]
EL1Ops["EL1 Operationsvaae1is, vmalle1"]
ArmEL2Feature["arm-el2 feature"]
end
subgraph subGraph1["TLB Management"]
FlushTLB["flush_tlb()"]
SingleFlush["Single Address Flush"]
FullFlush["Full TLB Flush"]
end
subgraph A64HVPagingMetaData["A64HVPagingMetaData"]
Levels["LEVELS = 3"]
PABits["PA_MAX_BITS = 48"]
VABits["VA_MAX_BITS = 40"]
VirtAddr["VirtAddr = GuestPhysAddr"]
end
ArmEL2Feature --> EL1Ops
ArmEL2Feature --> EL2Ops
FlushTLB --> FullFlush
FlushTLB --> SingleFlush
FullFlush --> EL1Ops
FullFlush --> EL2Ops
Levels --> FlushTLB
PABits --> FlushTLB
SingleFlush --> EL1Ops
SingleFlush --> EL2Ops
VABits --> FlushTLB
VirtAddr --> FlushTLB
Sources: src/npt/arch/aarch64.rs(L213 - L253)
TLB Flush Implementation
The TLB flush implementation supports both Exception Level 1 (EL1) and Exception Level 2 (EL2) operations:
EL2 Mode (with arm-el2 feature):
- Single address:
tlbi vae2is- Invalidate by VA in EL2 - Full flush:
tlbi alle2is- Invalidate all EL2 TLB entries
EL1 Mode (default):
- Single address:
tlbi vaae1is- Invalidate by VA, all ASID in EL1 - Full flush:
tlbi vmalle1- Invalidate all EL1 TLB entries
All TLB operations include data synchronization barriers (dsb sy) and instruction synchronization barriers (isb) to ensure proper ordering.
Integration with Generic Framework
The AArch64 implementation integrates with the generic page table framework through type aliases and trait implementations.
| Component | Type Definition | Purpose |
|---|---|---|
| NestedPageTable | PageTable64<A64HVPagingMetaData, A64PTEHV, H> | Main page table type for AArch64 |
| Page Table Entry | A64PTEHV | VMSAv8-64 descriptor implementation |
| Metadata | A64HVPagingMetaData | Architecture-specific configuration |
| Virtual Address | GuestPhysAddr | Guest physical address type |
Sources: src/npt/arch/aarch64.rs(L252 - L253)
The implementation ensures compatibility with the generic nested page table interface while providing ARM-specific optimizations for hypervisor workloads, including proper Stage 2 translation semantics and efficient TLB management for virtualized environments.
x86_64 Implementation
Relevant source files
This document covers the Intel x86_64 Extended Page Table (EPT) implementation for nested page tables in the axaddrspace crate. The EPT system provides hardware-assisted virtualization support for Intel VMX environments, enabling efficient guest-to-host physical address translation. For general nested page table concepts and architecture selection, see Architecture Selection. For implementations on other architectures, see AArch64 Implementation and RISC-V Implementation.
EPT Overview and Intel Virtualization Context
The x86_64 implementation leverages Intel's Extended Page Table technology, which is part of the VMX (Virtual Machine Extensions) feature set. EPT provides hardware-accelerated second-level address translation, mapping guest physical addresses to host physical addresses without requiring software intervention for most memory accesses.
EPT System Architecture
flowchart TD
subgraph subGraph3["Generic Framework Integration"]
GenericPTE["GenericPTE trait"]
PagingMetaData["PagingMetaData trait"]
MappingFlags["MappingFlags"]
end
subgraph subGraph2["EPT Entry Components"]
EPTEntry["EPTEntry"]
EPTFlags["EPTFlags"]
EPTMemType["EPTMemType"]
end
subgraph subGraph1["axaddrspace EPT Implementation"]
ExtPageTable["ExtendedPageTable<H>"]
EPTMeta["ExtendedPageTableMetadata"]
PageTable64["PageTable64<ExtendedPageTableMetadata, EPTEntry, H>"]
end
subgraph subGraph0["Intel VMX Environment"]
VMCS["VMCS (VM Control Structure)"]
EPTP["EPTP (EPT Pointer)"]
EPT_BASE["EPT Base Address"]
end
EPTEntry --> EPTFlags
EPTEntry --> EPTMemType
EPTEntry --> GenericPTE
EPTFlags --> MappingFlags
EPTMeta --> PagingMetaData
EPTP --> EPT_BASE
EPT_BASE --> ExtPageTable
ExtPageTable --> PageTable64
PageTable64 --> EPTEntry
PageTable64 --> EPTMeta
VMCS --> EPTP
Sources: src/npt/arch/x86_64.rs(L182 - L184)
EPT Entry Structure and Flags
The EPTEntry structure represents a 64-bit EPT page table entry that follows Intel's EPT specification. Each entry contains physical address bits, access permissions, and memory type information.
EPT Entry Bit Layout and Components
flowchart TD
subgraph subGraph2["Memory Type Values"]
Uncached["Uncached (0)"]
WriteCombining["WriteCombining (1)"]
WriteThrough["WriteThrough (4)"]
WriteProtected["WriteProtected (5)"]
WriteBack["WriteBack (6)"]
end
subgraph subGraph1["EPTFlags Components"]
READ["READ (bit 0)Read access permission"]
WRITE["WRITE (bit 1)Write access permission"]
EXECUTE["EXECUTE (bit 2)Execute access permission"]
MEM_TYPE_MASK["MEM_TYPE_MASK (bits 5:3)Memory type specification"]
IGNORE_PAT["IGNORE_PAT (bit 6)Ignore PAT memory type"]
HUGE_PAGE["HUGE_PAGE (bit 7)Large page indicator"]
ACCESSED["ACCESSED (bit 8)Page accessed flag"]
DIRTY["DIRTY (bit 9)Page dirty flag"]
EXECUTE_FOR_USER["EXECUTE_FOR_USER (bit 10)User-mode execute access"]
end
subgraph subGraph0["EPTEntry 64-bit Structure"]
RawBits["u64 raw value (self.0)"]
PhysAddr["Physical Address [51:12]PHYS_ADDR_MASK: 0x000f_ffff_ffff_f000"]
FlagsBits["Flags and Properties [11:0]"]
end
FlagsBits --> ACCESSED
FlagsBits --> DIRTY
FlagsBits --> EXECUTE
FlagsBits --> EXECUTE_FOR_USER
FlagsBits --> HUGE_PAGE
FlagsBits --> IGNORE_PAT
FlagsBits --> MEM_TYPE_MASK
FlagsBits --> READ
FlagsBits --> WRITE
MEM_TYPE_MASK --> Uncached
MEM_TYPE_MASK --> WriteBack
MEM_TYPE_MASK --> WriteCombining
MEM_TYPE_MASK --> WriteProtected
MEM_TYPE_MASK --> WriteThrough
RawBits --> FlagsBits
RawBits --> PhysAddr
Sources: src/npt/arch/x86_64.rs(L9 - L32) src/npt/arch/x86_64.rs(L34 - L45) src/npt/arch/x86_64.rs(L99 - L107)
EPT Flags Implementation
The EPTFlags structure uses the bitflags! macro to provide type-safe manipulation of EPT entry flags. Key access permissions follow Intel's VMX specification:
| Flag | Bit Position | Purpose |
|---|---|---|
| READ | 0 | Enables read access to the page |
| WRITE | 1 | Enables write access to the page |
| EXECUTE | 2 | Enables instruction execution from the page |
| MEM_TYPE_MASK | 5:3 | Specifies memory type for terminate pages |
| HUGE_PAGE | 7 | Indicates 2MB or 1GB page size |
| ACCESSED | 8 | Hardware-set when page is accessed |
| DIRTY | 9 | Hardware-set when page is written |
Sources: src/npt/arch/x86_64.rs(L9 - L32)
Memory Type Management
EPT supports Intel's memory type system for controlling caching behavior and memory ordering. The EPTMemType enum defines the available memory types that can be encoded in EPT entries.
Memory Type Configuration Flow
flowchart TD
subgraph subGraph2["EPTFlags Methods"]
SetMemType["EPTFlags::set_mem_type()"]
GetMemType["EPTFlags::mem_type()"]
BitsManip["BitField operationsbits.set_bits(3..6, type)"]
end
subgraph subGraph1["EPT Memory Type Selection"]
MemTypeCheck["Device memory?"]
WriteBackType["EPTMemType::WriteBack(normal memory)"]
UncachedType["EPTMemType::Uncached(device memory)"]
end
subgraph subGraph0["MappingFlags Input"]
DeviceFlag["MappingFlags::DEVICE"]
StandardFlags["Standard R/W/X flags"]
end
BitsManip --> GetMemType
DeviceFlag --> MemTypeCheck
MemTypeCheck --> UncachedType
MemTypeCheck --> WriteBackType
SetMemType --> BitsManip
StandardFlags --> MemTypeCheck
UncachedType --> SetMemType
WriteBackType --> SetMemType
Sources: src/npt/arch/x86_64.rs(L47 - L56) src/npt/arch/x86_64.rs(L58 - L78) src/npt/arch/x86_64.rs(L80 - L97)
The memory type conversion logic ensures that:
- Normal memory regions use
WriteBackcaching for optimal performance - Device memory regions use
Uncachedtype to prevent caching side effects - The conversion is bidirectional between
MappingFlagsandEPTFlags
Generic Page Table Framework Integration
The EPTEntry implements the GenericPTE trait, enabling integration with the architecture-independent page table framework. This abstraction allows the same high-level page table operations to work across different architectures.
GenericPTE Implementation for EPTEntry
flowchart TD
subgraph subGraph2["Address Space Integration"]
HostPhysAddr["HostPhysAddr type"]
GuestPhysAddr["GuestPhysAddr type"]
PageTable64Framework["PageTable64<Meta, PTE, H>"]
end
subgraph subGraph1["EPTEntry Implementation Details"]
PhysAddrMask["PHYS_ADDR_MASK0x000f_ffff_ffff_f000"]
FlagsConversion["EPTFlags ↔ MappingFlagsbidirectional conversion"]
PresenceCheck["RWX bits != 0determines presence"]
HugePageBit["HUGE_PAGE flagindicates large pages"]
end
subgraph subGraph0["GenericPTE Trait Methods"]
NewPage["new_page(paddr, flags, is_huge)"]
NewTable["new_table(paddr)"]
GetPaddr["paddr() → HostPhysAddr"]
GetFlags["flags() → MappingFlags"]
SetPaddr["set_paddr(paddr)"]
SetFlags["set_flags(flags, is_huge)"]
IsPresent["is_present() → bool"]
IsHuge["is_huge() → bool"]
IsUnused["is_unused() → bool"]
Clear["clear()"]
end
GetFlags --> FlagsConversion
GetPaddr --> HostPhysAddr
IsHuge --> HugePageBit
IsPresent --> PresenceCheck
NewPage --> FlagsConversion
NewPage --> PhysAddrMask
NewTable --> PhysAddrMask
PageTable64Framework --> GetPaddr
PageTable64Framework --> IsPresent
PageTable64Framework --> NewPage
SetFlags --> FlagsConversion
SetFlags --> HugePageBit
SetPaddr --> PhysAddrMask
Sources: src/npt/arch/x86_64.rs(L109 - L154)
Key Implementation Details
The EPTEntry provides several critical implementation details:
| Method | Implementation | Purpose |
|---|---|---|
| is_present() | self.0 & 0x7 != 0 | Checks if any of R/W/X bits are set |
| paddr() | (self.0 & PHYS_ADDR_MASK) | Extracts 40-bit physical address |
| new_table() | Sets R+W+X for intermediate tables | Creates non-leaf EPT entries |
| set_flags() | ConvertsMappingFlagstoEPTFlags | Handles huge page flag setting |
Sources: src/npt/arch/x86_64.rs(L117 - L149)
Extended Page Table Metadata
The ExtendedPageTableMetadata structure defines the architectural parameters for Intel EPT, implementing the PagingMetaData trait to provide page table configuration to the generic framework.
EPT Configuration Parameters
flowchart TD
subgraph subGraph1["Address Type Mapping"]
VirtAddrType["type VirtAddr = GuestPhysAddr"]
GuestContext["Guest Physical Address SpaceWhat guests see as 'physical'"]
end
subgraph subGraph0["ExtendedPageTableMetadata Constants"]
LEVELS["LEVELS = 44-level page table"]
PA_MAX_BITS["PA_MAX_BITS = 5252-bit physical addresses"]
VA_MAX_BITS["VA_MAX_BITS = 4848-bit guest addresses"]
end
subgraph subGraph2["TLB Management"]
FlushTlb["flush_tlb(vaddr)TODO: implement"]
HardwareFlush["Hardware TLB invalidationINVEPT instruction"]
end
FlushTlb --> HardwareFlush
LEVELS --> VirtAddrType
PA_MAX_BITS --> VirtAddrType
VA_MAX_BITS --> VirtAddrType
VirtAddrType --> GuestContext
Sources: src/npt/arch/x86_64.rs(L167 - L180)
The metadata configuration aligns with Intel's EPT specification:
- 4-level hierarchy: EPT4, EPT3, EPT2, EPT1 tables providing 48-bit guest address translation
- 52-bit physical addresses: Supporting Intel's maximum physical address width
- Guest physical address context: EPT translates what guests consider "physical" addresses
Type Alias and Framework Integration
The final component ties together all EPT-specific implementations with the generic page table framework through a type alias:
pub type ExtendedPageTable<H> = PageTable64<ExtendedPageTableMetadata, EPTEntry, H>;
This type alias creates the main EPT page table type that:
- Uses
ExtendedPageTableMetadatafor architectural parameters - Uses
EPTEntryfor page table entries with Intel-specific features - Accepts a generic hardware abstraction layer
H - Inherits all functionality from the
PageTable64generic implementation
Sources: src/npt/arch/x86_64.rs(L182 - L184)
The resulting ExtendedPageTable<H> type provides a complete EPT implementation that integrates seamlessly with the broader address space management system while leveraging Intel's hardware virtualization features for optimal performance.
RISC-V Implementation
Relevant source files
This document covers the RISC-V-specific implementation of nested page tables within the axaddrspace crate. The RISC-V implementation provides support for the SV39 virtual memory scheme, enabling guest-to-host address translation in RISC-V virtualization environments.
For information about the overall nested page table architecture selection, see Architecture Selection. For details about other architecture implementations, see AArch64 Implementation and x86_64 Implementation.
Architecture Overview
The RISC-V implementation follows the same architectural pattern as other platform-specific nested page table implementations. It combines generic page table functionality with RISC-V-specific components to provide hardware-appropriate virtualization support.
flowchart TD
subgraph subGraph2["Address Translation"]
GPA["GuestPhysAddrGuest Physical Address"]
SV39["SV39 Virtual Memory39-bit Address Space"]
end
subgraph subGraph1["External Dependencies"]
ENTRY_LIB["page_table_entry::riscv::Rv64PTE"]
MULTI_LIB["page_table_multiarch::PageTable64"]
META_LIB["page_table_multiarch::riscv::Sv39MetaData"]
end
subgraph subGraph0["RISC-V NPT Architecture"]
NPT["NestedPageTable<H>Type Alias"]
PT64["PageTable64Generic Framework"]
META["Sv39MetaData<GuestPhysAddr>RISC-V Metadata"]
PTE["Rv64PTE64-bit Page Table Entry"]
HAL["H: AxMmHalHardware Abstraction"]
end
META --> GPA
META --> META_LIB
META --> SV39
NPT --> HAL
NPT --> META
NPT --> PT64
NPT --> PTE
PT64 --> MULTI_LIB
PTE --> ENTRY_LIB
Sources: src/npt/arch/riscv.rs(L1 - L6)
Type Definition and Components
The RISC-V nested page table implementation is defined as a single type alias that combines specialized RISC-V components with the generic PageTable64 framework:
pub type NestedPageTable<H> = PageTable64<Sv39MetaData<GuestPhysAddr>, Rv64PTE, H>;
This type alias instantiates the generic page table with three key parameters:
| Parameter | Type | Purpose |
|---|---|---|
| Metadata | Sv39MetaData | RISC-V SV39 paging scheme configuration |
| Entry Type | Rv64PTE | 64-bit RISC-V page table entry format |
| HAL Type | H: AxMmHal | Hardware abstraction layer implementation |
Sources: src/npt/arch/riscv.rs(L6)
SV39 Virtual Memory Support
The implementation leverages the Sv39MetaData type to provide support for RISC-V's SV39 virtual memory scheme. SV39 is a 39-bit virtual address translation scheme commonly used in RISC-V systems, providing:
- 39-bit virtual address space
- Three-level page table hierarchy
- 4 KiB page size support
- Integration with
GuestPhysAddrfor guest physical addressing
flowchart TD
subgraph subGraph1["RISC-V Components"]
META["Sv39MetaData<GuestPhysAddr>"]
PTE["Rv64PTE"]
end
subgraph subGraph0["SV39 Address Translation"]
VA["39-bit Virtual Address"]
L2["Level 2Page Directory"]
L1["Level 1Page Directory"]
L0["Level 0Page Table"]
PA["Physical Address"]
end
L0 --> PA
L1 --> L0
L2 --> L1
META --> L0
META --> L1
META --> L2
PTE --> L0
VA --> L2
Sources: src/npt/arch/riscv.rs(L2 - L6)
Integration with Generic Framework
The RISC-V implementation demonstrates the modular design of the nested page table system. It relies entirely on external crates for the actual implementation details:
- page_table_entry: Provides the
Rv64PTEtype for RISC-V page table entries - page_table_multiarch: Supplies both the generic
PageTable64framework and RISC-V-specificSv39MetaData
This approach ensures that:
- Architecture-specific logic is maintained in specialized crates
- The axaddrspace crate focuses on integration and abstraction
- Updates to RISC-V specifications can be handled in dedicated libraries
- The implementation remains consistent with other architectures
flowchart TD
subgraph Result["Result"]
TYPE["NestedPageTable<H>RISC-V Type Alias"]
end
subgraph subGraph1["Crate Dependencies"]
ENTRY["page_table_entry::riscv"]
MULTI["page_table_multiarch"]
end
subgraph subGraph0["Integration Pattern"]
IMPL["riscv.rsArchitecture Integration"]
GENERIC["PageTable64Generic Implementation"]
SPECIFIC["RISC-V Specific Types"]
end
GENERIC --> MULTI
IMPL --> GENERIC
IMPL --> SPECIFIC
IMPL --> TYPE
SPECIFIC --> ENTRY
SPECIFIC --> MULTI
Sources: src/npt/arch/riscv.rs(L1 - L6)
Memory Mapping Backends
Relevant source files
This document explains the different strategies for mapping guest physical memory to host physical memory in the axaddrspace crate. Memory mapping backends provide the core abstraction for how virtual memory regions are translated to physical memory, supporting both static linear mappings and dynamic allocation-based mappings.
For detailed implementation of individual backends, see Linear Backend and Allocation Backend. For information about how backends integrate with address space management, see Address Space Management.
Backend Architecture Overview
The memory mapping backend system is built around a unified Backend<H> enum that provides different strategies for memory mapping. Each backend implements the MappingBackend trait to provide consistent interfaces for memory operations while allowing different underlying implementation strategies.
Backend Strategy Selection
flowchart TD
subgraph subGraph2["Memory Characteristics"]
LinearMem["Contiguous Physical MemoryKnown at Creation Time"]
AllocMem["Dynamic Physical MemoryFrom Global Allocator"]
end
subgraph subGraph1["Implementation Strategy"]
LinearImpl["Fixed Offset Translation"]
AllocEager["Eager Allocation (populate=true)"]
AllocLazy["Lazy Allocation (populate=false)"]
end
subgraph subGraph0["Backend Selection"]
BackendEnum["Backend<H> enum"]
LinearVariant["Linear { pa_va_offset: usize }"]
AllocVariant["Alloc { populate: bool }"]
end
AllocEager --> AllocMem
AllocLazy --> AllocMem
AllocVariant --> AllocEager
AllocVariant --> AllocLazy
BackendEnum --> AllocVariant
BackendEnum --> LinearVariant
LinearImpl --> LinearMem
LinearVariant --> LinearImpl
Sources: src/address_space/backend/mod.rs(L11 - L41)
Backend Trait Implementation
Each backend variant implements the MappingBackend trait, which defines the core memory management operations. The trait provides a uniform interface while allowing backend-specific implementations for mapping, unmapping, and protection operations.
MappingBackend Trait Implementation
flowchart TD
subgraph subGraph2["Page Table Operations"]
PageTableOps["PageTable<H> operations"]
MappingFlags["MappingFlags"]
GuestPhysAddr["GuestPhysAddr"]
end
subgraph subGraph1["Backend Dispatch"]
BackendMatch["Backend<H> match"]
LinearDispatch["map_linear / unmap_linear"]
AllocDispatch["map_alloc / unmap_alloc"]
end
subgraph subGraph0["MappingBackend Trait"]
MapMethod["map(start, size, flags, pt)"]
UnmapMethod["unmap(start, size, pt)"]
ProtectMethod["protect(start, size, new_flags, pt)"]
end
AllocDispatch --> PageTableOps
BackendMatch --> AllocDispatch
BackendMatch --> LinearDispatch
LinearDispatch --> PageTableOps
MapMethod --> BackendMatch
PageTableOps --> GuestPhysAddr
PageTableOps --> MappingFlags
UnmapMethod --> BackendMatch
Sources: src/address_space/backend/mod.rs(L55 - L90)
Memory Mapping Strategies
The system supports two fundamental mapping strategies that serve different use cases in virtualization scenarios:
| Backend Type | Strategy | Physical Memory | Page Faults | Use Case |
|---|---|---|---|---|
| Linear | Fixed offset translation | Contiguous, pre-allocated | Not supported | Device memory, firmware regions |
| Alloc(populate=true) | Eager allocation | Dynamic, immediate | Not triggered | High-performance guest RAM |
| Alloc(populate=false) | Lazy allocation | Dynamic, on-demand | Handled on access | Large sparse memory regions |
The Linear backend uses a simple arithmetic translation where guest physical address gpa maps to host physical address gpa - pa_va_offset. This provides zero-overhead translation for contiguous memory regions.
The Alloc backend obtains physical frames from the global allocator through the hardware abstraction layer. When populate is true, all frames are allocated during mapping creation. When false, frames are allocated on-demand through page fault handling.
Sources: src/address_space/backend/mod.rs(L13 - L40)
Page Fault Handling
Page fault handling behavior differs significantly between backend types. The handle_page_fault method provides backend-specific fault resolution:
Page Fault Resolution Flow
flowchart TD
subgraph subGraph3["Alloc Backend Response"]
AllocHandler["handle_page_fault_alloc()"]
PopulateCheck["Check populate flag"]
FrameAllocation["Allocate physical frame"]
PageTableUpdate["Update page table entry"]
end
subgraph subGraph2["Linear Backend Response"]
LinearReturn["return false"]
NoFaultExpected["Linear mappings should not fault"]
end
subgraph subGraph1["Backend Dispatch"]
HandlePageFault["handle_page_fault()"]
BackendCheck["Backend variant check"]
end
subgraph subGraph0["Page Fault Trigger"]
PageFaultEvent["Guest Memory Access"]
FaultInfo["vaddr, orig_flags, page_table"]
end
AllocHandler --> PopulateCheck
BackendCheck --> AllocHandler
BackendCheck --> LinearReturn
FaultInfo --> HandlePageFault
FrameAllocation --> PageTableUpdate
HandlePageFault --> BackendCheck
LinearReturn --> NoFaultExpected
PageFaultEvent --> FaultInfo
PopulateCheck --> FrameAllocation
Linear backends return false from handle_page_fault because all mappings should be established at creation time. Page faults on linear mappings indicate configuration errors or hardware issues.
Allocation backends with populate=false use page fault handling to implement lazy allocation. When a page fault occurs, the handler allocates a new physical frame and updates the page table entry to resolve the fault.
Sources: src/address_space/backend/mod.rs(L92 - L106)
Backend Integration
The Backend<H> enum integrates with the broader address space management system through generic type parameter H, which must implement PagingHandler. This allows backends to work with different nested page table implementations across architectures.
System Integration Points
flowchart TD
subgraph subGraph3["Hardware Abstraction"]
AxMmHal["AxMmHal trait"]
PhysFrame["PhysFrame<H>"]
end
subgraph subGraph2["Page Table Layer"]
NestedPageTable["NestedPageTable<H>"]
PagingHandler["PagingHandler trait"]
end
subgraph subGraph1["Backend Layer"]
Backend["Backend<H>"]
MappingBackendTrait["MappingBackend trait"]
end
subgraph subGraph0["Address Space Layer"]
AddrSpace["AddrSpace<H>"]
MemorySet["MemorySet<Backend<H>>"]
end
AddrSpace --> MemorySet
Backend --> AxMmHal
Backend --> MappingBackendTrait
Backend --> PhysFrame
MappingBackendTrait --> NestedPageTable
MemorySet --> Backend
NestedPageTable --> PagingHandler
The type system ensures that backends, page tables, and hardware abstraction layers are all compatible through the shared H: PagingHandler constraint. This enables compile-time verification of architectural compatibility while maintaining runtime flexibility in backend selection.
Sources: src/address_space/backend/mod.rs(L3 - L6) src/address_space/backend/mod.rs(L55 - L59)
Linear Backend
Relevant source files
Purpose and Scope
The Linear Backend implements a fixed-offset memory mapping strategy for guest address spaces in the ArceOS-Hypervisor. This backend provides a simple, predictable translation mechanism between guest physical addresses and host physical addresses using a constant offset value.
For information about dynamic allocation strategies, see Allocation Backend. For the broader memory mapping system architecture, see Memory Mapping Backends.
Overview
The Linear Backend uses a straightforward address translation model where guest physical addresses are mapped to host physical addresses by subtracting a fixed offset (pa_va_offset). This creates a linear, one-to-one mapping relationship that is efficient for scenarios where the guest memory layout can be predetermined and doesn't require dynamic allocation.
Linear Mapping Address Translation
flowchart TD GPA["GuestPhysAddrGuest Physical Address"] Offset["pa_va_offsetFixed Offset Value"] HPA["HostPhysAddrHost Physical Address"] Translation["Address Translationhost_addr = guest_addr - offset"] Mapping["Page Table Mappingpt.map_region()"] NPT["NestedPageTableHardware Page Table"] GPA --> Translation Mapping --> NPT Offset --> Translation Translation --> HPA Translation --> Mapping
Sources: src/address_space/backend/linear.rs(L21 - L32)
Implementation Structure
The Linear Backend is implemented as a variant within the Backend<H> enum and provides three core operations through associated methods.
Backend Construction and Core Methods
Sources: src/address_space/backend/linear.rs(L7 - L11) src/address_space/backend/linear.rs(L13 - L51)
Core Operations
Backend Creation
The new_linear method creates a Linear Backend instance with a specified physical-to-virtual address offset:
| Method | Parameters | Return Type | Purpose |
|---|---|---|---|
| new_linear | pa_va_offset: usize | Backend | Creates Linear variant with fixed offset |
The method is marked as const, allowing compile-time initialization of linear mapping configurations.
Sources: src/address_space/backend/linear.rs(L8 - L11)
Memory Mapping Operations
The Linear Backend provides two primary mapping operations that work with the nested page table system:
Map Operation
The map_linear method establishes mappings in the page table using the linear translation strategy:
sequenceDiagram
participant Caller as Caller
participant Backendmap_linear as "Backend::map_linear"
participant AddressTranslation as "Address Translation"
participant PageTablemap_region as "PageTable::map_region"
Caller ->> Backendmap_linear: map_linear(start, size, flags, pt, offset)
Backendmap_linear ->> AddressTranslation: Calculate host_addr = guest_addr - offset
AddressTranslation -->> Backendmap_linear: PhysAddr
Backendmap_linear ->> PageTablemap_region: map_region(start, translation_fn, size, flags)
PageTablemap_region -->> Backendmap_linear: Result<()>
Backendmap_linear -->> Caller: bool (success/failure)
Note over Backendmap_linear,PageTablemap_region: Translation function:<br>|va| PhysAddr::from(va - offset)
Sources: src/address_space/backend/linear.rs(L13 - L39)
Unmap Operation
The unmap_linear method removes existing mappings from the page table:
| Parameter | Type | Description |
|---|---|---|
| start | GuestPhysAddr | Starting guest physical address |
| size | usize | Size of region to unmap |
| pt | &mut PageTable | Mutable reference to page table |
| _pa_va_offset | usize | Unused in unmap operation |
Sources: src/address_space/backend/linear.rs(L41 - L50)
Address Translation Mechanism
The Linear Backend implements address translation through a closure passed to the page table's map_region method. This translation function converts guest virtual addresses to host physical addresses by subtracting the configured offset:
flowchart TD
subgraph subGraph1["Page Table Integration"]
MapRegion["pt.map_region()"]
TransFn["Translation Function|va| PhysAddr::from(va - offset)"]
end
subgraph subGraph0["Linear Translation Process"]
GVA["Guest Virtual Address(va.as_usize())"]
Subtract["Subtraction Operationva - pa_va_offset"]
HPA["Host Physical AddressPhysAddr::from()"]
end
GVA --> Subtract
MapRegion --> TransFn
Subtract --> HPA
TransFn --> Subtract
The translation occurs at src/address_space/backend/linear.rs(L32) where the closure |va| PhysAddr::from(va.as_usize() - pa_va_offset) is passed to the page table mapping function.
Sources: src/address_space/backend/linear.rs(L30 - L37)
Integration with Nested Page Tables
The Linear Backend operates through the NestedPageTable<H> type (aliased as PageTable<H>), which provides hardware-abstracted page table operations. The backend leverages the page table's map_region and unmap_region methods to establish the actual memory mappings.
Page Table Method Parameters
| Method | Key Parameters | Linear Backend Usage |
|---|---|---|
| map_region | start,translate_fn,size,flags | Provides linear translation function |
| unmap_region | start,size,flush | Always flushes TLB (true) |
The Linear Backend sets specific parameters for page table operations:
- Always uses
falsefor both huge page flags inmap_region - Always flushes the TLB during
unmap_regionoperations - Provides a simple subtraction-based translation function
Sources: src/address_space/backend/linear.rs(L30 - L37) src/address_space/backend/linear.rs(L49)
Allocation Backend
Relevant source files
This document covers the dynamic allocation backend implementation for guest memory management in axaddrspace. The allocation backend provides flexible memory allocation strategies with support for both eager and lazy allocation patterns. For information about the alternative linear mapping strategy, see Linear Backend. For broader context on how backends fit into the address space management system, see Memory Mapping Backends and Address Space Management.
Overview
The allocation backend implements dynamic memory management for guest physical memory regions, where physical frames are allocated from the host system either eagerly at mapping time or lazily on first access. This backend is implemented as part of the Backend<H> enum and provides three core operations: mapping, unmapping, and page fault handling.
Allocation Backend Architecture
flowchart TD
subgraph subGraph3["Page Table Operations"]
PT["PageTable<H>"]
PT_MAP["pt.map()"]
PT_UNMAP["pt.unmap()"]
PT_REMAP["pt.remap()"]
PT_MAP_REGION["pt.map_region()"]
end
subgraph subGraph2["Physical Memory Management"]
HAL_ALLOC["H::alloc_frame()"]
HAL_DEALLOC["H::dealloc_frame()"]
FRAME["HostPhysAddr"]
end
subgraph subGraph1["Allocation Operations"]
NEW["new_alloc(populate: bool)"]
MAP["map_alloc()"]
UNMAP["unmap_alloc()"]
PF["handle_page_fault_alloc()"]
end
subgraph subGraph0["Backend Selection"]
BE["Backend<H>"]
ALLOC_VAR["Backend::Alloc { populate, _phantom }"]
end
ALLOC_VAR --> MAP
ALLOC_VAR --> NEW
ALLOC_VAR --> PF
ALLOC_VAR --> UNMAP
BE --> ALLOC_VAR
HAL_ALLOC --> FRAME
HAL_DEALLOC --> FRAME
MAP --> HAL_ALLOC
MAP --> PT_MAP
MAP --> PT_MAP_REGION
PF --> HAL_ALLOC
PF --> PT_REMAP
UNMAP --> HAL_DEALLOC
UNMAP --> PT_UNMAP
Sources: src/address_space/backend/alloc.rs(L7 - L14)
Allocation Strategies
The allocation backend supports two distinct allocation strategies controlled by the populate boolean parameter:
| Strategy | Description | Physical Frame Allocation | Page Fault Behavior |
|---|---|---|---|
| Eager (populate=true) | Physical frames allocated at mapping time | Immediate allocation duringmap_alloc() | Should not occur |
| Lazy (populate=false) | Physical frames allocated on first access | Deferred until page fault | Allocates frame on demand |
Eager Allocation Strategy
When populate is true, the backend performs immediate physical frame allocation for all pages in the requested range:
Eager Allocation Flow
Sources: src/address_space/backend/alloc.rs(L31 - L41)
Lazy Allocation Strategy
When populate is false, the backend creates empty page table entries that will trigger page faults on first access:
Lazy Allocation Flow
Sources: src/address_space/backend/alloc.rs(L42 - L54) src/address_space/backend/alloc.rs(L79 - L96)
Implementation Details
Backend Creation
The allocation backend is created through the new_alloc() constructor method:
pub const fn new_alloc(populate: bool) -> Self {
Self::Alloc {
populate,
_phantom: core::marker::PhantomData,
}
}
The populate parameter determines the allocation strategy for the lifetime of this backend instance.
Sources: src/address_space/backend/alloc.rs(L8 - L14)
Memory Mapping Process
The map_alloc() method handles the mapping process differently based on the populate flag:
Mapping Implementation Structure
flowchart TD
subgraph subGraph1["Lazy Branch"]
MAP_REGION["pt.map_region(start, |_va| PhysAddr::from(0), size, empty_flags)"]
SUCCESS_LAZY["Return pt.map_region().is_ok()"]
end
subgraph subGraph0["Populate Branch"]
POPULATE_CHECK["populate == true?"]
PAGE_ITER["PageIter4K::new(start, start + size)"]
ALLOC_FRAME["H::alloc_frame()"]
PT_MAP["pt.map(addr, frame, Size4K, flags)"]
SUCCESS_EAGER["Return true"]
FAIL_EAGER["Return false"]
end
MAP_ALLOC["map_alloc(start, size, flags, pt, populate)"]
ALLOC_FRAME --> PT_MAP
MAP_ALLOC --> POPULATE_CHECK
MAP_REGION --> SUCCESS_LAZY
PAGE_ITER --> ALLOC_FRAME
POPULATE_CHECK --> MAP_REGION
POPULATE_CHECK --> PAGE_ITER
PT_MAP --> FAIL_EAGER
PT_MAP --> SUCCESS_EAGER
Sources: src/address_space/backend/alloc.rs(L16 - L54)
Memory Unmapping Process
The unmap_alloc() method handles cleanup by iterating through all pages and deallocating physical frames:
- Uses
PageIter4Kto iterate through all 4KB pages in the range - Calls
pt.unmap()to remove page table entries - Deallocates physical frames via
H::dealloc_frame()when mappings exist - Gracefully handles pages that are not mapped
- Rejects huge page mappings for safety
Sources: src/address_space/backend/alloc.rs(L56 - L77)
Page Fault Handling
The allocation backend implements lazy allocation through page fault handling in handle_page_fault_alloc():
Page Fault Resolution Logic
flowchart TD
subgraph subGraph1["Lazy Allocation"]
ALLOC_FRAME["H::alloc_frame()"]
PT_REMAP["pt.remap(vaddr, frame, orig_flags)"]
SUCCESS_LAZY["Return success"]
end
subgraph subGraph0["Eager Allocation Error"]
ERROR_POPULATED["Return false"]
ERROR_NOTE["Populated mappings should not fault"]
end
PF_START["handle_page_fault_alloc(vaddr, orig_flags, pt, populate)"]
POPULATE_CHECK["populate == true?"]
ALLOC_FRAME --> PT_REMAP
ERROR_POPULATED --> ERROR_NOTE
PF_START --> POPULATE_CHECK
POPULATE_CHECK --> ALLOC_FRAME
POPULATE_CHECK --> ERROR_POPULATED
PT_REMAP --> SUCCESS_LAZY
Key characteristics:
- Eager allocation faults: Return false since populated mappings should never fault
- Lazy allocation faults: Allocate physical frame and remap the faulting virtual address
- Address alignment: The
pt.remap()method automatically handles address alignment
Sources: src/address_space/backend/alloc.rs(L79 - L96)
Memory Management Lifecycle
The allocation backend integrates with the hardware abstraction layer for physical memory management:
- Frame Allocation: Uses
H::alloc_frame()to obtainHostPhysAddrvalues - Page Table Integration: Works with
PageTable<H>for virtual-to-physical mappings - Frame Deallocation: Uses
H::dealloc_frame()during unmapping operations - Size Constraints: Currently supports only 4KB pages, rejecting huge page operations
The backend ensures proper resource management through RAII principles and explicit deallocation during unmapping operations.
Sources: src/address_space/backend/alloc.rs(L1 - L98)
Device Support
Relevant source files
Purpose and Scope
The device support module provides abstractions for hardware device interaction within the axaddrspace crate. This system defines standardized address types and access patterns for different classes of devices, including memory-mapped devices, I/O ports, and system registers. The module establishes a trait-based architecture that allows uniform handling of diverse device addressing schemes while maintaining type safety.
For information about memory mapping strategies used with devices, see Memory Mapping Backends. For details about the underlying address space management, see Address Space Management.
Device Address Architecture
The device support system is built around two core traits that provide a foundation for device addressing abstractions.
Device Address Trait Hierarchy
flowchart TD DeviceAddr["DeviceAddr traitCopy + Eq + Ord + Debug"] DeviceAddrRange["DeviceAddrRange traitcontains() method"] GuestPhysAddr["GuestPhysAddrPhysical memory addresses"] Port["PortI/O port numbers (u16)"] SysRegAddr["SysRegAddrSystem register addresses (usize)"] AddrRange["AddrRange<GuestPhysAddr>Standard address range"] SysRegAddrRange["SysRegAddrRangeInclusive register range"] PortRange["PortRangeInclusive port range"] DeviceAddr --> GuestPhysAddr DeviceAddr --> Port DeviceAddr --> SysRegAddr DeviceAddrRange --> AddrRange DeviceAddrRange --> PortRange DeviceAddrRange --> SysRegAddrRange GuestPhysAddr --> AddrRange Port --> PortRange SysRegAddr --> SysRegAddrRange
Sources: src/device/device_addr.rs(L9 - L10) src/device/device_addr.rs(L12 - L19) src/device/mod.rs(L66 - L132)
The DeviceAddr trait defines the minimal requirements for types that can represent device addresses. All implementing types must be copyable, comparable, orderable, and debuggable. The DeviceAddrRange trait provides address containment checking for ranges of device addresses.
Device Address Implementations
| Address Type | Underlying Type | Purpose | Range Support |
|---|---|---|---|
| GuestPhysAddr | Guest physical address | Memory-mapped devices | AddrRange |
| Port | u16 | I/O port operations | PortRange |
| SysRegAddr | usize | System register access | SysRegAddrRange |
Sources: src/device/device_addr.rs(L21) src/device/device_addr.rs(L31) src/device/device_addr.rs(L65)
Access Width Specifications
The system defines standardized access widths that correspond to different data transfer sizes during device operations.
AccessWidth Enumeration
flowchart TD
subgraph subGraph0["Conversion Methods"]
TryFromUsize["try_from(usize)"]
IntoUsize["into usize"]
Size["size() -> usize"]
BitsRange["bits_range() -> Range<usize>"]
end
AccessWidth["AccessWidth enum"]
Byte["Byte8-bit (1 byte)"]
Word["Word16-bit (2 bytes)"]
Dword["Dword32-bit (4 bytes)"]
Qword["Qword64-bit (8 bytes)"]
AccessWidth --> BitsRange
AccessWidth --> Byte
AccessWidth --> Dword
AccessWidth --> IntoUsize
AccessWidth --> Qword
AccessWidth --> Size
AccessWidth --> TryFromUsize
AccessWidth --> Word
Sources: src/device/mod.rs(L9 - L64)
The AccessWidth enum provides bidirectional conversion between access sizes and byte counts, along with utility methods for determining bit ranges covered by each access type.
I/O Port Addressing
The Port type encapsulates 16-bit I/O port numbers used in x86-style port-mapped I/O operations.
Port Type Structure
The Port struct wraps a u16 value and provides formatting implementations for debugging and display purposes. Port numbers are used to identify specific I/O devices in architectures that support port-mapped I/O.
flowchart TD
subgraph subGraph2["Range Support"]
PortRange["PortRangestart: Port, end: Port"]
PortRangeNew["new(start: Port, end: Port)"]
PortRangeContains["contains(addr: Port) -> bool"]
end
subgraph Formatting["Formatting"]
LowerHex["LowerHexPort(#x)"]
UpperHex["UpperHexPort(#X)"]
Debug["DebugPort(decimal)"]
end
subgraph Creation["Creation"]
NewMethod["new(port: u16) -> Port"]
NumberMethod["number() -> u16"]
end
Port["Port(u16)"]
Port --> Debug
Port --> LowerHex
Port --> NewMethod
Port --> NumberMethod
Port --> PortRange
Port --> UpperHex
PortRange --> PortRangeContains
PortRange --> PortRangeNew
Sources: src/device/mod.rs(L66 - L98) src/device/device_addr.rs(L67 - L97)
The PortRange struct provides inclusive range checking for port numbers, supporting both formatted output and containment testing.
System Register Addressing
The SysRegAddr type represents addresses used to access architecture-specific system registers.
System Register Type Structure
System register addresses use usize to accommodate different architecture requirements while maintaining generality across platforms. The inclusive range type supports efficient range checking for register banks.
flowchart TD
subgraph subGraph2["Range Support"]
SysRegAddrRange["SysRegAddrRangestart: SysRegAddr, end: SysRegAddr"]
SysRangeNew["new(start: SysRegAddr, end: SysRegAddr)"]
SysRangeContains["contains(addr: SysRegAddr) -> bool"]
SysRangeFormat["LowerHex formatting#x..=#x"]
end
subgraph Formatting["Formatting"]
SysLowerHex["LowerHexSysRegAddr(#x)"]
SysUpperHex["UpperHexSysRegAddr(#X)"]
SysDebug["DebugSysRegAddr(decimal)"]
end
subgraph Creation["Creation"]
NewConst["const new(addr: usize) -> SysRegAddr"]
AddrConst["const addr() -> usize"]
end
SysRegAddr["SysRegAddr(usize)"]
SysRegAddr --> AddrConst
SysRegAddr --> NewConst
SysRegAddr --> SysDebug
SysRegAddr --> SysLowerHex
SysRegAddr --> SysRegAddrRange
SysRegAddr --> SysUpperHex
SysRegAddrRange --> SysRangeContains
SysRegAddrRange --> SysRangeFormat
SysRegAddrRange --> SysRangeNew
Sources: src/device/mod.rs(L100 - L132) src/device/device_addr.rs(L31 - L63)
The SysRegAddrRange struct uses inclusive bounds on both ends, unlike standard Rust ranges, and provides specialized formatting for register address ranges.
Guest Physical Address Device Support
Guest physical addresses serve as device addresses for memory-mapped I/O operations within the guest address space.
GuestPhysAddr Integration
The integration leverages the existing memory_addr::AddrRange type to provide standard range operations while maintaining compatibility with the device address abstraction framework.
Sources: src/device/device_addr.rs(L21 - L29)
The GuestPhysAddr implementation uses the standard AddrRange<GuestPhysAddr> for range operations, providing consistency with other address management components in the system.
Development Guide
Relevant source files
This document provides essential information for developers working on the axaddrspace crate, including environment setup, build procedures, testing, and contribution guidelines. For information about the codebase architecture and components, see Core Architecture. For usage examples and API documentation, see Overview.
Development Environment Setup
The axaddrspace crate requires a Rust nightly toolchain with specific components and target platforms for cross-architecture development.
Required Toolchain Components
The project requires the following Rust toolchain components as configured in the CI pipeline:
| Component | Purpose |
|---|---|
| rust-src | Source code for standard library cross-compilation |
| clippy | Linting and static analysis |
| rustfmt | Code formatting |
Supported Target Platforms
The codebase supports multiple target architectures for hypervisor deployment:
flowchart TD
subgraph subGraph2["Target Categories"]
hosted["Hosted Environment(Linux userspace)"]
bare_metal["Bare Metal(No OS)"]
end
subgraph subGraph1["Supported Build Targets"]
x86_hosted["x86_64-unknown-linux-gnu"]
x86_bare["x86_64-unknown-none"]
riscv_bare["riscv64gc-unknown-none-elf"]
aarch64_bare["aarch64-unknown-none-softfloat"]
end
subgraph subGraph0["Rust Toolchain Configuration"]
toolchain["nightly toolchain"]
end
aarch64_bare --> bare_metal
riscv_bare --> bare_metal
toolchain --> aarch64_bare
toolchain --> riscv_bare
toolchain --> x86_bare
toolchain --> x86_hosted
x86_bare --> bare_metal
x86_hosted --> hosted
Target Platform Configuration Sources: .github/workflows/ci.yml(L12)
Build System and CI Pipeline
The project uses GitHub Actions for continuous integration with a matrix build strategy covering all supported architectures.
CI Pipeline Architecture
flowchart TD
subgraph subGraph3["Documentation Job Steps"]
doc_build["cargo doc --no-deps --all-features"]
doc_deploy["JamesIves/github-pages-deploy-action@v4"]
end
subgraph subGraph2["CI Job Steps"]
checkout["actions/checkout@v4"]
toolchain["dtolnay/rust-toolchain@nightly"]
version_check["rustc --version --verbose"]
format_check["cargo fmt --all -- --check"]
clippy_check["cargo clippy --target TARGET --all-features"]
build_step["cargo build --target TARGET --all-features"]
test_step["cargo test --target x86_64-unknown-linux-gnu"]
end
subgraph subGraph1["Build Matrix Jobs"]
ci_job["ci job"]
doc_job["doc job"]
end
subgraph subGraph0["GitHub Actions Workflow"]
trigger["Push/PR Trigger"]
matrix["Matrix Strategy"]
end
build_step --> test_step
checkout --> toolchain
ci_job --> checkout
clippy_check --> build_step
doc_build --> doc_deploy
doc_job --> doc_build
format_check --> clippy_check
matrix --> ci_job
matrix --> doc_job
toolchain --> version_check
trigger --> matrix
version_check --> format_check
CI Workflow Configuration Sources: .github/workflows/ci.yml(L1 - L31)
Build Commands
The CI pipeline executes the following build and verification commands:
| Command | Purpose | Target Scope |
|---|---|---|
| cargo fmt --all -- --check | Code formatting verification | All files |
| cargo clippy --target $TARGET --all-features | Static analysis | Per target |
| cargo build --target $TARGET --all-features | Compilation | Per target |
| cargo test --target x86_64-unknown-linux-gnu | Unit testing | Hosted target only |
Sources: .github/workflows/ci.yml(L23 - L30)
Testing Procedures
Unit Testing
Unit tests are executed only on the x86_64-unknown-linux-gnu target, which provides a hosted environment with full standard library support. Tests run with the --nocapture flag to display all output.
The testing is configured to run conditionally:
if: ${{ matrix.targets == 'x86_64-unknown-linux-gnu' }}
This approach ensures tests have access to necessary host OS features while maintaining cross-platform build verification.
Sources: .github/workflows/ci.yml(L28 - L30)
Code Quality Checks
The CI pipeline enforces code quality through automated checks:
- Format Verification: Ensures consistent code formatting using
rustfmt - Linting: Uses
clippywith custom configuration allowingnew_without_defaultwarnings - Build Verification: Compiles all targets with full feature sets enabled
Sources: .github/workflows/ci.yml(L22 - L27)
Documentation Generation
The project maintains automated documentation generation and deployment through GitHub Pages.
Documentation Build Process
flowchart TD
subgraph subGraph2["Output Structure"]
target_doc["target/doc/"]
crate_docs["Crate Documentation"]
index_redirect["index.html(auto-redirect)"]
end
subgraph subGraph1["Documentation Flags"]
rustdoc_flags["RUSTDOCFLAGS"]
broken_links["-D rustdoc::broken_intra_doc_links"]
missing_docs["-D missing-docs"]
end
subgraph subGraph0["Documentation Generation"]
doc_trigger["Push to Default Branch"]
doc_build["cargo doc --no-deps --all-features"]
index_gen["Generate index.html redirect"]
doc_deploy["Deploy to gh-pages"]
end
broken_links --> doc_build
doc_build --> index_gen
doc_build --> target_doc
doc_trigger --> doc_build
index_gen --> doc_deploy
missing_docs --> doc_build
rustdoc_flags --> broken_links
rustdoc_flags --> missing_docs
target_doc --> crate_docs
target_doc --> index_redirect
Documentation Configuration Sources: .github/workflows/ci.yml(L32 - L55)
Documentation Standards
The documentation build process enforces strict standards:
- Broken Link Detection: Fails on broken intra-doc links using
-D rustdoc::broken_intra_doc_links - Missing Documentation: Requires documentation for all public items using
-D missing-docs - Auto-deployment: Automatically deploys to GitHub Pages on pushes to the default branch
The documentation includes an auto-generated index redirect that points to the main crate documentation.
Sources: .github/workflows/ci.yml(L40 - L48)
Code Quality Standards
Linting Configuration
The project uses clippy for static analysis with the following configuration:
- Allows
clippy::new_without_defaultwarnings using-A clippy::new_without_default - Runs with
--all-featuresto check all conditional compilation paths - Executes on all supported target platforms
Format Standards
Code formatting is enforced using rustfmt with the --check flag to ensure consistent style across the codebase. All files must pass formatting checks before merge.
Sources: .github/workflows/ci.yml(L23 - L25)
Development Workflow
- Environment Setup: Install Rust nightly with required components and targets
- Local Development: Use standard
cargocommands for building and testing - Pre-commit Checks: Run
cargo fmt,cargo clippy, andcargo testlocally - CI Validation: All checks must pass in the automated CI pipeline
- Documentation: Ensure all public APIs are documented to meet
-D missing-docsrequirements
The automated CI pipeline provides comprehensive validation across all supported architectures, ensuring code quality and cross-platform compatibility.
Sources: .github/workflows/ci.yml(L1 - L55) .gitignore(L1 - L5)
Overview
Relevant source files
Purpose and Scope
This document provides an overview of the riscv_vcpu codebase, which implements a RISC-V virtual CPU (VCPU) system designed for hypervisor environments. The system provides virtualization capabilities for RISC-V guest virtual machines running within the ArceOS hypervisor framework.
The riscv_vcpu crate serves as a foundational layer that abstracts RISC-V hardware virtualization features, implements the Supervisor Binary Interface (SBI), and manages virtual CPU lifecycle operations. It requires RISC-V H-extension hardware support to enable efficient guest VM execution with hardware-assisted virtualization.
For detailed information about the core VCPU implementation and execution flow, see Core VCPU Implementation. For system architecture and supporting components, see System Architecture. For low-level assembly and hardware interface details, see Low-Level Implementation.
System Architecture Overview
The RISC-V VCPU system follows a layered architecture that integrates with the ArceOS hypervisor framework while providing hardware-accelerated virtualization for RISC-V guest VMs.
RISC-V VCPU System Architecture
flowchart TD
subgraph subGraph4["Hardware Layer"]
riscv_h_ext["RISC-V H-ExtensionHardware virtualization support"]
riscv_core["RISC-V CoreBase ISA + Extensions"]
end
subgraph subGraph3["Low-Level Systems"]
trap_s["trap.SAssembly trap handlersGuest entry/exit"]
regs_rs["regs.rsVmCpuRegistersRegister state management"]
csr_def["def.rsCSR definitionsHardware register interface"]
end
subgraph subGraph2["Core Implementation Layer"]
vcpu_rs["vcpu.rsRISCVVCpu structVM execution & SBI handling"]
percpu_rs["percpu.rsRISCVPerCpu structPer-CPU state management"]
detect_rs["detect.rsdetect_h_extension()Hardware capability detection"]
end
subgraph subGraph1["riscv_vcpu Public Interface"]
lib_rs["lib.rsRISCVVCpu, RISCVPerCpuhas_hardware_support()"]
create_config["RISCVVCpuCreateConfighart_id, dtb_addr"]
eid_hvc["EID_HVCHypercall Extension ID"]
end
subgraph subGraph0["ArceOS Framework Layer"]
axvcpu["axvcpuVCPU Abstraction"]
axaddrspace["axaddrspaceAddress Space Management"]
end
axaddrspace --> lib_rs
axvcpu --> lib_rs
create_config --> vcpu_rs
csr_def --> riscv_core
detect_rs --> csr_def
lib_rs --> detect_rs
lib_rs --> percpu_rs
lib_rs --> vcpu_rs
percpu_rs --> csr_def
regs_rs --> csr_def
trap_s --> riscv_h_ext
vcpu_rs --> csr_def
vcpu_rs --> regs_rs
vcpu_rs --> trap_s
Sources: src/lib.rs(L1 - L46) Cargo.toml(L1 - L26)
Key Components and Data Flow
The system operates through well-defined interfaces that manage the complete lifecycle of virtual CPU operations, from hardware detection through guest execution.
Component Interaction and Data Flow
flowchart TD
subgraph subGraph3["External Interface"]
guest_vm["Guest Virtual Machine"]
host_hypervisor["Host Hypervisor"]
rustsbi["RustSBI Framework"]
end
subgraph subGraph2["State Management"]
vm_cpu_regs["VmCpuRegistersregs.rs"]
csr_setup["setup_csrspercpu.rs"]
register_sync["Register synchronizationGeneral purpose & CSRs"]
end
subgraph subGraph1["Runtime Execution"]
guest_entry["_run_guesttrap.S assembly"]
vm_exit_handler["vmexit_handlervcpu.rs"]
sbi_processing["SBI call handlingvcpu.rs"]
end
subgraph subGraph0["Initialization Flow"]
hw_detect["has_hardware_support()detect.rs"]
percpu_init["RISCVPerCpu::new()percpu.rs"]
vcpu_create["RISCVVCpu::new()vcpu.rs"]
end
csr_setup --> vm_cpu_regs
guest_entry --> register_sync
guest_entry --> vm_exit_handler
guest_vm --> guest_entry
hw_detect --> percpu_init
percpu_init --> vcpu_create
rustsbi --> guest_vm
sbi_processing --> guest_entry
sbi_processing --> rustsbi
vcpu_create --> csr_setup
vm_exit_handler --> host_hypervisor
vm_exit_handler --> register_sync
vm_exit_handler --> sbi_processing
Sources: src/lib.rs(L18 - L20) src/vcpu.rs src/percpu.rs src/detect.rs
Hardware Requirements and Dependencies
The RISC-V VCPU system requires specific hardware and software dependencies to function correctly.
| Component | Requirement | Detection Method | Purpose |
|---|---|---|---|
| RISC-V H-Extension | Hardware support required | detect_h_extension() | Hardware-assisted virtualization |
| Base RISC-V ISA | RV64 or RV32 | Compile-time configuration | Core instruction set |
| SBI Implementation | RustSBI framework | Runtime dependency | Guest-host interface |
| Address Space Management | axaddrspace crate | Framework integration | Memory virtualization |
The system performs runtime hardware detection using a trap-based mechanism implemented in detect.rs to verify H-extension availability before initializing virtualization capabilities.
Hardware Detection and Initialization Sequence
flowchart TD start["System Boot"] hw_check["has_hardware_support()calls detect_h_extension()"] percpu_new["RISCVPerCpu::new()Initialize per-CPU state"] hw_fail["Hardware not supportedReturn error"] csr_init["setup_csrs()Configure hypervisor CSRs"] vcpu_ready["System ready forVCPU creation"] vcpu_create["RISCVVCpu::new()with RISCVVCpuCreateConfig"] guest_ready["Guest VM readyfor execution"] end_fail["Virtualization unavailable"] end_success["VCPU operational"] csr_init --> vcpu_ready guest_ready --> end_success hw_check --> hw_fail hw_check --> percpu_new hw_fail --> end_fail percpu_new --> csr_init start --> hw_check vcpu_create --> guest_ready vcpu_ready --> vcpu_create
Sources: src/detect.rs src/lib.rs(L20) src/percpu.rs
Integration with ArceOS Framework
The riscv_vcpu crate integrates seamlessly with the broader ArceOS hypervisor ecosystem through well-defined interfaces and shared abstractions.
| ArceOS Component | Integration Point | Purpose |
|---|---|---|
| axvcpu | Trait implementation | Generic VCPU abstraction |
| axaddrspace | GuestPhysAddrtype | Guest physical address management |
| RustSBI | SBI call forwarding | Guest system call interface |
| riscv crate | Hardware intrinsics | Low-level RISC-V operations |
The crate exports three primary interfaces through lib.rs:
RISCVVCpu<H>: The main virtual CPU implementationRISCVPerCpu<H>: Per-CPU state managementhas_hardware_support(): Hardware capability detection function
These components work together to provide a complete RISC-V virtualization solution that abstracts hardware complexity while maintaining high performance through hardware-assisted virtualization features.
Sources: src/lib.rs(L1 - L46) Cargo.toml(L25 - L26)
Core VCPU Implementation
Relevant source files
Purpose and Scope
This document covers the core virtual CPU implementation centered around the RISCVVCpu struct and its fundamental operations. The RISCVVCpu serves as the primary abstraction for executing RISC-V guest virtual machines within the ArceOS hypervisor framework, handling register state management, guest execution control, and hypervisor interactions.
For detailed information about VCPU creation and lifecycle management, see VCPU Lifecycle and Management. For SBI call processing and hypercall handling, see SBI Interface and Hypercalls. For VM exit event processing, see VM Exit Processing.
Core Data Structures
The VCPU implementation is built around several key data structures that manage virtual CPU state and execution context:
RISCVVCpu Structure
The main RISCVVCpu<H: AxVCpuHal> struct src/vcpu.rs(L23 - L27) encapsulates all virtual CPU state:
| Field | Type | Purpose |
|---|---|---|
| regs | VmCpuRegisters | Complete guest register state including GPRs, CSRs, and trap context |
| sbi | RISCVVCpuSbi | SBI interface implementation for handling supervisor binary interface calls |
| _marker | PhantomData | Type marker for hardware abstraction layer integration |
SBI Integration Structure
The RISCVVCpuSbi struct src/vcpu.rs(L29 - L33) implements the RustSBI interface using the #[derive(RustSBI)] macro, providing automatic forwarding for standard SBI extensions including console, PMU, fence, reset, info, and HSM operations.
Configuration Structures
The system defines two configuration types:
VCpuConfigsrc/vcpu.rs(L18 - L19) - Architecture-dependent configuration (currently empty)RISCVVCpuCreateConfigsrc/lib.rs(L29 - L36) - Creation-time configuration including hart ID and device tree address
Architecture Overview
The following diagram illustrates the core VCPU architecture and its relationship to key code entities:
flowchart TD
subgraph subGraph3["External Assembly Interface"]
RunGuest["_run_guest()Assembly Entry Point"]
TrapHandler["Trap Handlingtrap.S"]
end
subgraph subGraph2["Internal Implementation Methods"]
VmexitHandler["vmexit_handler()VM Exit Processing"]
AdvancePc["advance_pc()PC Manipulation"]
GetGpr["get_gpr()Register Access"]
SetGpr["set_gpr_from_gpr_index()Register Modification"]
end
subgraph subGraph1["AxArchVCpu Trait Implementation"]
NewMethod["new()VCPU Creation"]
SetupMethod["setup()CSR Configuration"]
RunMethod["run()Guest Execution"]
BindMethod["bind()Memory Binding"]
SetEntryMethod["set_entry()Entry Point Setup"]
SetEptRootMethod["set_ept_root()Page Table Root"]
end
subgraph subGraph0["RISCVVCpu Core Structure"]
RISCVVCpu["RISCVVCpu<H>Main VCPU Implementation"]
VmCpuRegisters["VmCpuRegistersComplete Register State"]
RISCVVCpuSbi["RISCVVCpuSbiSBI Interface Handler"]
end
RISCVVCpu --> BindMethod
RISCVVCpu --> NewMethod
RISCVVCpu --> RISCVVCpuSbi
RISCVVCpu --> RunMethod
RISCVVCpu --> SetEntryMethod
RISCVVCpu --> SetEptRootMethod
RISCVVCpu --> SetupMethod
RISCVVCpu --> VmCpuRegisters
RunGuest --> TrapHandler
RunMethod --> RunGuest
RunMethod --> VmexitHandler
TrapHandler --> VmexitHandler
VmexitHandler --> AdvancePc
VmexitHandler --> GetGpr
VmexitHandler --> SetGpr
Sources: src/vcpu.rs(L23 - L164)
VCPU State Management
The VCPU maintains comprehensive state through the VmCpuRegisters structure, which includes guest general-purpose registers, control and status registers, and trap context information.
Register Access Interface
The VCPU provides controlled access to guest registers through dedicated methods:
flowchart TD
subgraph subGraph1["Internal Storage"]
GuestRegs["guest_regs.gprsGeneralPurposeRegisters"]
VirtualCSRs["virtual_hs_csrsHypervisor CSRs"]
TrapCSRs["trap_csrsTrap Context"]
end
subgraph subGraph0["Register Access Methods"]
GetGpr["get_gpr(GprIndex)Read Guest Register"]
SetGprIndex["set_gpr_from_gpr_index()Write by GprIndex"]
SetGprOffset["set_gpr(usize, usize)Write by Offset"]
end
GetGpr --> GuestRegs
GuestRegs --> TrapCSRs
GuestRegs --> VirtualCSRs
SetGprIndex --> GuestRegs
SetGprOffset --> GuestRegs
Sources: src/vcpu.rs(L129 - L153)
Execution Control Flow
The VCPU execution follows a structured pattern that integrates with the hypervisor's execution model:
Core Execution Cycle
sequenceDiagram
participant HostHypervisor as "Host/Hypervisor"
participant RISCVVCpurun as "RISCVVCpu::run()"
participant _run_guest as "_run_guest()"
participant vmexit_handler as "vmexit_handler()"
HostHypervisor ->> RISCVVCpurun: Call run()
RISCVVCpurun ->> RISCVVCpurun: Setup interrupts (sie)
RISCVVCpurun ->> _run_guest: _run_guest(&mut regs)
_run_guest ->> _run_guest: Execute guest code
_run_guest ->> vmexit_handler: VM Exit occurs
vmexit_handler ->> vmexit_handler: Read trap CSRs
vmexit_handler ->> vmexit_handler: Process exit reason
vmexit_handler ->> RISCVVCpurun: Return AxVCpuExitReason
RISCVVCpurun ->> RISCVVCpurun: Cleanup interrupts
RISCVVCpurun ->> HostHypervisor: Return exit reason
Sources: src/vcpu.rs(L92 - L111) src/vcpu.rs(L167 - L308)
Integration with ArceOS Framework
The VCPU implements the AxArchVCpu trait src/vcpu.rs(L42 - L142) providing standardized interfaces for:
| Method | Purpose | Key Operations |
|---|---|---|
| new() | VCPU creation | Initialize registers, set hart ID and DTB address |
| setup() | Configuration | ConfiguresstatusandhstatusCSRs |
| set_entry() | Entry point | Set guest program counter (sepc) |
| set_ept_root() | Memory setup | Configure hypervisor guest address translation (hgatp) |
| bind() | Memory binding | Activate guest page tables and invalidate TLB |
| run() | Execution | Execute guest code and handle VM exits |
Hardware Abstraction Layer Integration
The VCPU uses a generic type parameter H: AxVCpuHal src/vcpu.rs(L23) to integrate with hardware abstraction layers, enabling platform-specific optimizations while maintaining a consistent interface.
Sources: src/vcpu.rs(L42 - L142) src/lib.rs(L18 - L20)
Memory Management Integration
The VCPU integrates with the ArceOS address space management through several key mechanisms:
Extended Page Table Support
The set_ept_root() method src/vcpu.rs(L87 - L90) configures the hgatp CSR with an Sv48 page table format (mode 8) and the physical page number of the root page table, enabling two-stage address translation for guest memory access.
Memory Binding Operations
The bind() and unbind() methods src/vcpu.rs(L113 - L126) manage the activation and deactivation of guest address spaces, including TLB invalidation through hfence_gvma_all() to ensure memory consistency.
Sources: src/vcpu.rs(L87 - L90) src/vcpu.rs(L113 - L126)
VCPU Lifecycle and Management
Relevant source files
This document covers the complete lifecycle management of RISC-V virtual CPUs in the hypervisor system, from initial creation through execution and cleanup. It details the RISCVVCpu implementation, register state management, and the coordination between guest and hypervisor execution contexts.
For information about SBI interface handling during VCPU execution, see SBI Interface and Hypercalls. For details about VM exit processing, see VM Exit Processing. For per-CPU system initialization, see Per-CPU Management.
VCPU Creation and Initialization
The VCPU lifecycle begins with the creation of a RISCVVCpu<H> instance through the new() method. This process establishes the fundamental guest execution environment and initializes the register state required for virtual machine operation.
flowchart TD CreateConfig["RISCVVCpuCreateConfig"] NewMethod["RISCVVCpu::new()"] RegInit["VmCpuRegisters::default()"] SetHartId["Set A0 register (hart_id)"] SetDTB["Set A1 register (dtb_addr)"] SBIInit["RISCVVCpuSbi::default()"] GuestRegs["GuestCpuState"] HypRegs["HypervisorCpuState"] VsCSRs["GuestVsCsrs"] VirtualHsCSRs["GuestVirtualHsCsrs"] TrapCSRs["VmCpuTrapState"] VCPUCreated["RISCVVCpu Instance"] CreateConfig --> NewMethod GuestRegs --> VCPUCreated HypRegs --> VCPUCreated NewMethod --> RegInit NewMethod --> SBIInit NewMethod --> SetDTB NewMethod --> SetHartId RegInit --> GuestRegs RegInit --> HypRegs RegInit --> TrapCSRs RegInit --> VirtualHsCSRs RegInit --> VsCSRs SBIInit --> VCPUCreated SetDTB --> VCPUCreated SetHartId --> VCPUCreated TrapCSRs --> VCPUCreated VirtualHsCSRs --> VCPUCreated VsCSRs --> VCPUCreated
The creation process initializes several critical components:
| Component | Purpose | Configuration |
|---|---|---|
| regs.guest_regs.gprs | General purpose registers | A0 = hart_id, A1 = dtb_addr |
| sbi | SBI interface handler | Forward-only RustSBI implementation |
| regs | Complete register state | Default-initialized structures |
Sources: src/vcpu.rs(L47 - L62) src/regs.rs(L179 - L196)
VCPU Setup and Configuration
After creation, the VCPU undergoes setup to establish the proper execution environment. This involves configuring supervisor-level and hypervisor-level control and status registers.
flowchart TD SetupCall["setup() method"] ReadSstatus["sstatus::read()"] ConfigSstatus["Set SPP = Supervisor"] WriteSstatus["Store in guest_regs.sstatus"] ReadHstatus["hstatus::read()"] ConfigHstatus["Set SPV = true, SPVP = true"] WriteHstatus["hstatus.write() & store"] SetupComplete["Setup Complete"] SetEntry["set_entry(entry_point)"] SetEPT["set_ept_root(ept_root)"] ConfigSEPC["guest_regs.sepc = entry"] ConfigHGATP["virtual_hs_csrs.hgatp = ept_config"] ConfigHstatus --> WriteHstatus ConfigSstatus --> WriteSstatus ReadHstatus --> ConfigHstatus ReadSstatus --> ConfigSstatus SetEPT --> ConfigHGATP SetEntry --> ConfigSEPC SetupCall --> ReadHstatus SetupCall --> ReadSstatus SetupComplete --> SetEPT SetupComplete --> SetEntry WriteHstatus --> SetupComplete WriteSstatus --> SetupComplete
The setup process configures two critical status registers:
- sstatus Configuration: Sets the previous privilege level to Supervisor mode, ensuring proper guest execution context
- hstatus Configuration: Enables virtualization (
SPV=true) and VS-mode memory access from HS-mode (SPVP=true)
Additional configuration methods complete the VCPU preparation:
| Method | Purpose | Register Modified |
|---|---|---|
| set_entry() | Sets guest entry point | guest_regs.sepc |
| set_ept_root() | Configures page table root | virtual_hs_csrs.hgatp |
Sources: src/vcpu.rs(L64 - L90)
Runtime State Management
The VCPU maintains extensive state information through the VmCpuRegisters structure, which encompasses all register contexts needed for virtualization.
Register State Architecture
flowchart TD VmCpuRegisters["VmCpuRegisters"] HypRegs["hyp_regs: HypervisorCpuState"] GuestRegs["guest_regs: GuestCpuState"] VsCSRs["vs_csrs: GuestVsCsrs"] VirtualHsCSRs["virtual_hs_csrs: GuestVirtualHsCsrs"] TrapCSRs["trap_csrs: VmCpuTrapState"] HypGPRs["gprs: GeneralPurposeRegisters"] HypStatus["sstatus, hstatus, scounteren"] HypVec["stvec, sscratch"] GuestGPRs["gprs: GeneralPurposeRegisters"] GuestStatus["sstatus, hstatus, scounteren"] GuestPC["sepc"] VsState["vsstatus, vsie, vstvec"] VsTrap["vsepc, vscause, vstval"] VsTime["htimedelta, vstimecmp"] VsMMU["vsatp"] VirtualInt["hie, hgeie"] VirtualMMU["hgatp"] TrapInfo["scause, stval, htval, htinst"] GuestRegs --> GuestGPRs GuestRegs --> GuestPC GuestRegs --> GuestStatus HypRegs --> HypGPRs HypRegs --> HypStatus HypRegs --> HypVec TrapCSRs --> TrapInfo VirtualHsCSRs --> VirtualInt VirtualHsCSRs --> VirtualMMU VmCpuRegisters --> GuestRegs VmCpuRegisters --> HypRegs VmCpuRegisters --> TrapCSRs VmCpuRegisters --> VirtualHsCSRs VmCpuRegisters --> VsCSRs VsCSRs --> VsMMU VsCSRs --> VsState VsCSRs --> VsTime VsCSRs --> VsTrap
Register Access Interface
The VCPU provides controlled access to register state through dedicated methods:
| Method | Purpose | Register Type |
|---|---|---|
| get_gpr() | Read general purpose register | Guest GPRs |
| set_gpr_from_gpr_index() | Write general purpose register | Guest GPRs |
| advance_pc() | Increment program counter | Guest SEPC |
| regs() | Access complete register state | All registers |
Sources: src/vcpu.rs(L144 - L163) src/regs.rs(L1 - L196)
Execution Lifecycle
The VCPU execution cycle involves binding to a physical CPU, running guest code, and handling exits. This process manages the transition between hypervisor and guest execution contexts.
Execution Flow
flowchart TD BindCall["bind()"] SetHGATP["csrw hgatp, virtual_hs_csrs.hgatp"] FlushTLB["hfence_gvma_all()"] Bound["VCPU Bound to CPU"] RunCall["run()"] DisableInts["sstatus::clear_sie()"] EnableExts["Enable external/software/timer interrupts"] RunGuest["_run_guest(&mut regs)"] VMExit["Guest exits to hypervisor"] DisableExts["Disable guest interrupts"] EnableInts["sstatus::set_sie()"] VMExitHandler["vmexit_handler()"] ExitReason["AxVCpuExitReason"] CheckContinue["Continue execution?"] UnbindCall["unbind()"] Unbound["VCPU Unbound"] BindCall --> SetHGATP Bound --> RunCall CheckContinue --> RunCall CheckContinue --> UnbindCall DisableExts --> EnableInts DisableInts --> EnableExts EnableExts --> RunGuest EnableInts --> VMExitHandler ExitReason --> CheckContinue FlushTLB --> Bound RunCall --> DisableInts RunGuest --> VMExit SetHGATP --> FlushTLB UnbindCall --> Unbound VMExit --> DisableExts VMExitHandler --> ExitReason
Interrupt Management During Execution
The execution cycle carefully manages interrupt state to ensure proper isolation between guest and hypervisor:
Pre-execution Setup src/vcpu.rs(L93 - L98) :
- Disable supervisor interrupts (
sstatus::clear_sie()) - Enable external, software, and timer interrupt delegation
- Prepare for guest entry
Post-execution Cleanup src/vcpu.rs(L104 - L109) :
- Disable interrupt delegation
- Re-enable supervisor interrupts
- Return control to hypervisor
Sources: src/vcpu.rs(L92 - L126)
Memory and Address Space Management
The VCPU manages guest physical address translation through the hypervisor's extended page table mechanism and maintains proper memory isolation.
Memory Management Components
flowchart TD GuestVA["Guest Virtual Address"] GuestPT["Guest Page Table (vsatp)"] GuestPA["Guest Physical Address"] HGATP["Hypervisor Gate Page Table (hgatp)"] HostPA["Host Physical Address"] SetEPTRoot["set_ept_root()"] ConfigHGATP["Configure virtual_hs_csrs.hgatp"] TwoStageTranslation["Two-stage Address Translation"] BindVCPU["bind()"] LoadHGATP["Load hgatp into hardware"] FlushTLB["Flush guest TLB (hfence_gvma_all)"] BindVCPU --> LoadHGATP ConfigHGATP --> TwoStageTranslation GuestPA --> HGATP GuestPT --> GuestPA GuestVA --> GuestPT HGATP --> HostPA LoadHGATP --> FlushTLB SetEPTRoot --> ConfigHGATP
The memory management system implements two-stage address translation:
- First Stage: Guest virtual to guest physical using
vsatp - Second Stage: Guest physical to host physical using
hgatp
The hgatp register configuration src/vcpu.rs(L88) uses:
- Mode field (bits 63:60) = 8 (Sv39x4 format)
- Physical page number derived from EPT root address
Sources: src/vcpu.rs(L87 - L89) src/vcpu.rs(L113 - L122)
Error Handling and State Validation
The VCPU implementation includes comprehensive error handling for invalid operations and state inconsistencies.
Register Access Validation
General purpose register access includes bounds checking src/vcpu.rs(L129 - L141) :
flowchart TD SetGPR["set_gpr(index, val)"] CheckIndex["index in range 0..=7?"] MapIndex["Map to GprIndex::A(index+10)"] LogWarning["Log unsupported register warning"] SetRegister["set_gpr_from_gpr_index()"] UpdateState["Update guest_regs.gprs"] NoOperation["No state change"] CheckIndex --> LogWarning CheckIndex --> MapIndex LogWarning --> NoOperation MapIndex --> SetRegister SetGPR --> CheckIndex SetRegister --> UpdateState
Special Register Handling
The zero register (GprIndex::Zero) receives special treatment src/regs.rs(L97 - L99) :
- Write attempts are silently ignored
- Always reads as zero value
- Maintains RISC-V architectural compliance
Sources: src/vcpu.rs(L129 - L141) src/regs.rs(L95 - L102)
SBI Interface and Hypercalls
Relevant source files
This page covers the Supervisor Binary Interface (SBI) implementation and hypercall handling within the RISC-V VCPU system. It details how the hypervisor processes SBI calls from guest virtual machines, including legacy extensions, modern extensions like Hart State Management (HSM), and custom hypercalls. For information about VM exit processing in general, see 2.3. For details about the overall VCPU lifecycle, see 2.1.
SBI Architecture Overview
The SBI interface serves as the critical communication layer between guest operating systems and the hypervisor. The RISC-V VCPU implements a comprehensive SBI handler that supports both legacy and modern SBI extensions while providing a custom hypercall interface.
flowchart TD
subgraph subGraph2["Host/Hypervisor Layer"]
HostSBI["Host SBI Implementation"]
RustSBI["RustSBI Framework"]
HostHardware["Host Hardware"]
end
subgraph subGraph1["RISC-V VCPU SBI Handler"]
VMExit["vmexit_handler"]
SBIRouter["SBI Call Router"]
LegacySBI["Legacy SBI Handler"]
ModernSBI["Modern SBI Handler"]
HypercallHandler["Hypercall Handler"]
ForwardSBI["RISCVVCpuSbi::handle_ecall"]
end
subgraph subGraph0["Guest Virtual Machine"]
GuestOS["Guest OS"]
GuestEcall["ecall instruction"]
GuestRegs["Guest Registersa0-a7"]
end
ForwardSBI --> GuestOS
ForwardSBI --> RustSBI
GuestEcall --> GuestRegs
GuestOS --> GuestEcall
GuestRegs --> VMExit
HostSBI --> HostHardware
HypercallHandler --> GuestOS
LegacySBI --> GuestOS
LegacySBI --> HostSBI
ModernSBI --> GuestOS
RustSBI --> HostSBI
SBIRouter --> ForwardSBI
SBIRouter --> HypercallHandler
SBIRouter --> LegacySBI
SBIRouter --> ModernSBI
VMExit --> SBIRouter
Sources: src/vcpu.rs(L184 - L308)
RISCVVCpuSbi Implementation
The RISCVVCpuSbi struct provides the core SBI handling infrastructure using the RustSBI framework. It implements a forwarding mechanism for standard SBI extensions while allowing custom handling of specific calls.
classDiagram
note for RISCVVCpuSbi "Derived using #[derive(RustSBI)]Automatically implements SBI traits"
note for RISCVVCpuSbi "Forwards calls to host SBIimplementation"
note for Forward "Forwards calls to host SBIimplementation"
class RISCVVCpuSbi {
+Forward forward
+handle_ecall(eid, fid, param) SbiResult
}
class RISCVVCpu {
+VmCpuRegisters regs
+RISCVVCpuSbi sbi
+vmexit_handler() AxVCpuExitReason
}
class Forward {
+console: ConsoleExtension
+pmu: PmuExtension
+fence: FenceExtension
+reset: ResetExtension
+info: InfoExtension
+hsm: HsmExtension
}
RISCVVCpu *-- RISCVVCpuSbi
RISCVVCpu *-- Forward
The struct uses the #[derive(RustSBI)] macro to automatically implement the necessary SBI traits, with the forward field handling console, PMU, fence, reset, info, and HSM extensions through delegation to the host SBI implementation.
Sources: src/vcpu.rs(L29 - L40)
Legacy SBI Extensions
The hypervisor provides backward compatibility by implementing legacy SBI extensions that were defined in earlier RISC-V SBI specifications. These extensions use function-based calling conventions rather than the modern extension-based approach.
| Extension ID | Function | Implementation | Return Behavior |
|---|---|---|---|
| LEGACY_SET_TIMER | Set timer interrupt | Callssbi_rt::set_timer(), clearsvstip | Setsa0to 0 |
| LEGACY_CONSOLE_PUTCHAR | Output character | Forwards to host viasbi_call_legacy_1 | Direct forwarding |
| LEGACY_CONSOLE_GETCHAR | Input character | Forwards to host viasbi_call_legacy_0 | Returns char ina0 |
| LEGACY_SHUTDOWN | System shutdown | Triggers system shutdown | ReturnsSystemDownexit |
sequenceDiagram
participant GuestOS as "Guest OS"
participant RISCVVCpu as "RISCVVCpu"
participant HostSBI as "Host SBI"
GuestOS ->> RISCVVCpu: "ecall with legacy EID"
RISCVVCpu ->> RISCVVCpu: "Extract a0-a7 registers"
alt LEGACY_SET_TIMER
RISCVVCpu ->> HostSBI: "sbi_rt::set_timer(param[0])"
RISCVVCpu ->> RISCVVCpu: "hvip::clear_vstip()"
RISCVVCpu ->> GuestOS: "Set a0=0, advance PC"
else LEGACY_CONSOLE_PUTCHAR
RISCVVCpu ->> HostSBI: "sbi_call_legacy_1(EID, char)"
RISCVVCpu ->> GuestOS: "Advance PC"
else LEGACY_CONSOLE_GETCHAR
RISCVVCpu ->> HostSBI: "sbi_call_legacy_0(EID)"
HostSBI ->> RISCVVCpu: "Return character"
RISCVVCpu ->> GuestOS: "Set a0=char, advance PC"
else LEGACY_SHUTDOWN
RISCVVCpu ->> RISCVVCpu: "Return SystemDown exit"
end
Sources: src/vcpu.rs(L192 - L220) src/vcpu.rs(L312 - L335)
Modern SBI Extensions - HSM
The Hart State Management (HSM) extension provides standardized CPU lifecycle management operations. The hypervisor implements key HSM functions to enable guest operating systems to manage multiple CPU cores.
HSM Function Implementation
flowchart TD
subgraph Parameters["Parameters"]
StartParams["hartid: target CPUstart_addr: entry pointopaque: argument"]
SuspendParams["suspend_type: suspend moderesume_addr: resume pointopaque: context"]
end
subgraph subGraph1["VCPU Exit Reasons"]
CpuUp["AxVCpuExitReason::CpuUp"]
CpuDown["AxVCpuExitReason::CpuDown"]
Halt["AxVCpuExitReason::Halt"]
end
subgraph subGraph0["HSM Functions"]
HartStart["HART_STARTfid=0x0"]
HartStop["HART_STOPfid=0x1"]
HartSuspend["HART_SUSPENDfid=0x3"]
end
HartStart --> CpuUp
HartStart --> StartParams
HartStop --> CpuDown
HartSuspend --> Halt
HartSuspend --> SuspendParams
The HSM implementation extracts parameters from guest registers and translates them into appropriate VCPU exit reasons that the hypervisor can handle at a higher level.
Sources: src/vcpu.rs(L222 - L245)
Hypercall Interface
The hypervisor provides a custom hypercall interface using the EID_HVC extension ID, allowing direct communication between guest software and the hypervisor for performance-critical operations or hypervisor-specific functionality.
Hypercall Processing Flow
flowchart TD
subgraph subGraph2["Exit Reason Structure"]
HypercallExit["AxVCpuExitReason::Hypercall {nr: function_id,args: [param0..param5]}"]
end
subgraph subGraph1["VCPU Processing"]
CheckEID["Check extension_id == EID_HVC"]
ExtractParams["Extract function_id from a6Extract param[0-5] from a0-a5"]
CreateExit["Create AxVCpuExitReason::Hypercall"]
AdvancePC["Advance PC by 4 bytes"]
end
subgraph subGraph0["Guest Hypercall"]
GuestCall["Guest calls ecall"]
SetRegs["Set a7=EID_HVCSet a6=function_idSet a0-a5=parameters"]
end
AdvancePC --> HypercallExit
CheckEID --> ExtractParams
CreateExit --> AdvancePC
ExtractParams --> CreateExit
GuestCall --> SetRegs
SetRegs --> CheckEID
The hypercall mechanism provides a standardized way for guests to request hypervisor services while maintaining the SBI calling convention. The hypervisor returns control to the calling hypervisor framework through the Hypercall exit reason.
Sources: src/vcpu.rs(L246 - L260)
SBI Call Processing Flow
The complete SBI call processing occurs within the vmexit_handler function when a VirtualSupervisorEnvCall exception is detected. The handler examines register contents to determine the appropriate processing path.
flowchart TD VMExit["VM Exit: VirtualSupervisorEnvCall"] ExtractRegs["Extract a0-a7 registersextension_id = a[7]function_id = a[6]param = a[0..5]"] Router["Route by extension_id"] LegacyPath["Legacy SBI Processing"] HSMPath["HSM Extension Processing"] HypercallPath["Hypercall Processing"] ForwardPath["Forward to RustSBI"] SetRegs1["Set return values in a0/a1"] ExitReason1["Return VCPU exit reason"] ExitReason2["Return VCPU exit reason"] SBIHandle["sbi.handle_ecall(eid, fid, param)"] CheckError["ret.is_err()?"] LogWarn["Log warning message"] SetRegs2["Set a0=ret.errorSet a1=ret.value"] AdvancePC1["Advance PC by 4"] AdvancePC2["Advance PC by 4"] ReturnNothing1["Return Nothing"] ReturnNothing2["Return Nothing"] HostHandler["Return to host handler"] GuestResume["Resume guest execution"] AdvancePC1 --> ReturnNothing1 AdvancePC2 --> ReturnNothing2 CheckError --> LogWarn CheckError --> SetRegs2 ExitReason1 --> HostHandler ExitReason2 --> HostHandler ExtractRegs --> Router ForwardPath --> SBIHandle HSMPath --> ExitReason1 HypercallPath --> ExitReason2 LegacyPath --> SetRegs1 LogWarn --> SetRegs2 ReturnNothing1 --> GuestResume ReturnNothing2 --> GuestResume Router --> ForwardPath Router --> HSMPath Router --> HypercallPath Router --> LegacyPath SBIHandle --> CheckError SetRegs1 --> AdvancePC1 SetRegs2 --> AdvancePC2 VMExit --> ExtractRegs
The processing flow ensures that all SBI calls either result in immediate guest resumption (for calls handled within the hypervisor) or appropriate exit reasons for calls requiring host-level processing.
Sources: src/vcpu.rs(L184 - L278)
Register Convention and Error Handling
The SBI implementation follows the standard RISC-V calling convention where a7 contains the extension ID, a6 contains the function ID, and a0-a5 contain parameters. Return values are placed in a0 (error code) and a1 (return value).
For forwarded calls that fail, the system logs detailed error information including the extension ID, function ID, parameters, error code, and return value to aid in debugging SBI-related issues.
Sources: src/vcpu.rs(L264 - L273)
VM Exit Processing
Relevant source files
Purpose and Scope
This document covers the VM exit processing mechanism in the RISC-V VCPU hypervisor system. VM exits occur when guest execution needs to be interrupted and control transferred back to the hypervisor for handling specific events such as privileged instructions, interrupts, or memory access violations.
The VM exit processing is primarily implemented in the vmexit_handler function and encompasses the detection of exit reasons, processing of different exit types, and the coordination of return mechanisms back to guest execution. For information about the overall VCPU lifecycle, see VCPU Lifecycle and Management. For details about SBI interface implementation, see SBI Interface and Hypercalls.
VM Exit Flow Overview
The VM exit processing follows a structured flow from guest execution interruption through exit reason determination to appropriate handling and guest resumption.
High-Level Exit Processing Flow
flowchart TD GuestRun["Guest Execution_run_guest()"] VMExit["VM Exit Occurs"] CSRRead["Read Exit Informationscause, stval, htval, htinst"] ExitDecode["Decode Exit Reasonvmexit_handler()"] SBICall["scause == VirtualSupervisorEnvCall"] TimerInt["scause == SupervisorTimer"] ExtInt["scause == SupervisorExternal"] PageFault["Load/Store GuestPageFault"] UnhandledTrap["Unhandled Trappanic!"] ProcessSBI["Process SBI CallExtension/Function ID"] HandleTimer["Handle Timer InterruptSet VSTIP"] HandleExternal["Handle External Interrupt"] HandlePageFault["Handle Nested Page Fault"] ReturnNothing["Return Nothing"] ReturnCpuUp["Return CpuUp"] ReturnCpuDown["Return CpuDown"] ReturnHalt["Return Halt"] ReturnHypercall["Return Hypercall"] ReturnSystemDown["Return SystemDown"] ReturnExtInt["Return ExternalInterrupt"] ReturnPageFault["Return NestedPageFault"] ResumeGuest["Resume Guest Execution"] HostAction["Host Action Required"] EventualResume["Eventually Resume Guest"] CSRRead --> ExitDecode EventualResume --> GuestRun ExitDecode --> ExtInt ExitDecode --> PageFault ExitDecode --> SBICall ExitDecode --> TimerInt ExitDecode --> UnhandledTrap ExtInt --> HandleExternal GuestRun --> VMExit HandleExternal --> ReturnExtInt HandlePageFault --> ReturnPageFault HandleTimer --> ReturnNothing HostAction --> EventualResume PageFault --> HandlePageFault ProcessSBI --> ReturnCpuDown ProcessSBI --> ReturnCpuUp ProcessSBI --> ReturnHalt ProcessSBI --> ReturnHypercall ProcessSBI --> ReturnNothing ProcessSBI --> ReturnSystemDown ResumeGuest --> GuestRun ReturnCpuDown --> HostAction ReturnCpuUp --> HostAction ReturnExtInt --> HostAction ReturnHalt --> HostAction ReturnHypercall --> HostAction ReturnNothing --> ResumeGuest ReturnPageFault --> HostAction ReturnSystemDown --> HostAction SBICall --> ProcessSBI TimerInt --> HandleTimer VMExit --> CSRRead
Sources: src/vcpu.rs(L92 - L111) src/vcpu.rs(L167 - L308)
Exit Reason Detection and CSR Reading
The VM exit processing begins by capturing the CPU state and trap information from various Control and Status Registers (CSRs) to determine the cause and context of the exit.
CSR State Capture Process
flowchart TD VMExit["VM Exit Event"] ReadCSRs["Read Trap CSRs"] SCAUSE["scauseTrap Cause"] STVAL["stvalTrap Value"] HTVAL["htvalHypervisor Trap Value"] HTINST["htinstHypervisor Trap Instruction"] DecodeType["Decode Trap TypeException vs Interrupt"] FaultAddr["Calculate Fault Address"] GuestPageAddr["Guest Page Information"] InstInfo["Instruction Information"] ProcessExit["Route to Specific Handler"] DecodeType --> ProcessExit FaultAddr --> ProcessExit GuestPageAddr --> ProcessExit HTINST --> InstInfo HTVAL --> GuestPageAddr InstInfo --> ProcessExit ReadCSRs --> HTINST ReadCSRs --> HTVAL ReadCSRs --> SCAUSE ReadCSRs --> STVAL SCAUSE --> DecodeType STVAL --> FaultAddr VMExit --> ReadCSRs
The vmexit_handler function captures the following CSR state immediately upon entry:
| CSR | Purpose | Usage in Exit Processing |
|---|---|---|
| scause | Trap cause identification | Determines if exit was due to exception or interrupt |
| stval | Trap value/address | Contains faulting address for memory exceptions |
| htval | Hypervisor trap value | Guest physical page number for guest page faults |
| htinst | Hypervisor trap instruction | Instruction bits for trapped instruction |
Sources: src/vcpu.rs(L167 - L181)
SBI Call Processing
Virtual Supervisor Environment Calls represent the largest category of VM exits, handling communication between the guest operating system and the hypervisor through the Supervisor Binary Interface.
SBI Call Processing Flow
flowchart TD VSEnvCall["VirtualSupervisorEnvCallException"] ExtractArgs["Extract Argumentsa0-a7 registers"] ExtID["extension_id = a[7]"] FuncID["function_id = a[6]"] Params["param[0-5] = a[0-5]"] CheckLegacy["Legacy SBI?LEGACY_SET_TIMER..LEGACY_SHUTDOWN"] CheckHSM["HSM Extension?EID_HSM"] CheckHVC["Hypercall?EID_HVC"] ForwardSBI["Forward to RustSBI"] ProcessLegacy["Process Legacy SBITimer/Console/Shutdown"] ProcessHSM["Process HSMHart Management"] ProcessHVC["Process Hypercall"] SetTimer["LEGACY_SET_TIMERsbi_rt::set_timer()"] ConsolePut["LEGACY_CONSOLE_PUTCHAR"] ConsoleGet["LEGACY_CONSOLE_GETCHAR"] Shutdown["LEGACY_SHUTDOWNReturn SystemDown"] HartStart["HART_STARTReturn CpuUp"] HartStop["HART_STOPReturn CpuDown"] HartSuspend["HART_SUSPENDReturn Halt"] ReturnHypercall["Return Hypercallwith nr and args"] RustSBIHandle["sbi.handle_ecall()Forward to RustSBI"] AdvancePC["Advance PC by 4"] ReturnResult["Return Appropriate Result"] AdvancePC --> ReturnResult CheckHSM --> ProcessHSM CheckHVC --> ProcessHVC CheckLegacy --> ProcessLegacy ConsoleGet --> AdvancePC ConsolePut --> AdvancePC ExtID --> CheckHSM ExtID --> CheckHVC ExtID --> CheckLegacy ExtID --> ForwardSBI ExtractArgs --> ExtID ExtractArgs --> FuncID ExtractArgs --> Params ForwardSBI --> RustSBIHandle HartStart --> AdvancePC ProcessHSM --> HartStart ProcessHSM --> HartStop ProcessHSM --> HartSuspend ProcessHVC --> AdvancePC ProcessHVC --> ReturnHypercall ProcessLegacy --> ConsoleGet ProcessLegacy --> ConsolePut ProcessLegacy --> SetTimer ProcessLegacy --> Shutdown RustSBIHandle --> AdvancePC SetTimer --> AdvancePC VSEnvCall --> ExtractArgs
SBI Extension Categories
The system processes SBI calls in several categories:
| Extension Type | Extension ID Range | Processing Method | Return Behavior |
|---|---|---|---|
| Legacy SBI | 0x00-0x08 | Direct implementation | Continue guest execution |
| HSM Extension | 0x48534D | Return to host for CPU management | Host handles CPU lifecycle |
| Hypercalls | EID_HVC | Return to host with call details | Host processes hypercall |
| Standard Extensions | Other values | Forward to RustSBI implementation | Continue guest execution |
Sources: src/vcpu.rs(L184 - L278) src/vcpu.rs(L311 - L335)
Interrupt Handling
The hypervisor handles two primary categories of interrupts that cause VM exits: timer interrupts and external interrupts.
Interrupt Processing Mechanisms
flowchart TD IntTrap["Interrupt Trap"] CheckType["Interrupt Type"] TimerInt["SupervisorTimerInterrupt"] ExtInt["SupervisorExternalInterrupt"] EnableVSTIP["Enable Guest Timerhvip::set_vstip()"] EnableSTimer["Enable S-mode Timersie::set_stimer()"] ReturnNothing1["Return NothingResume Guest"] ReturnExternal["Return ExternalInterruptvector: 0"] GuestResume1["Guest Continueswith Timer Available"] HostHandle["Host HandlesExternal Interrupt"] CheckType --> ExtInt CheckType --> TimerInt EnableSTimer --> ReturnNothing1 EnableVSTIP --> ReturnNothing1 ExtInt --> ReturnExternal IntTrap --> CheckType ReturnExternal --> HostHandle ReturnNothing1 --> GuestResume1 TimerInt --> EnableSTimer TimerInt --> EnableVSTIP
Timer Interrupt Processing
Timer interrupts are handled by enabling the virtual supervisor timer interrupt (VSTIP) for the guest and allowing it to continue execution. This delegation allows the guest to handle its own timer events while maintaining hypervisor control.
External Interrupt Processing
External interrupts are returned to the host system for processing, as they typically require hypervisor-level handling and may affect multiple guests or system-wide state.
Sources: src/vcpu.rs(L279 - L290)
Page Fault Processing
Guest page faults represent memory access violations that require hypervisor intervention, typically for memory management, protection, or virtualization purposes.
Guest Page Fault Analysis
flowchart TD PageFaultTrap["Guest Page Fault"] FaultType["Fault Type"] LoadFault["LoadGuestPageFaultException"] StoreFault["StoreGuestPageFaultException"] CalcAddr["Calculate Fault Address"] AddrCalc["fault_addr = htval << 2 | stval & 0x3"] CreateResult["Create NestedPageFault Result"] ReturnFault["Return NestedPageFaultaddr: GuestPhysAddraccess_flags: empty"] HostEPT["Host HandlesExtended Page Tables"] AddrCalc --> CreateResult CalcAddr --> AddrCalc CreateResult --> ReturnFault FaultType --> LoadFault FaultType --> StoreFault LoadFault --> CalcAddr PageFaultTrap --> FaultType ReturnFault --> HostEPT StoreFault --> CalcAddr
Fault Address Calculation
The system calculates the precise fault address by combining information from two CSRs:
htval: Contains the guest physical page number (shifted left by 2)stval: Contains the page offset (lower 2 bits)
The combination htval << 2 | stval & 0x3 provides the complete fault address for the host to resolve through extended page table management.
Sources: src/vcpu.rs(L291 - L298)
Return Mechanisms and Exit Reasons
The VM exit processing concludes by returning specific exit reasons that inform the host system about the required action and whether guest execution should resume immediately or requires host intervention.
Exit Reason Categories
flowchart TD ExitReasons["AxVCpuExitReason"] ImmediateResume["Immediate Resume"] HostAction["Host Action Required"] Nothing["NothingContinue guest execution"] CpuMgmt["CPU Management"] SystemMgmt["System Management"] MemoryMgmt["Memory Management"] CallMgmt["Call Management"] CpuUp["CpuUpStart additional CPU"] CpuDown["CpuDownStop current CPU"] Halt["HaltSuspend CPU"] SystemDown["SystemDownShutdown system"] ExternalInterrupt["ExternalInterruptHandle interrupt"] NestedPageFault["NestedPageFaultResolve memory fault"] Hypercall["HypercallProcess hypercall"] CallMgmt --> Hypercall CpuMgmt --> CpuDown CpuMgmt --> CpuUp CpuMgmt --> Halt ExitReasons --> HostAction ExitReasons --> ImmediateResume HostAction --> CallMgmt HostAction --> CpuMgmt HostAction --> MemoryMgmt HostAction --> SystemMgmt ImmediateResume --> Nothing MemoryMgmt --> NestedPageFault SystemMgmt --> ExternalInterrupt SystemMgmt --> SystemDown
Exit Reason Impact on Execution Flow
| Exit Reason | Host Action | Guest State | Execution Resumption |
|---|---|---|---|
| Nothing | None | PC advanced | Immediate |
| CpuUp | Start target CPU | PC advanced | After CPU start |
| CpuDown | Handle CPU stop | Unchanged | Never (CPU stopped) |
| Halt | Suspend handling | Unchanged | On resume event |
| SystemDown | Shutdown sequence | Unchanged | Never (system down) |
| ExternalInterrupt | Process interrupt | Unchanged | After interrupt handling |
| NestedPageFault | Resolve page fault | Unchanged | After page table update |
| Hypercall | Process hypercall | PC advanced | After hypercall completion |
Sources: src/vcpu.rs(L212) src/vcpu.rs(L228 - L232) src/vcpu.rs(L235) src/vcpu.rs(L242) src/vcpu.rs(L249 - L259) src/vcpu.rs(L277) src/vcpu.rs(L286) src/vcpu.rs(L289) src/vcpu.rs(L294 - L297)
System Architecture
Relevant source files
This document covers the supporting infrastructure and architectural components that enable the RISC-V VCPU system to function. It focuses on the foundational systems including per-CPU management, hardware detection, and the overall system initialization flow. For detailed coverage of the core VCPU implementation and VM execution lifecycle, see Core VCPU Implementation. For low-level assembly implementations and hardware register definitions, see Low-Level Implementation.
System Initialization and Component Integration
The RISC-V VCPU system follows a layered architecture where hardware detection validates platform capabilities before initializing per-CPU state and enabling hypervisor functionality.
System Startup Flow
flowchart TD Start["System Startup"] LibInit["lib.rs Module Loading"] HWDetect["detect_h_extension()"] HWCheck["Hardware Support?"] PerCPUInit["RISCVPerCpu::new()"] Failed["AxError::Unsupported"] CSRSetup["setup_csrs()"] HedelegConfig["hedeleg::Hedeleg::write()"] HidelegConfig["hideleg::Hideleg::write()"] InterruptClear["hvip::clear_*()"] SIEConfig["sie::set_*()"] VCPUReady["VCPU System Ready"] VCPUCreate["RISCVVCpu Creation"] VMExecution["VM Execution Loop"] End["System Unavailable"] CSRSetup --> HedelegConfig CSRSetup --> HidelegConfig CSRSetup --> InterruptClear CSRSetup --> SIEConfig Failed --> End HWCheck --> Failed HWCheck --> PerCPUInit HWDetect --> HWCheck HedelegConfig --> VCPUReady HidelegConfig --> VCPUReady InterruptClear --> VCPUReady LibInit --> HWDetect PerCPUInit --> CSRSetup SIEConfig --> VCPUReady Start --> LibInit VCPUCreate --> VMExecution VCPUReady --> VCPUCreate
Sources: src/lib.rs(L1 - L46) src/percpu.rs(L15 - L41) src/detect.rs(L15 - L25)
Public Interface Architecture
The system exposes its functionality through a clean public API defined in the main library module:
| Component | Type | Purpose |
|---|---|---|
| RISCVPerCpu | Public Struct | Per-CPU state management |
| RISCVVCpu | Public Struct | Virtual CPU implementation |
| has_hardware_support() | Function | Hardware capability detection |
| RISCVVCpuCreateConfig | Configuration | VCPU creation parameters |
| EID_HVC | Constant | Hypercall extension identifier |
The library uses feature gates for no-std operation and RISC-V specific intrinsics, enabling bare-metal hypervisor deployment.
Sources: src/lib.rs(L1 - L46)
Per-CPU State Management Architecture
The RISCVPerCpu<H> component implements the AxArchPerCpu trait to provide hardware-specific per-CPU functionality within the ArceOS framework.
Per-CPU Component Structure
flowchart TD
subgraph subGraph3["Hardware Detection"]
HasHWSupport["has_hardware_support()"]
DetectHExt["detect_h_extension()"]
end
subgraph subGraph2["CSR Management"]
SetupCSRs["setup_csrs()"]
HedelegSetup["hedeleg::Hedeleg"]
HidelegSetup["hideleg::Hideleg"]
HVIPClear["hvip::clear_*()"]
SIESet["sie::set_*()"]
end
subgraph subGraph1["RISCVPerCpu Implementation"]
RISCVPerCpu["RISCVPerCpu"]
NewMethod["new(cpu_id: usize)"]
HardwareEnable["hardware_enable()"]
IsEnabled["is_enabled()"]
HardwareDisable["hardware_disable()"]
end
subgraph subGraph0["ArceOS Framework Interface"]
AxArchPerCpu["AxArchPerCpu Trait"]
AxVCpuHal["AxVCpuHal Trait"]
end
AxArchPerCpu --> RISCVPerCpu
AxVCpuHal --> RISCVPerCpu
HardwareEnable --> HasHWSupport
HasHWSupport --> DetectHExt
NewMethod --> SetupCSRs
SetupCSRs --> HVIPClear
SetupCSRs --> HedelegSetup
SetupCSRs --> HidelegSetup
SetupCSRs --> SIESet
Sources: src/percpu.rs(L1 - L82)
CSR Initialization Process
The setup_csrs() function configures critical Control and Status Registers for hypervisor operation:
Exception Delegation Configuration:
- Delegates specific synchronous exceptions via
hedeleg::Hedeleg::write() - Includes instruction address misalignment, breakpoints, environment calls, and page faults
- Enables guest operating systems to handle these exceptions directly
Interrupt Delegation Configuration:
- Delegates virtual supervisor interrupts through
hideleg::Hideleg::write() - Covers timer, external, and software interrupts for guest VMs
- Allows efficient interrupt handling without hypervisor intervention
Interrupt State Management:
- Clears all pending virtual supervisor interrupts using
hvip::clear_*()functions - Configures supervisor interrupt enable via
sie::set_*()functions - Establishes clean interrupt state for VM execution
Sources: src/percpu.rs(L43 - L81) src/consts.rs(L1 - L50)
Hardware Detection System
The hardware detection subsystem uses a trap-and-emulate approach to safely probe RISC-V H-extension availability without causing system instability.
Detection Mechanism Architecture
flowchart TD
subgraph subGraph3["CSR Access Test"]
CSRRInstr["asm!(csrr {}, 0x680)"]
HGATPRead["hgatp CSR Read"]
end
subgraph subGraph2["Trap Handler Components"]
OnDetectTrap["on_detect_trap()"]
RustDetectTrap["rust_detect_trap()"]
TrapFrame["TrapFrame struct"]
end
subgraph subGraph1["Trap Detection Framework"]
WithDetectTrap["with_detect_trap()"]
InitDetectTrap["init_detect_trap()"]
RestoreDetectTrap["restore_detect_trap()"]
end
subgraph subGraph0["Detection Interface"]
DetectHExt["detect_h_extension()"]
HasHWSupport["has_hardware_support()"]
end
CSRRInstr --> HGATPRead
DetectHExt --> WithDetectTrap
InitDetectTrap --> OnDetectTrap
OnDetectTrap --> RustDetectTrap
RustDetectTrap --> TrapFrame
WithDetectTrap --> CSRRInstr
WithDetectTrap --> InitDetectTrap
WithDetectTrap --> RestoreDetectTrap
Sources: src/detect.rs(L1 - L218)
Detection Process Flow
The hardware detection operates through controlled exception handling:
- Environment Setup:
init_detect_trap()disables interrupts and installs custom trap handler - Probe Execution: Attempts to read the
hgatpCSR (register 0x680) using inline assembly - Exception Analysis:
rust_detect_trap()examinesscauseto determine if illegal instruction occurred - State Restoration:
restore_detect_trap()restores original interrupt and trap configuration
The system returns success (true) if the CSR read completes without illegal instruction exception, indicating H-extension support.
Trap Frame Structure
| Field | Purpose |
|---|---|
| ra,tp,a0-a7,t0-t6 | General-purpose register preservation |
| sstatus | Supervisor status register state |
| sepc | Exception program counter |
| scause | Exception cause identification |
| stval | Exception value (instruction bits for illegal instruction) |
The trap handler uses instruction length analysis via riscv_illegal_insn_bits() to correctly advance sepc past the illegal instruction, ensuring proper execution continuation.
Sources: src/detect.rs(L120 - L144) src/detect.rs(L40 - L77)
Component Integration and Data Flow
The system architecture enables seamless integration between hardware detection, per-CPU management, and the broader ArceOS framework.
Module Dependency Graph
flowchart TD
subgraph subGraph1["Internal Modules"]
LibRS["lib.rs"]
PerCPU["percpu.rs"]
Detect["detect.rs"]
Consts["consts.rs"]
Regs["regs.rs"]
Trap["trap.rs"]
VCPU["vcpu.rs"]
end
subgraph subGraph0["External Dependencies"]
ArceOS["axvcpu::AxArchPerCpu"]
AddrSpace["axaddrspace::GuestPhysAddr"]
RISCVRegs["riscv::register::*"]
Log["log crate"]
end
AddrSpace --> LibRS
ArceOS --> PerCPU
Detect --> Consts
LibRS --> Detect
LibRS --> PerCPU
LibRS --> VCPU
Log --> LibRS
PerCPU --> Consts
PerCPU --> Detect
RISCVRegs --> Detect
RISCVRegs --> PerCPU
VCPU --> Regs
VCPU --> Trap
Sources: src/lib.rs(L7 - L20)
Configuration and Error Handling
The system provides robust configuration through RISCVVCpuCreateConfig:
pub struct RISCVVCpuCreateConfig {
pub hart_id: usize, // CPU core identifier
pub dtb_addr: axaddrspace::GuestPhysAddr, // Device tree blob location
}
Error handling follows ArceOS conventions using AxResult<T> and AxError types. Hardware capability failures return AxError::Unsupported, enabling graceful degradation when H-extension is unavailable.
The hypercall extension identifier EID_HVC (0x485643, "HVC" in ASCII) provides a standardized interface for guest-hypervisor communication outside the standard SBI specification.
Sources: src/lib.rs(L22 - L45)
Per-CPU Management
Relevant source files
Purpose and Scope
This document describes the per-CPU state management system in the RISC-V VCPU hypervisor, focusing on how each CPU core is initialized and configured for virtualization. The per-CPU management system handles Control and Status Register (CSR) initialization, hardware feature enablement, and interrupt/exception delegation setup required for RISC-V hypervisor extensions.
For information about overall VCPU lifecycle management, see VCPU Lifecycle and Management. For details about hardware feature detection, see Hardware Detection. For CSR register definitions and bitfield details, see CSR Definitions and Hardware Registers.
Per-CPU Architecture Overview
The per-CPU management system provides a hardware abstraction layer that initializes each CPU core for hypervisor operation. It bridges the gap between the ArceOS framework's generic per-CPU interface and RISC-V-specific hypervisor initialization requirements.
flowchart TD
subgraph subGraph2["Hardware Layer"]
hedeleg["HEDELEG CSR"]
hideleg["HIDELEG CSR"]
hvip["HVIP CSR"]
hcounteren["HCOUNTEREN CSR"]
sie["SIE CSR"]
end
subgraph subGraph1["RISC-V Per-CPU Implementation"]
RISCVPerCpu["RISCVPerCpu<H>"]
setup_csrs["setup_csrs()"]
hardware_enable["hardware_enable()"]
has_hardware_support["has_hardware_support()"]
end
subgraph subGraph0["ArceOS Framework"]
AxArchPerCpu["AxArchPerCpu Trait"]
AxVCpuHal["AxVCpuHal Interface"]
end
AxArchPerCpu --> RISCVPerCpu
AxVCpuHal --> RISCVPerCpu
RISCVPerCpu --> hardware_enable
RISCVPerCpu --> setup_csrs
hardware_enable --> has_hardware_support
setup_csrs --> hcounteren
setup_csrs --> hedeleg
setup_csrs --> hideleg
setup_csrs --> hvip
setup_csrs --> sie
Sources: src/percpu.rs(L1 - L82)
RISCVPerCpu Structure and Lifecycle
The RISCVPerCpu struct serves as the primary per-CPU state container, implementing the AxArchPerCpu trait to integrate with the ArceOS hypervisor framework.
Structure Definition
The structure uses a PhantomData marker to maintain the generic AxVCpuHal type parameter while storing no actual data, as the per-CPU state is maintained entirely in hardware CSRs.
Sources: src/percpu.rs(L10 - L13) src/percpu.rs(L15 - L41)
Initialization Flow
The per-CPU initialization follows a specific sequence that prepares each CPU core for hypervisor operation:
flowchart TD
subgraph subGraph0["setup_csrs() Details"]
exception_delegate["Configure Exception Delegation (HEDELEG)"]
interrupt_delegate["Configure Interrupt Delegation (HIDELEG)"]
clear_interrupts["Clear Virtual Interrupts (HVIP)"]
inst_misalign["INST_ADDR_MISALIGN"]
breakpoint["BREAKPOINT"]
ecall["ENV_CALL_FROM_U_OR_VU"]
page_faults["Page Fault Exceptions"]
vs_timer["VIRTUAL_SUPERVISOR_TIMER"]
vs_external["VIRTUAL_SUPERVISOR_EXTERNAL"]
vs_soft["VIRTUAL_SUPERVISOR_SOFT"]
clear_vssip["hvip::clear_vssip()"]
clear_vstip["hvip::clear_vstip()"]
clear_vseip["hvip::clear_vseip()"]
end
Start["RISCVPerCpu::new(cpu_id)"]
setup_csrs_call["Call setup_csrs()"]
setup_counters["Setup Counter Access (HCOUNTEREN)"]
enable_interrupts["Enable Supervisor Interrupts (SIE)"]
create_struct["Create RISCVPerCpu Instance"]
End["Return AxResult"]
Start --> setup_csrs_call
clear_interrupts --> clear_vseip
clear_interrupts --> clear_vssip
clear_interrupts --> clear_vstip
clear_interrupts --> setup_counters
create_struct --> End
enable_interrupts --> create_struct
exception_delegate --> breakpoint
exception_delegate --> ecall
exception_delegate --> inst_misalign
exception_delegate --> interrupt_delegate
exception_delegate --> page_faults
interrupt_delegate --> clear_interrupts
interrupt_delegate --> vs_external
interrupt_delegate --> vs_soft
interrupt_delegate --> vs_timer
setup_counters --> enable_interrupts
setup_csrs_call --> exception_delegate
Sources: src/percpu.rs(L16 - L24) src/percpu.rs(L44 - L81)
CSR Initialization Process
The setup_csrs() function performs critical hypervisor-level Control and Status Register initialization. This unsafe function configures the hardware delegation and interrupt management necessary for virtualization.
Exception Delegation Configuration
The hypervisor exception delegation register (HEDELEG) is configured to automatically forward specific exceptions from guest execution to the guest's supervisor mode rather than trapping to the hypervisor:
| Exception Type | Constant | Purpose |
|---|---|---|
| Instruction Address Misaligned | INST_ADDR_MISALIGN | Memory alignment errors |
| Breakpoint | BREAKPOINT | Debug breakpoint traps |
| Environment Call | ENV_CALL_FROM_U_OR_VU | System calls from user/virtual-user mode |
| Instruction Page Fault | INST_PAGE_FAULT | Instruction fetch page faults |
| Load Page Fault | LOAD_PAGE_FAULT | Load operation page faults |
| Store Page Fault | STORE_PAGE_FAULT | Store operation page faults |
| Illegal Instruction | ILLEGAL_INST | Invalid instruction execution |
Sources: src/percpu.rs(L47 - L56) src/consts.rs
Interrupt Delegation Setup
The hypervisor interrupt delegation register (HIDELEG) forwards virtual supervisor-level interrupts directly to the guest:
flowchart TD
subgraph subGraph2["Guest Handling"]
GuestISR["Guest Interrupt Handlers"]
end
subgraph subGraph1["HIDELEG Configuration"]
VSTIMER_BIT["VIRTUAL_SUPERVISOR_TIMER"]
VSEXT_BIT["VIRTUAL_SUPERVISOR_EXTERNAL"]
VSSOFT_BIT["VIRTUAL_SUPERVISOR_SOFT"]
end
subgraph subGraph0["Interrupt Sources"]
VSTimer["Virtual Supervisor Timer"]
VSExternal["Virtual Supervisor External"]
VSSoft["Virtual Supervisor Software"]
end
VSEXT_BIT --> GuestISR
VSExternal --> VSEXT_BIT
VSSOFT_BIT --> GuestISR
VSSoft --> VSSOFT_BIT
VSTIMER_BIT --> GuestISR
VSTimer --> VSTIMER_BIT
Sources: src/percpu.rs(L59 - L64) src/consts.rs
Counter and Interrupt Enablement
The initialization process also configures:
- Counter Access (
HCOUNTEREN): Enables guest access to performance counters by setting all bits to 1 - Virtual Interrupt Clearing (
HVIP): Clears all pending virtual supervisor interrupts - Supervisor Interrupt Enable (
SIE): Enables external, software, and timer interrupts at supervisor level
Sources: src/percpu.rs(L67 - L79)
Hardware Enablement Interface
The per-CPU system provides hardware enablement control through the AxArchPerCpu trait implementation:
stateDiagram-v2 [*] --> Uninitialized Uninitialized --> Initialized : new(cpu_id) Initialized --> HardwareEnabled : hardware_enable() HardwareEnabled --> HardwareDisabled : hardware_disable() HardwareDisabled --> HardwareEnabled : hardware_enable() note left of HardwareEnabled : ['has_hardware_support() == true'] note left of HardwareDisabled : ['Hardware virtualization disabled'] note left of Uninitialized : ['Per-CPU state not created']
Hardware Support Validation
The hardware_enable() method validates that the underlying hardware supports RISC-V hypervisor extensions before enabling virtualization features:
- Success Path: Returns
Ok(())whenhas_hardware_support()returnstrue - Failure Path: Returns
AxError::Unsupportedwhen hardware lacks H-extension support
Sources: src/percpu.rs(L30 - L36)
Unimplemented Features
The current implementation includes placeholder methods for future functionality:
is_enabled(): Returns whether hardware virtualization is currently active (unimplemented)hardware_disable(): Disables hardware virtualization features (unimplemented)
Sources: src/percpu.rs(L26 - L28) src/percpu.rs(L38 - L40)
Integration with System Architecture
The per-CPU management system integrates with the broader RISC-V hypervisor architecture through several key interfaces:
flowchart TD
subgraph subGraph3["VCPU Management"]
VCPUCreation["VCPU Creation"]
VCPUExecution["VCPU Execution"]
end
subgraph subGraph2["Hardware Detection"]
has_hardware_support_func["has_hardware_support()"]
detect_h_extension["detect_h_extension()"]
end
subgraph subGraph1["Per-CPU Layer"]
RISCVPerCpu_new["RISCVPerCpu::new()"]
setup_csrs_func["setup_csrs()"]
hardware_enable_func["hardware_enable()"]
end
subgraph subGraph0["System Initialization"]
SystemBoot["System Boot"]
CPUEnumeration["CPU Enumeration"]
PerCPUCreation["Per-CPU Creation"]
end
CPUEnumeration --> PerCPUCreation
PerCPUCreation --> RISCVPerCpu_new
RISCVPerCpu_new --> hardware_enable_func
RISCVPerCpu_new --> setup_csrs_func
SystemBoot --> CPUEnumeration
VCPUCreation --> VCPUExecution
hardware_enable_func --> has_hardware_support_func
has_hardware_support_func --> detect_h_extension
setup_csrs_func --> VCPUCreation
This per-CPU initialization must complete successfully before any VCPU instances can be created or executed on the respective CPU core, ensuring that the hardware virtualization environment is properly configured.
Sources: src/percpu.rs(L1 - L82) src/detect.rs
Hardware Detection
Relevant source files
This page documents the trap-based hardware feature detection system used to identify RISC-V H-extension support at runtime. The detection mechanism safely probes for hypervisor extension availability by attempting to access hypervisor-specific Control Status Registers (CSRs) and catching any resulting illegal instruction exceptions.
For information about per-CPU management and CSR initialization, see Per-CPU Management. For details about trap handling in the main VCPU system, see VM Exit Processing.
Purpose and Scope
The hardware detection system provides runtime identification of RISC-V hypervisor extension support through a controlled trap-and-return procedure. The system disables interrupts, attempts to access hypervisor CSRs, and uses exception handling to determine whether the hardware supports the required extensions.
Sources: src/detect.rs(L1 - L6)
Detection Process Overview
The hardware detection system uses a trap-based approach to safely probe for hardware features. The process attempts to read the hgatp (Hypervisor Guest Address Translation and Protection) register, which is only available when the H-extension is present.
Detection Flow Diagram
flowchart TD Start["detect_h_extension()"] Setup["with_detect_trap()"] DisableInt["Disable S-level interrupts"] SetTrap["Set custom trap handler"] AttemptCSR["Attempt to read hgatp CSR (0x680)"] Success["CSR read successful"] IllegalInst["Illegal instruction exception"] RestoreOrig["Restore original state"] TrapHandler["on_detect_trap assembly handler"] RustHandler["rust_detect_trap()"] SkipInst["Skip illegal instruction"] RestoreAfterTrap["Restore original state"] ReturnTrue["Return true (H-extension present)"] ReturnFalse["Return false (H-extension absent)"] End["Hardware support confirmed"] AttemptCSR --> IllegalInst AttemptCSR --> Success DisableInt --> SetTrap IllegalInst --> TrapHandler RestoreAfterTrap --> ReturnFalse RestoreOrig --> ReturnTrue ReturnFalse --> End ReturnTrue --> End RustHandler --> SkipInst SetTrap --> AttemptCSR Setup --> DisableInt SkipInst --> RestoreAfterTrap Start --> Setup Success --> RestoreOrig TrapHandler --> RustHandler
Sources: src/detect.rs(L18 - L25) src/detect.rs(L32 - L39)
Implementation Architecture
The detection system consists of several interconnected components that work together to safely probe hardware features and handle any resulting exceptions.
Component Relationships
flowchart TD
subgraph subGraph3["Hardware Interface"]
CSRAccess["CSR Access (hgatp)"]
InsnDecoding["riscv_illegal_insn_bits()"]
end
subgraph subGraph2["Trap Handling System"]
AsmHandler["on_detect_trap() (naked asm)"]
RustHandler["rust_detect_trap()"]
TrapFrameStruct["TrapFrame struct"]
end
subgraph subGraph1["Core Detection Logic"]
WithTrap["with_detect_trap()"]
InitTrap["init_detect_trap()"]
RestoreTrap["restore_detect_trap()"]
end
subgraph subGraph0["Public Interface"]
DetectFunc["detect_h_extension()"]
PublicAPI["has_hardware_support (lib.rs)"]
end
AsmHandler --> RustHandler
CSRAccess --> AsmHandler
DetectFunc --> WithTrap
InitTrap --> AsmHandler
PublicAPI --> DetectFunc
RestoreTrap --> WithTrap
RustHandler --> InsnDecoding
RustHandler --> TrapFrameStruct
WithTrap --> CSRAccess
WithTrap --> InitTrap
WithTrap --> RestoreTrap
Sources: src/lib.rs(L20) src/detect.rs(L18 - L25) src/detect.rs(L32 - L39) src/detect.rs(L155 - L217)
Detection Process Implementation
The detect_h_extension() function serves as the primary entry point for hardware detection. It uses a controlled environment to attempt accessing hypervisor-specific CSRs.
Core Detection Function
The detection process attempts to read the hgatp CSR (register 0x680) within a controlled trap environment:
// From detect.rs lines 18-25
pub fn detect_h_extension() -> bool {
let ans = with_detect_trap(0, || unsafe {
asm!("csrr {}, 0x680", out(reg) _, options(nomem, nostack)); // 0x680 => hgatp
});
ans != 2 // 0 => success, 2 => failed (illegal instruction)
}
The function returns true if the CSR access succeeds (indicating H-extension presence) or false if an illegal instruction exception occurs (indicating absence).
Sources: src/detect.rs(L18 - L25)
Trap Environment Setup
The with_detect_trap() function creates a controlled environment for safe hardware probing:
| Phase | Function | Purpose |
|---|---|---|
| Setup | init_detect_trap() | Disable interrupts, set custom trap handler |
| Execution | Closure parameter | Execute the probing instruction |
| Cleanup | restore_detect_trap() | Restore original state, return result |
The setup process preserves the original system state while creating a minimal trap handling environment.
Sources: src/detect.rs(L32 - L39) src/detect.rs(L81 - L101) src/detect.rs(L105 - L118)
Trap Handling Mechanism
When an illegal instruction exception occurs during hardware probing, the system uses a specialized trap handling mechanism to safely recover and continue execution.
Trap Handler Flow
sequenceDiagram
participant CPUCore as "CPU Core"
participant on_detect_trapasm as "on_detect_trap() (asm)"
participant rust_detect_trap as "rust_detect_trap()"
participant TrapFrame as "TrapFrame"
CPUCore ->> on_detect_trapasm: "Illegal instruction exception"
on_detect_trapasm ->> TrapFrame: "Save all registers to TrapFrame"
on_detect_trapasm ->> rust_detect_trap: "Call with TrapFrame pointer"
rust_detect_trap ->> TrapFrame: "Read scause, stval, sepc"
rust_detect_trap ->> rust_detect_trap: "Decode instruction length"
rust_detect_trap ->> TrapFrame: "Update sepc to skip instruction"
rust_detect_trap ->> TrapFrame: "Set tp register with exception code"
rust_detect_trap ->> on_detect_trapasm: "Return to assembly handler"
on_detect_trapasm ->> TrapFrame: "Restore all registers"
on_detect_trapasm ->> CPUCore: "sret (return from trap)"
Sources: src/detect.rs(L155 - L217) src/detect.rs(L42 - L60)
TrapFrame Structure
The trap handler uses a specialized TrapFrame structure to preserve processor state during exception handling:
// Key fields from TrapFrame (lines 122-144)
struct TrapFrame {
ra: usize, // Return address
tp: usize, // Thread pointer (used for return value)
a0-a7: usize, // Argument registers
t0-t6: usize, // Temporary registers
sstatus: usize, // Supervisor status
sepc: usize, // Exception program counter
scause: Scause,// Exception cause
stval: usize, // Trap value
}
The tp register serves dual purposes: passing parameters into the detection routine and returning the exception code.
Sources: src/detect.rs(L121 - L144)
Exception Processing and Recovery
The trap handler analyzes exceptions and implements appropriate recovery mechanisms based on the exception type.
Exception Analysis Process
flowchart TD TrapEntry["rust_detect_trap() entry"] ReadCause["Read scause register"] CheckType["Exception type?"] ProcessIllegal["Process illegal instruction"] Unreachable1["unreachable!()"] Unreachable2["unreachable!() (filtered)"] ReadStval["Read stval for instruction bits"] CheckStval["stval[0..16] == 0?"] ReadMemory["Read instruction from sepc"] DecodeStval["Decode from stval"] DecodeLength["riscv_illegal_insn_bits()"] UpdateSepc["sepc += instruction_length"] SetReturnCode["tp = scause.bits()"] Return["Return to assembly handler"] CheckStval --> DecodeStval CheckStval --> ReadMemory CheckType --> ProcessIllegal CheckType --> Unreachable1 CheckType --> Unreachable2 DecodeLength --> UpdateSepc DecodeStval --> DecodeLength ProcessIllegal --> ReadStval ReadCause --> CheckType ReadMemory --> DecodeLength ReadStval --> CheckStval SetReturnCode --> Return TrapEntry --> ReadCause UpdateSepc --> SetReturnCode
Sources: src/detect.rs(L42 - L60) src/detect.rs(L64 - L77)
Instruction Length Decoding
The system includes logic to determine RISC-V instruction lengths for proper exception recovery:
| Instruction Pattern | Length | Encoding |
|---|---|---|
| xxxxxxxxxxxxxxxx11(bits [1:0] != 11) | 16-bit | Compressed |
| xxxxxxxxxxxxxxxnnn11(bits [4:2] != 111) | 32-bit | Standard |
| xxxxxxxxxxxxxxx11111(bits [4:2] == 111) | ≥48-bit | Extended |
The riscv_illegal_insn_bits() function implements this decoding logic to ensure the exception handler skips the correct number of bytes.
Sources: src/detect.rs(L64 - L77)
Integration with System Architecture
The hardware detection system integrates with the broader RISC-V VCPU architecture through the public API and initialization processes.
Public Interface Integration
The detection functionality is exposed through the main library interface:
// From lib.rs line 20
pub use detect::detect_h_extension as has_hardware_support;
This allows other system components to query hardware capabilities before attempting to use hypervisor features.
Sources: src/lib.rs(L20)
Error Handling Strategy
The detection system uses a fail-safe approach where:
- Successful CSR access indicates H-extension support
- Any exception (particularly illegal instruction) indicates lack of support
- The system gracefully recovers from all exception scenarios
- No system state is permanently modified during detection
This approach ensures that hardware detection can safely run on any RISC-V system, regardless of hypervisor extension availability.
Sources: src/detect.rs(L18 - L25) src/detect.rs(L42 - L60)
Register Management
Relevant source files
This document covers the register data structures and management systems that maintain CPU state for virtual machines in the RISC-V hypervisor. This includes general-purpose registers, control and status registers (CSRs), and the mechanisms for saving, restoring, and accessing register state during VM execution and context switches.
For information about trap handling that uses these registers, see Low-Level Implementation. For details about the VCPU implementation that manages these registers, see Core VCPU Implementation.
Register Data Structures
The register management system is built around a hierarchy of data structures that organize different categories of CPU state. The main container is VmCpuRegisters, which holds all register state needed for virtualization.
Core Register Structure Hierarchy
flowchart TD VmCpuRegisters["VmCpuRegistersMain register container"] HypervisorCpuState["HypervisorCpuStatehyp_regs"] GuestCpuState["GuestCpuStateguest_regs"] GuestVsCsrs["GuestVsCsrsvs_csrs"] GuestVirtualHsCsrs["GuestVirtualHsCsrsvirtual_hs_csrs"] VmCpuTrapState["VmCpuTrapStatetrap_csrs"] HypGprs["GeneralPurposeRegistersgprs"] HypCsrs["CSRs:sstatus, hstatusscounteren, stvec, sscratch"] GuestGprs["GeneralPurposeRegistersgprs"] GuestCsrs["CSRs:sstatus, hstatusscounteren, sepc"] VsCsrList["VS-level CSRs:htimedelta, vsstatus, vsievstvec, vsscratch, vsepcvscause, vstval, vsatp, vstimecmp"] VirtHsCsrList["Virtual HS CSRs:hie, hgeie, hgatp"] TrapCsrList["Trap CSRs:scause, stvalhtval, htinst"] GprIndex["GprIndex enumZero, RA, SP, GP, TPT0-T6, S0-S11, A0-A7"] GuestCpuState --> GuestCsrs GuestCpuState --> GuestGprs GuestGprs --> GprIndex GuestVirtualHsCsrs --> VirtHsCsrList GuestVsCsrs --> VsCsrList HypGprs --> GprIndex HypervisorCpuState --> HypCsrs HypervisorCpuState --> HypGprs VmCpuRegisters --> GuestCpuState VmCpuRegisters --> GuestVirtualHsCsrs VmCpuRegisters --> GuestVsCsrs VmCpuRegisters --> HypervisorCpuState VmCpuRegisters --> VmCpuTrapState VmCpuTrapState --> TrapCsrList
Sources: src/regs.rs(L1 - L197)
General Purpose Register Management
The GeneralPurposeRegisters structure provides a type-safe wrapper around the 32 RISC-V general-purpose registers, using the GprIndex enum for meaningful register names.
| Register Category | Registers | Purpose |
|---|---|---|
| Argument registers | A0-A7 | Function arguments and return values |
| Saved registers | S0-S11 | Callee-saved registers |
| Temporary registers | T0-T6 | Caller-saved temporary values |
| Special registers | Zero, RA, SP, GP, TP | Zero, return address, stack pointer, global pointer, thread pointer |
The GeneralPurposeRegisters implementation enforces RISC-V semantics, particularly that writes to the zero register (GprIndex::Zero) are ignored.
flowchart TD GprAccess["Register Access Methods"] reg["reg(GprIndex) -> usizeRead register value"] set_reg["set_reg(GprIndex, usize)Write register value(ignores Zero writes)"] a_regs["a_regs() -> &[usize]Get A0-A7 slice"] a_regs_mut["a_regs_mut() -> &mut [usize]Get mutable A0-A7 slice"] GprIndex["GprIndex"] Zero["Zero (x0)"] RA["RA (x1)"] SP["SP (x2)"] A0A7["A0-A7 (x10-x17)"] T0T6["T0-T6 (x5-x7, x28-x31)"] S0S11["S0-S11 (x8-x9, x18-x27)"] GprAccess --> a_regs GprAccess --> a_regs_mut GprAccess --> reg GprAccess --> set_reg GprIndex --> A0A7 GprIndex --> RA GprIndex --> S0S11 GprIndex --> SP GprIndex --> T0T6 GprIndex --> Zero
Sources: src/regs.rs(L1 - L114)
Register Categories and Organization
The register management system organizes CPU state into five distinct categories, each serving different purposes in the virtualization lifecycle.
Hypervisor vs Guest Register State
flowchart TD
subgraph subGraph2["Virtualization-Specific State"]
VsCsrs["GuestVsCsrsActive when V=1"]
VirtHsCsrs["GuestVirtualHsCsrsEmulated hypervisor state"]
TrapCsrs["VmCpuTrapStateSet on VM exit"]
end
subgraph subGraph1["Guest State"]
GuestRegs["GuestCpuStateRestored when entering VM"]
GuestGprs["GPRs: Guest register values"]
GuestSstatus["sstatus: Guest supervisor status"]
GuestHstatus["hstatus: Guest hypervisor status"]
GuestSepc["sepc: Guest program counter"]
end
subgraph subGraph0["Hypervisor State"]
HypRegs["HypervisorCpuStateSaved when entering VM"]
HypGprs["GPRs: Host register values"]
HypSstatus["sstatus: Host supervisor status"]
HypHstatus["hstatus: Host hypervisor status"]
HypOther["scounteren, stvec, sscratch"]
end
VMEntry["VM Entry"]
VMExit["VM Exit"]
GuestRegs --> GuestGprs
GuestRegs --> GuestHstatus
GuestRegs --> GuestSepc
GuestRegs --> GuestSstatus
HypRegs --> HypGprs
HypRegs --> HypHstatus
HypRegs --> HypOther
HypRegs --> HypSstatus
VMEntry --> GuestRegs
VMEntry --> HypRegs
VMEntry --> VsCsrs
VMExit --> HypRegs
VMExit --> TrapCsrs
Sources: src/regs.rs(L116 - L196)
Privilege Level and Usage Context
| Structure | Usage Context | Save/Restore Timing |
|---|---|---|
| HypervisorCpuState | Host execution state | Saved on VM entry, restored on VM exit |
| GuestCpuState | Guest execution state | Restored on VM entry, saved on VM exit |
| GuestVsCsrs | VS-mode virtualization | Active when V=1, context switched between VMs |
| GuestVirtualHsCsrs | Emulated hypervisor | Maintained per-VM for nested virtualization |
| VmCpuTrapState | Trap information | Written by hardware on VM exit |
Register Access Methods
The VCPU provides several interfaces for accessing and manipulating register state, with different methods for different use cases.
VCPU Register Access Interface
flowchart TD
subgraph subGraph1["Internal Register State"]
VmCpuRegs["VmCpuRegistersself.regs"]
GuestGprs["guest_regs.gprs"]
GuestSepc["guest_regs.sepc"]
end
subgraph subGraph0["Public VCPU Methods"]
GetGpr["get_gpr(GprIndex) -> usizeRead guest GPR"]
SetGpr["set_gpr_from_gpr_index(GprIndex, usize)Write guest GPR"]
SetGprIndex["set_gpr(index: usize, val: usize)Write GPR by numeric index"]
AdvancePc["advance_pc(instr_len: usize)Increment guest PC"]
RegsAccess["regs() -> &mut VmCpuRegistersDirect register access"]
end
AdvancePc --> GuestSepc
GetGpr --> GuestGprs
RegsAccess --> VmCpuRegs
SetGpr --> GuestGprs
SetGprIndex --> GuestGprs
VmCpuRegs --> GuestGprs
VmCpuRegs --> GuestSepc
Sources: src/vcpu.rs(L129 - L163)
Register Initialization During VCPU Creation
The VCPU constructor initializes guest registers with boot parameters, setting up the initial execution environment for the guest VM.
flowchart TD
subgraph subGraph0["Boot Parameters"]
HartId["A0 = config.hart_idHardware thread ID"]
DtbAddr["A1 = config.dtb_addrDevice tree blob address"]
end
VCpuNew["RISCVVCpu::new(config)"]
CreateRegs["VmCpuRegisters::default()"]
SetA0["regs.guest_regs.gprs.set_reg(A0, hart_id)"]
SetA1["regs.guest_regs.gprs.set_reg(A1, dtb_addr)"]
VCpuReady["VCPU ready with initialized registers"]
CreateRegs --> SetA0
SetA0 --> HartId
SetA0 --> SetA1
SetA1 --> DtbAddr
SetA1 --> VCpuReady
VCpuNew --> CreateRegs
Sources: src/vcpu.rs(L47 - L62)
Register Lifecycle and Context Switching
Register state undergoes several transitions during VM execution, with different register categories being saved and restored at different points in the virtualization lifecycle.
VM Entry and Exit Register Flow
sequenceDiagram
participant HostExecution as "Host Execution"
participant RISCVVCpu as "RISCVVCpu"
participant _run_guest as "_run_guest"
participant GuestExecution as "Guest Execution"
HostExecution ->> RISCVVCpu: run()
RISCVVCpu ->> RISCVVCpu: Setup interrupt state (sie, sstatus)
RISCVVCpu ->> _run_guest: _run_guest(&mut self.regs)
Note over _run_guest: Save hypervisor state to hyp_regs<br>Restore guest state from guest_regs<br>Load VS CSRs, virtual HS CSRs
_run_guest ->> GuestExecution: Enter guest mode
GuestExecution ->> GuestExecution: Execute guest code
GuestExecution ->> _run_guest: VM Exit (trap, interrupt, ecall)
Note over _run_guest: Save guest state to guest_regs<br>Restore hypervisor state from hyp_regs<br>Update trap_csrs with exit info
_run_guest ->> RISCVVCpu: Return to hypervisor
RISCVVCpu ->> RISCVVCpu: vmexit_handler()
RISCVVCpu ->> RISCVVCpu: Read trap CSRs (scause, stval, htval, htinst)
RISCVVCpu ->> HostExecution: Return AxVCpuExitReason
Sources: src/vcpu.rs(L92 - L111) src/vcpu.rs(L167 - L181)
Register State During SBI Call Processing
When the guest makes an SBI call, the VCPU accesses argument registers and modifies return value registers according to the SBI specification.
flowchart TD SbiCall["Guest SBI Call (ecall)"] ReadArgs["Read arguments from A0-A7a_regs() -> [a0..a7]"] ExtractIds["Extract extension_id (A7)Extract function_id (A6)"] ProcessCall["Process SBI call"] LegacyTimer["Legacy Timerset_timer(a0)"] LegacyConsole["Legacy Consoleputchar/getchar"] HsmCalls["HSM Extensionhart_start/stop/suspend"] Hypercall["HypercallCustom extension"] ForwardSbi["Forward to RustSBI"] SetA0Zero["set_gpr_from_gpr_index(A0, 0)"] SetA0Result["set_gpr_from_gpr_index(A0, result)"] SetRetVals["set_gpr_from_gpr_index(A0, error)set_gpr_from_gpr_index(A1, value)"] AdvancePC["advance_pc(4)"] ContinueGuest["Continue guest execution"] AdvancePC --> ContinueGuest ExtractIds --> ProcessCall ForwardSbi --> SetRetVals LegacyConsole --> SetA0Result LegacyTimer --> SetA0Zero ProcessCall --> ForwardSbi ProcessCall --> HsmCalls ProcessCall --> Hypercall ProcessCall --> LegacyConsole ProcessCall --> LegacyTimer ReadArgs --> ExtractIds SbiCall --> ReadArgs SetA0Result --> AdvancePC SetA0Zero --> AdvancePC SetRetVals --> AdvancePC
Sources: src/vcpu.rs(L184 - L277)
The register management system provides the foundation for all VCPU operations, maintaining the complete CPU state needed for virtualizing RISC-V guests. The structured approach to organizing different categories of register state enables efficient context switching and proper isolation between hypervisor and guest execution environments.
Low-Level Implementation
Relevant source files
This page covers the assembly-level implementations, hardware register definitions, and low-level system constants that form the foundation of the RISC-V VCPU system. This includes the critical assembly trap handlers, Control and Status Register (CSR) bitfield definitions, and system-wide constants for interrupts and exceptions.
For higher-level VCPU management and lifecycle operations, see Core VCPU Implementation. For register state management data structures, see Register Management.
Assembly Trap Handler Implementation
The core of the hypervisor's guest-host transition mechanism is implemented in assembly language through the trap handler system. This provides the critical low-level routines for entering and exiting guest virtual machines.
Guest Entry and Exit Assembly Functions
The system implements two primary assembly functions that handle the critical guest-hypervisor transitions:
Guest Entry Flow (_run_guest)
flowchart TD start["_run_guest"] save_hyp["Save Hypervisor GPRs"] swap_csr["Swap in Guest CSRs"] set_stvec["Set stvec to _guest_exit"] save_sscratch["Save sscratch, set to GuestInfo"] restore_guest["Restore Guest GPRs"] sret["sret - Enter Guest"] guest_exec["Guest Execution"] trap["VM Exit/Trap"] guest_exit["_guest_exit"] guest_exec --> trap restore_guest --> sret save_hyp --> swap_csr save_sscratch --> restore_guest set_stvec --> save_sscratch sret --> guest_exec start --> save_hyp swap_csr --> set_stvec trap --> guest_exit
Guest Exit Flow (_guest_exit)
flowchart TD guest_exit["_guest_exit"] swap_a0["Swap GuestInfo from sscratch"] save_guest["Save Guest GPRs"] save_guest_a0["Save Guest a0 from sscratch"] restore_csr["Swap in Hypervisor CSRs"] save_sepc["Save Guest EPC"] restore_hyp["Restore Hypervisor GPRs"] ret_hyp["ret - Return to Hypervisor"] guest_exit --> swap_a0 restore_csr --> save_sepc restore_hyp --> ret_hyp save_guest --> save_guest_a0 save_guest_a0 --> restore_csr save_sepc --> restore_hyp swap_a0 --> save_guest
The assembly implementation uses precise register offset calculations to access the VmCpuRegisters structure fields. The offsets are computed using compile-time constants generated by the hyp_gpr_offset and guest_gpr_offset functions in src/trap.rs(L8 - L19)
Sources: src/trap.S(L1 - L183) src/trap.rs(L1 - L103)
Register Context Switching Mechanism
The trap handler performs comprehensive context switching between hypervisor and guest register states:
flowchart TD
subgraph subGraph1["Memory Layout"]
VmCpuRegs["VmCpuRegisters"]
HypState["HypervisorCpuStatehyp_regs"]
GuestState["GuestCpuStateguest_regs"]
end
subgraph subGraph0["Register Categories"]
GPRs["General Purpose Registersra, gp, tp, s0-s11, a1-a7, sp"]
CSRs["Control Status Registerssstatus, hstatus, scounterenstvec, sscratch, sepc"]
Special["Special Handlinga0 (via sscratch)t0-t6 (guest only)"]
end
CSRs --> GuestState
CSRs --> HypState
GPRs --> GuestState
GPRs --> HypState
VmCpuRegs --> GuestState
VmCpuRegs --> HypState
The offset calculation macros hyp_csr_offset! and guest_csr_offset! at src/trap.rs(L22 - L33) enable the assembly code to access specific CSR fields within the register structure using compile-time computed offsets.
Sources: src/trap.rs(L35 - L102) src/trap.S(L32 - L156)
CSR Register Definitions and Hardware Interfaces
The system provides comprehensive definitions for RISC-V Control and Status Registers using the tock_registers framework, enabling type-safe bitfield manipulation of hardware registers.
Hypervisor Extension CSR Definitions
Key hypervisor-specific CSRs are defined with detailed bitfield structures:
| CSR Name | Address | Purpose |
|---|---|---|
| hstatus | 0x600 | Hypervisor status register |
| hedeleg | 0x602 | Hypervisor exception delegation |
| hideleg | 0x603 | Hypervisor interrupt delegation |
| hie | 0x604 | Hypervisor interrupt enable |
| hcounteren | 0x606 | Hypervisor counter enable |
| hvip | 0x645 | Hypervisor virtual interrupt pending |
HSTATUS Register Bitfield Layout
The hstatus register contains critical hypervisor state control bits:
flowchart TD
subgraph subGraph0["hstatus Register Fields"]
VSBE["vsbe[6]Virtual SupervisorBig Endian"]
GVA["gva[6]Guest VirtualAddress"]
SPV["spv[7]Supervisor PreviousVirtualization Mode"]
SPVP["spvp[8]Supervisor PreviousVirtual Privilege"]
HU["hu[9]Hypervisor inUser Mode"]
VGEIN["vgein[17:12]Virtual GuestExternal Interrupt"]
VTVM["vtvm[20]Virtual TLBManagement"]
VTW["vtw[21]Virtual TimeoutWait"]
VTSR["vtsr[22]Virtual TimeoutState Reporting"]
VSXL["vsxl[33:32]Virtual SupervisorXLEN"]
end
The SPV field uses enumerated values for privilege modes:
User = 0- User mode virtualizationSupervisor = 1- Supervisor mode virtualization
Sources: def.rs(L37 - L1284)
Exception and Interrupt Delegation Registers
The hedeleg register controls which exceptions are delegated to VS-mode:
flowchart TD
subgraph subGraph0["Exception Types (hedeleg)"]
MISALIGN["instr_misaligned[0]Instruction Address Misaligned"]
IFAULT["instr_fault[1]Instruction Access Fault"]
ILLEGAL["illegal_instr[2]Illegal Instruction"]
BREAK["breakpoint[3]Breakpoint"]
LMISALIGN["load_misaligned[4]Load Address Misaligned"]
LFAULT["load_fault[5]Load Access Fault"]
SMISALIGN["store_misaligned[6]Store Address Misaligned"]
SFAULT["store_fault[7]Store Access Fault"]
UECALL["u_ecall[8]User Environment Call"]
IPFAULT["instr_page_fault[12]Instruction Page Fault"]
LPFAULT["load_page_fault[13]Load Page Fault"]
SPFAULT["store_page_fault[15]Store Page Fault"]
end
The hideleg register controls interrupt delegation with fields for:
vssoft[2]- VS-mode software interruptvstimer[6]- VS-mode timer interruptvsext[10]- VS-mode external interrupt
Sources: def.rs(L55 - L1420)
System Constants and Trap Definitions
The system defines comprehensive constants for interrupt handling, exception types, and trap processing organized into logical modules.
Interrupt Type Constants
The interrupt constants are organized by interrupt source and privilege level:
flowchart TD
subgraph subGraph0["Interrupt Constants (consts::traps::interrupt)"]
USER_SOFT["USER_SOFT = 1<<0"]
SUPERVISOR_SOFT["SUPERVISOR_SOFT = 1<<1"]
VIRTUAL_SUPERVISOR_SOFT["VIRTUAL_SUPERVISOR_SOFT = 1<<2"]
MACHINE_SOFT["MACHINE_SOFT = 1<<3"]
USER_TIMER["USER_TIMER = 1<<4"]
SUPERVISOR_TIMER["SUPERVISOR_TIMER = 1<<5"]
VIRTUAL_SUPERVISOR_TIMER["VIRTUAL_SUPERVISOR_TIMER = 1<<6"]
MACHINE_TIMER["MACHINE_TIMER = 1<<7"]
USER_EXTERNAL["USER_EXTERNAL = 1<<8"]
SUPERVISOR_EXTERNAL["SUPERVISOR_EXTERNAL = 1<<9"]
VIRTUAL_SUPERVISOR_EXTERNAL["VIRTUAL_SUPERVISOR_EXTERNAL = 1<<10"]
MACHINEL_EXTERNAL["MACHINEL_EXTERNAL = 1<<11"]
SUPERVISOR_GUEST_EXTERNEL["SUPERVISOR_GUEST_EXTERNEL = 1<<12"]
end
Exception Type Constants
Exception constants define the various fault and trap conditions:
| Exception Type | Constant Value | Description |
|---|---|---|
| INST_ADDR_MISALIGN | 1<<0 | Instruction address misaligned |
| INST_ACCESSS_FAULT | 1<<1 | Instruction access fault |
| ILLEGAL_INST | 1<<2 | Illegal instruction |
| BREAKPOINT | 1<<3 | Breakpoint |
| LOAD_ADDR_MISALIGNED | 1<<4 | Load address misaligned |
| LOAD_ACCESS_FAULT | 1<<5 | Load access fault |
| STORE_ADDR_MISALIGNED | 1<<6 | Store address misaligned |
| STORE_ACCESS_FAULT | 1<<7 | Store access fault |
| ENV_CALL_FROM_U_OR_VU | 1<<8 | Environment call from U-mode or VU-mode |
| ENV_CALL_FROM_HS | 1<<9 | Environment call from HS-mode |
| ENV_CALL_FROM_VS | 1<<10 | Environment call from VS-mode |
| ENV_CALL_FROM_M | 1<<11 | Environment call from M-mode |
IRQ Processing Constants
The IRQ module provides constants for interrupt processing and cause register interpretation:
flowchart TD
subgraph subGraph0["IRQ Constants (consts::traps::irq)"]
INTC_BASE["INTC_IRQ_BASE1 << (usize::BITS - 1)Interrupt Bit Mask"]
S_SOFT["S_SOFTINTC_IRQ_BASE + 1Supervisor Software IRQ"]
S_TIMER["S_TIMERINTC_IRQ_BASE + 5Supervisor Timer IRQ"]
S_EXT["S_EXTINTC_IRQ_BASE + 9Supervisor External IRQ"]
MAX_IRQ["MAX_IRQ_COUNT = 1024Maximum IRQ Number"]
TIMER_IRQ["TIMER_IRQ_NUM = S_TIMERTimer IRQ Identifier"]
end
The INTC_IRQ_BASE constant at src/consts.rs(L79) provides the base value for distinguishing interrupts from exceptions in the scause register, with interrupts having the most significant bit set.
Sources: src/consts.rs(L1 - L92)
Trap Handler Implementation
Relevant source files
This document covers the low-level assembly language trap handlers that manage guest virtual machine entry and exit. These handlers perform the critical register context switching between hypervisor and guest execution contexts, enabling secure isolation and efficient virtualization on RISC-V systems with H-extension support.
For higher-level trap processing and VM exit handling, see VM Exit Processing. For register data structure definitions, see Register Management.
Overview
The trap handler implementation consists of two primary assembly language routines that manage the transition between hypervisor and guest execution contexts:
- Guest Entry (
_run_guest) - Saves hypervisor state, loads guest state, and transfers control to the guest VM - Guest Exit (
_guest_exit) - Captures guest state upon VM exit and restores hypervisor execution context
These handlers work in conjunction with carefully designed data structures in src/regs.rs to ensure complete CPU state preservation during virtualization transitions.
Architecture and Data Flow
The trap handler operates through a precisely choreographed sequence of register and CSR manipulations:
flowchart TD
subgraph subGraph0["VmCpuRegisters Data Structure"]
HypRegs["hyp_regs: HypervisorCpuState"]
GuestRegs["guest_regs: GuestCpuState"]
VSCSRs["vs_csrs: GuestVsCsrs"]
TrapCSRs["trap_csrs: VmCpuTrapState"]
end
HypCall["Hypervisor calls _run_guest()"]
SaveHyp["Save Hypervisor State"]
LoadGuest["Load Guest State"]
SetTrap["Set trap vector to _guest_exit"]
SRET["Execute sret to enter guest"]
GuestExec["Guest Execution"]
TrapOccurs["Guest Trap/Exit Occurs"]
SaveGuest["Save Guest State"]
RestoreHyp["Restore Hypervisor State"]
Return["Return to Hypervisor"]
GuestExec --> TrapOccurs
HypCall --> SaveHyp
LoadGuest --> GuestRegs
LoadGuest --> SetTrap
RestoreHyp --> HypRegs
RestoreHyp --> Return
SRET --> GuestExec
SaveGuest --> GuestRegs
SaveGuest --> RestoreHyp
SaveHyp --> HypRegs
SaveHyp --> LoadGuest
SetTrap --> SRET
TrapOccurs --> SaveGuest
Sources: src/trap.S(L1 - L183) src/regs.rs(L116 - L196)
Register Context Data Structures
The trap handlers operate on the VmCpuRegisters structure, which contains separate state containers for hypervisor and guest contexts:
| Structure | Purpose | Key Fields |
|---|---|---|
| HypervisorCpuState | Hypervisor execution state | GPRs, sstatus, hstatus, scounteren, stvec, sscratch |
| GuestCpuState | Guest execution state | GPRs, sstatus, hstatus, scounteren, sepc |
| GeneralPurposeRegisters | 32 RISC-V registers | x0-x31 (zero, ra, sp, gp, tp, t0-t6, s0-s11, a0-a7) |
The assembly code accesses these structures through computed offsets provided by the Rust compilation system:
flowchart TD
subgraph subGraph2["Offset Calculation Functions"]
VmCpuRegs["VmCpuRegisters"]
HypState["hyp_regs"]
GuestState["guest_regs"]
HypGPRs["HypervisorCpuState.gprs"]
GuestGPRs["GuestCpuState.gprs"]
HypGPROffset["hyp_gpr_offset()"]
GuestGPROffset["guest_gpr_offset()"]
HypCSROffset["hyp_csr_offset!()"]
GuestCSROffset["guest_csr_offset!()"]
end
subgraph subGraph1["Assembly Constants"]
HypRA["hyp_ra offset"]
HypSP["hyp_sp offset"]
GuestRA["guest_ra offset"]
GuestSP["guest_sp offset"]
HypSSTATUS["hyp_sstatus offset"]
GuestSSTATUS["guest_sstatus offset"]
end
subgraph subGraph0["Rust Data Structure"]
VmCpuRegs["VmCpuRegisters"]
HypState["hyp_regs"]
GuestState["guest_regs"]
HypGPRs["HypervisorCpuState.gprs"]
GuestGPRs["GuestCpuState.gprs"]
HypGPROffset["hyp_gpr_offset()"]
GuestGPROffset["guest_gpr_offset()"]
end
GuestCSROffset --> GuestSSTATUS
GuestGPROffset --> GuestRA
GuestGPROffset --> GuestSP
GuestState --> GuestGPRs
HypCSROffset --> HypSSTATUS
HypGPROffset --> HypRA
HypGPROffset --> HypSP
HypState --> HypGPRs
VmCpuRegs --> GuestState
VmCpuRegs --> HypState
Sources: src/trap.rs(L7 - L33) src/regs.rs(L1 - L114) src/regs.rs(L116 - L138)
Guest Entry Sequence (_run_guest)
The _run_guest function implements the critical transition from hypervisor to guest execution:
Hypervisor State Preservation
The handler first saves the hypervisor's execution context, excluding temporary registers and the a0 register which contains the VmCpuRegisters pointer:
- General-purpose registers (ra, gp, tp, s0-s11, a1-a7, sp)
- Critical CSRs (sstatus, hstatus, scounteren, stvec, sscratch)
Guest State Loading
The handler then configures the CPU for guest execution:
- Swaps hypervisor CSRs with guest CSRs using
csrrwinstructions - Loads guest program counter into
sepc - Sets trap vector (
stvec) to_guest_exitfor handling guest traps - Stores
VmCpuRegisterspointer insscratchfor quick access during exit - Restores all guest general-purpose registers
Context Switch Completion
The sequence concludes with an sret instruction that:
- Switches to guest privilege level
- Jumps to the guest program counter (
sepc) - Enables guest execution with appropriate privilege and virtualization settings
Sources: src/trap.S(L3 - L91) src/trap.rs(L35 - L102)
Guest Exit Sequence (_guest_exit)
When the guest encounters a trap, interrupt, or exception, control transfers to _guest_exit:
Guest State Capture
The exit handler immediately preserves the guest's execution state:
- Retrieves
VmCpuRegisterspointer fromsscratch - Saves all guest general-purpose registers to the guest state structure
- Captures guest's
a0register value fromsscratch - Records guest program counter from
sepc
Hypervisor State Restoration
The handler restores the hypervisor execution environment:
- Swaps guest CSRs back to hypervisor CSRs using
csrrw - Restores hypervisor trap vector (
stvec) - Restores hypervisor scratch register (
sscratch) - Reloads all hypervisor general-purpose registers
Return to Hypervisor
The handler concludes with a ret instruction, returning control to the hypervisor code that called _run_guest.
sequenceDiagram
participant Hypervisor as Hypervisor
participant Guest as Guest
participant _run_guest as _run_guest
participant _guest_exit as _guest_exit
Hypervisor ->> _run_guest: Call with VmCpuRegisters*
_run_guest ->> _run_guest: Save hypervisor GPRs
_run_guest ->> _run_guest: Save hypervisor CSRs
_run_guest ->> _run_guest: Load guest CSRs
_run_guest ->> _run_guest: Set stvec to _guest_exit
_run_guest ->> _run_guest: Load guest GPRs
_run_guest ->> Guest: sret (enter guest)
Guest ->> Guest: Execute guest code
Guest ->> _guest_exit: Trap/Exception occurs
_guest_exit ->> _guest_exit: Save guest GPRs
_guest_exit ->> _guest_exit: Save guest CSRs
_guest_exit ->> _guest_exit: Restore hypervisor CSRs
_guest_exit ->> _guest_exit: Restore hypervisor GPRs
_guest_exit ->> Hypervisor: ret (return to hypervisor)
Sources: src/trap.S(L92 - L183)
Assembly-Rust Interface
The integration between assembly and Rust code relies on computed memory offsets and the global_asm! macro:
Offset Calculation
The Rust code provides compile-time offset calculations for accessing structure fields:
hyp_gpr_offset()computes offsets into hypervisor register arraysguest_gpr_offset()computes offsets into guest register arrayshyp_csr_offset!()andguest_csr_offset!()macros compute CSR field offsets
Assembly Integration
The core::arch::global_asm! macro embeds the assembly code with substituted constants:
core::arch::global_asm!(
include_str!("trap.S"),
hyp_ra = const hyp_gpr_offset(GprIndex::RA),
guest_ra = const guest_gpr_offset(GprIndex::RA),
hyp_sstatus = const hyp_csr_offset!(sstatus),
// ... additional offset constants
);
This approach ensures type safety and automatic offset calculation while maintaining the performance benefits of hand-optimized assembly code.
Sources: src/trap.rs(L1 - L102) src/regs.rs(L5 - L86)
Special Register Handling
sscratch Register Usage
The sscratch CSR serves a dual purpose in the trap handling mechanism:
- During guest execution: Contains the
VmCpuRegisterspointer for fast access - During hypervisor execution: Contains the hypervisor's original
sscratchvalue
Register A0 Special Case
The a0 register receives special handling because it carries the VmCpuRegisters pointer:
- Not saved during initial hypervisor state preservation
- Swapped with guest
a0viasscratchduring guest entry - Guest's
a0value recovered fromsscratchduring exit
Zero Register Enforcement
The GeneralPurposeRegisters::set_reg() method enforces RISC-V architectural requirements by preventing writes to the zero register (x0), ensuring compliance with the RISC-V specification.
Sources: src/trap.S(L53 - L55) src/trap.S(L94 - L95) src/trap.S(L129 - L131) src/regs.rs(L96 - L102)
CSR Definitions and Hardware Registers
Relevant source files
This document provides comprehensive reference material for the Control and Status Register (CSR) definitions and hardware register interfaces used throughout the RISC-V VCPU hypervisor system. It covers the register bitfield definitions, CSR initialization procedures, and the hierarchical organization of supervisor, virtual supervisor, and hypervisor-level registers.
For information about trap handling and exception processing that utilizes these CSRs, see Trap Handler Implementation. For system constants and trap definitions, see System Constants and Trap Definitions.
CSR Architecture Overview
The RISC-V hypervisor extension introduces a three-level privilege hierarchy that requires careful management of CSRs across supervisor, virtual supervisor, and hypervisor modes. The system implements comprehensive CSR definitions using the tock_registers framework to provide type-safe register access.
Register Hierarchy and Privilege Levels
flowchart TD
subgraph subGraph2["Virtual Supervisor Mode (VS-level)"]
VSSTATUS["VSSTATUSVirtual Supervisor Status"]
VSIE["VSIEVirtual Supervisor IE"]
VSTVEC["VSTVECVirtual Trap Vector"]
VSSCRATCH["VSSCRATCHVirtual Scratch"]
VSEPC["VSEPCVirtual Exception PC"]
VSCAUSE["VSCAUSEVirtual Exception Cause"]
VSTVAL["VSTVALVirtual Trap Value"]
VSATP["VSATPVirtual Address Translation"]
end
subgraph subGraph1["Supervisor Mode (S-level)"]
SSTATUS["SSTATUSSupervisor Status"]
SIE["SIESupervisor Interrupt Enable"]
STVEC["STVECTrap Vector"]
SSCRATCH["SSCRATCHScratch Register"]
SEPC["SEPCException PC"]
SCAUSE["SCAUSEException Cause"]
STVAL["STVALTrap Value"]
SATP["SATPAddress Translation"]
end
subgraph subGraph0["Hypervisor Mode (HS-level)"]
HSTATUS["HSTATUSHypervisor Status"]
HEDELEG["HEDELEGException Delegation"]
HIDELEG["HIDELEGInterrupt Delegation"]
HIE["HIEHypervisor Interrupt Enable"]
HVIP["HVIPVirtual Interrupt Pending"]
HCOUNTEREN["HCOUNTERENCounter Enable"]
HGATP["HGATPGuest Address Translation"]
end
HEDELEG --> SCAUSE
HIDELEG --> SIE
HSTATUS --> VSSTATUS
HVIP --> VSIE
Sources: def.rs(L1 - L52) src/percpu.rs(L44 - L81)
CSR Address Space Organization
flowchart TD
subgraph Implementation_Modules["Implementation_Modules"]
SIE_Impl["sie moduleSupervisor Interrupt Enable"]
HSTATUS_Impl["hstatus moduleHypervisor Status"]
HEDELEG_Impl["hedeleg moduleException Delegation"]
HIDELEG_Impl["hideleg moduleInterrupt Delegation"]
HIE_Impl["hie moduleHypervisor Interrupt Enable"]
HCOUNTEREN_Impl["hcounteren moduleCounter Enable"]
HVIP_Impl["hvip moduleVirtual Interrupt Pending"]
end
subgraph CSR_Address_Space["CSR_Address_Space"]
S_Range["0x100-0x1FFSupervisor CSRs"]
VS_Range["0x200-0x2FFVirtual Supervisor CSRs"]
H_Range["0x600-0x6FFHypervisor CSRs"]
M_Range["0x300-0x3FFMachine CSRs"]
end
H_Range --> HCOUNTEREN_Impl
H_Range --> HEDELEG_Impl
H_Range --> HIDELEG_Impl
H_Range --> HIE_Impl
H_Range --> HSTATUS_Impl
H_Range --> HVIP_Impl
S_Range --> SIE_Impl
Sources: def.rs(L4 - L52) def.rs(L551 - L684) def.rs(L687 - L1284)
CSR Initialization Process
The system initializes CSRs through the setup_csrs() function called during per-CPU initialization. This process configures exception delegation, interrupt delegation, and enables necessary hardware features.
CSR Setup Flow
flowchart TD
subgraph Exception_Types["Exception_Types"]
LOAD_PAGE_FAULT["LOAD_PAGE_FAULT"]
STORE_PAGE_FAULT["STORE_PAGE_FAULT"]
ILLEGAL_INST["ILLEGAL_INST"]
Start["setup_csrs()"]
subgraph Interrupt_Types["Interrupt_Types"]
VIRTUAL_SUPERVISOR_TIMER["VIRTUAL_SUPERVISOR_TIMER"]
VIRTUAL_SUPERVISOR_EXTERNAL["VIRTUAL_SUPERVISOR_EXTERNAL"]
VIRTUAL_SUPERVISOR_SOFT["VIRTUAL_SUPERVISOR_SOFT"]
INST_ADDR_MISALIGN["INST_ADDR_MISALIGN"]
BREAKPOINT["BREAKPOINT"]
ENV_CALL_FROM_U_OR_VU["ENV_CALL_FROM_U_OR_VU"]
INST_PAGE_FAULT["INST_PAGE_FAULT"]
end
end
HEDELEG_Setup["Configure HEDELEGException Delegation"]
HIDELEG_Setup["Configure HIDELEGInterrupt Delegation"]
HVIP_Clear["Clear HVIPVirtual Interrupts"]
HCOUNTEREN_Set["Set HCOUNTERENCounter Access"]
SIE_Enable["Enable SIESupervisor Interrupts"]
Complete["CSR Setup Complete"]
HCOUNTEREN_Set --> SIE_Enable
HEDELEG_Setup --> HIDELEG_Setup
HIDELEG_Setup --> HVIP_Clear
HVIP_Clear --> HCOUNTEREN_Set
SIE_Enable --> Complete
Start --> HEDELEG_Setup
Sources: src/percpu.rs(L44 - L81)
Key CSR Register Definitions
Hypervisor Exception Delegation Register (HEDELEG)
The hedeleg module provides bitfield definitions for configuring which exceptions are delegated to lower privilege levels. Each bit corresponds to a specific exception type defined in the trap constants.
| Field | Bit Position | Description |
|---|---|---|
| instr_misaligned | 0 | Instruction address misaligned |
| instr_fault | 1 | Instruction access fault |
| illegal_instr | 2 | Illegal instruction |
| breakpoint | 3 | Breakpoint |
| load_misaligned | 4 | Load address misaligned |
| load_fault | 5 | Load access fault |
| store_misaligned | 6 | Store address misaligned |
| store_fault | 7 | Store access fault |
| u_ecall | 8 | User environment call |
| instr_page_fault | 12 | Instruction page fault |
| load_page_fault | 13 | Load page fault |
| store_page_fault | 15 | Store page fault |
Sources: def.rs(L55 - L547)
Hypervisor Status Register (HSTATUS)
The hstatus module defines the hypervisor status register with fields controlling virtualization behavior and guest state management.
| Field | Bit Position | Width | Description |
|---|---|---|---|
| vsbe | 6 | 1 | Virtual supervisor big-endian |
| gva | 6 | 1 | Guest virtual address |
| spv | 7 | 1 | Supervisor previous virtualization mode |
| spvp | 8 | 1 | Supervisor previous privilege |
| hu | 9 | 1 | Hypervisor in user mode |
| vgein | 12-17 | 6 | Virtual guest external interrupt number |
| vtvm | 20 | 1 | Virtual TVM |
| vtw | 21 | 1 | Virtual timeout wait |
| vtsr | 22 | 1 | Virtual trap SRET |
| vsxl | 32-33 | 2 | Virtual supervisor XLEN |
Sources: def.rs(L687 - L1284)
Supervisor Interrupt Enable Register (SIE)
The sie module provides definitions for enabling specific interrupt types at the supervisor level.
| Field | Bit Position | Description |
|---|---|---|
| ssoft | 1 | Supervisor software interrupt |
| stimer | 5 | Supervisor timer interrupt |
| sext | 9 | Supervisor external interrupt |
Sources: def.rs(L551 - L684)
Virtual Interrupt Management
The system provides comprehensive virtual interrupt management through several CSR modules:
flowchart TD
subgraph Control_Operations["Control_Operations"]
Delegate["hideleg.write()Delegate interrupts"]
Enable["hie.write()Enable interrupts"]
Inject["hvip.set_*()Inject interrupts"]
Clear["hvip.clear_*()Clear interrupts"]
end
subgraph Interrupt_Types["Interrupt_Types"]
VSSOFT["vssoftBit 2VS-mode software"]
VSTIMER["vstimerBit 6VS-mode timer"]
VSEXT["vsextBit 10VS-mode external"]
SGEXT["sgextBit 12Supervisor guest external"]
end
subgraph Virtual_Interrupt_Control["Virtual_Interrupt_Control"]
HIDELEG_CSR["hideleg CSR0x603"]
HIE_CSR["hie CSR0x604"]
HVIP_CSR["hvip CSR0x645"]
end
Delegate --> VSEXT
Delegate --> VSSOFT
Delegate --> VSTIMER
Enable --> SGEXT
HIDELEG_CSR --> Delegate
HIE_CSR --> Enable
HVIP_CSR --> Clear
HVIP_CSR --> Inject
Sources: def.rs(L1287 - L1596) def.rs(L1422 - L1596) def.rs(L1774 - L1908)
CSR Usage in System Initialization
The RISCVPerCpu implementation demonstrates practical CSR usage during system initialization. The setup_csrs() function configures the hypervisor environment for guest execution.
Exception Delegation Configuration
flowchart TD
subgraph Delegated_Exceptions["Delegated_Exceptions"]
INST_ADDR_MISALIGN_BIT["INST_ADDR_MISALIGN"]
BREAKPOINT_BIT["BREAKPOINT"]
ENV_CALL_BIT["ENV_CALL_FROM_U_OR_VU"]
INST_PAGE_FAULT_BIT["INST_PAGE_FAULT"]
LOAD_PAGE_FAULT_BIT["LOAD_PAGE_FAULT"]
STORE_PAGE_FAULT_BIT["STORE_PAGE_FAULT"]
ILLEGAL_INST_BIT["ILLEGAL_INST"]
end
subgraph setup_csrs_function["setup_csrs_function"]
HEDELEG_Config["hedeleg::Hedeleg::from_bits()"]
Exception_Mask["traps::exception bitmask"]
HEDELEG_Write["hedeleg.write()"]
end
BREAKPOINT_BIT --> Exception_Mask
ENV_CALL_BIT --> Exception_Mask
Exception_Mask --> HEDELEG_Config
HEDELEG_Config --> HEDELEG_Write
ILLEGAL_INST_BIT --> Exception_Mask
INST_ADDR_MISALIGN_BIT --> Exception_Mask
INST_PAGE_FAULT_BIT --> Exception_Mask
LOAD_PAGE_FAULT_BIT --> Exception_Mask
STORE_PAGE_FAULT_BIT --> Exception_Mask
Counter Enable Configuration
The system includes a workaround for the hcounteren CSR due to an error in the riscv crate. Direct assembly is used to write the correct CSR address:
// Direct CSR write due to riscv crate error
core::arch::asm!("csrw {csr}, {rs}", csr = const 0x606, rs = in(reg) -1);
Sources: src/percpu.rs(L72 - L74)
Register Access Patterns
The CSR definitions use the tock_registers framework to provide type-safe register access with clear field semantics. Each register module includes:
- Field definitions with bit positions and widths
- Value enumerations for multi-bit fields
- SET/CLEAR constants for single-bit fields
- Type-safe conversion functions between values and register representations
Field Value Operations
flowchart TD
subgraph Usage_Examples["Usage_Examples"]
HSTATUS_SPV["hstatus::spv::Supervisor"]
SIE_SEXT["sie::sext::SET"]
HVIP_CLEAR["hvip::vstimer::CLEAR"]
end
subgraph Register_Field_Operations["Register_Field_Operations"]
FieldValue["FieldValue"]
SET_Const["Field::SET constant"]
CLEAR_Const["Field::CLEAR constant"]
Custom_Value["Custom value creation"]
end
CLEAR_Const --> HVIP_CLEAR
Custom_Value --> HSTATUS_SPV
FieldValue --> CLEAR_Const
FieldValue --> Custom_Value
FieldValue --> SET_Const
SET_Const --> SIE_SEXT
Sources: def.rs(L783 - L881) def.rs(L578 - L603) def.rs(L853 - L867)
System Constants and Trap Definitions
Relevant source files
This document covers the system constants, interrupt types, exception definitions, and trap-related constants used throughout the RISC-V VCPU hypervisor system. These definitions provide the foundation for trap handling, interrupt management, and VM exit processing.
For detailed information about the trap handler assembly implementation, see Trap Handler Implementation. For CSR register definitions used in conjunction with these constants, see CSR Definitions and Hardware Registers.
Trap Constants Architecture
The system defines comprehensive constants for all RISC-V trap types, organized into three main categories: interrupts, exceptions, and IRQ handling. These constants are used throughout the hypervisor for identifying and processing different trap conditions.
Trap Type Hierarchy
flowchart TD
subgraph subGraph3["Trap Constants (consts.rs)"]
TrapMod["traps module"]
subgraph subGraph2["IRQ Constants"]
IrqMod["irq module"]
IntcBase["INTC_IRQ_BASE = 1<<(usize::BITS-1)"]
SSoft["S_SOFT = INTC_IRQ_BASE + 1"]
STimer["S_TIMER = INTC_IRQ_BASE + 5"]
SExt["S_EXT = INTC_IRQ_BASE + 9"]
MaxIrq["MAX_IRQ_COUNT = 1024"]
TimerIrq["TIMER_IRQ_NUM = S_TIMER"]
end
subgraph subGraph1["Exception Constants"]
ExcMod["exception module"]
InstMisalign["INST_ADDR_MISALIGN = 1<<0"]
InstFault["INST_ACCESSS_FAULT = 1<<1"]
IllegalInst["ILLEGAL_INST = 1<<2"]
Breakpoint["BREAKPOINT = 1<<3"]
LoadMisalign["LOAD_ADDR_MISALIGNED = 1<<4"]
LoadFault["LOAD_ACCESS_FAULT = 1<<5"]
StoreMisalign["STORE_ADDR_MISALIGNED = 1<<6"]
StoreFault["STORE_ACCESS_FAULT = 1<<7"]
EnvCallU["ENV_CALL_FROM_U_OR_VU = 1<<8"]
EnvCallHS["ENV_CALL_FROM_HS = 1<<9"]
EnvCallVS["ENV_CALL_FROM_VS = 1<<10"]
EnvCallM["ENV_CALL_FROM_M = 1<<11"]
InstPageFault["INST_PAGE_FAULT = 1<<12"]
LoadPageFault["LOAD_PAGE_FAULT = 1<<13"]
StorePageFault["STORE_PAGE_FAULT = 1<<15"]
InstGuestFault["INST_GUEST_PAGE_FAULT = 1<<20"]
LoadGuestFault["LOAD_GUEST_PAGE_FAULT = 1<<21"]
VirtInst["VIRTUAL_INST = 1<<22"]
StoreGuestFault["STORE_GUEST_PAGE_FAULT = 1<<23"]
end
subgraph subGraph0["Interrupt Constants"]
IntMod["interrupt module"]
UserSoft["USER_SOFT = 1<<0"]
SuperSoft["SUPERVISOR_SOFT = 1<<1"]
VirtSupSoft["VIRTUAL_SUPERVISOR_SOFT = 1<<2"]
MachineSoft["MACHINE_SOFT = 1<<3"]
UserTimer["USER_TIMER = 1<<4"]
SuperTimer["SUPERVISOR_TIMER = 1<<5"]
VirtSupTimer["VIRTUAL_SUPERVISOR_TIMER = 1<<6"]
MachineTimer["MACHINE_TIMER = 1<<7"]
UserExt["USER_EXTERNAL = 1<<8"]
SuperExt["SUPERVISOR_EXTERNAL = 1<<9"]
VirtSupExt["VIRTUAL_SUPERVISOR_EXTERNAL = 1<<10"]
MachineExt["MACHINEL_EXTERNAL = 1<<11"]
SuperGuestExt["SUPERVISOR_GUEST_EXTERNEL = 1<<12"]
end
end
ExcMod --> Breakpoint
ExcMod --> EnvCallHS
ExcMod --> EnvCallM
ExcMod --> EnvCallU
ExcMod --> EnvCallVS
ExcMod --> IllegalInst
ExcMod --> InstFault
ExcMod --> InstGuestFault
ExcMod --> InstMisalign
ExcMod --> InstPageFault
ExcMod --> LoadFault
ExcMod --> LoadGuestFault
ExcMod --> LoadMisalign
ExcMod --> LoadPageFault
ExcMod --> StoreFault
ExcMod --> StoreGuestFault
ExcMod --> StoreMisalign
ExcMod --> StorePageFault
ExcMod --> VirtInst
IntMod --> MachineExt
IntMod --> MachineSoft
IntMod --> MachineTimer
IntMod --> SuperExt
IntMod --> SuperGuestExt
IntMod --> SuperSoft
IntMod --> SuperTimer
IntMod --> UserExt
IntMod --> UserSoft
IntMod --> UserTimer
IntMod --> VirtSupExt
IntMod --> VirtSupSoft
IntMod --> VirtSupTimer
IrqMod --> IntcBase
IrqMod --> MaxIrq
IrqMod --> SExt
IrqMod --> SSoft
IrqMod --> STimer
IrqMod --> TimerIrq
TrapMod --> ExcMod
TrapMod --> IntMod
TrapMod --> IrqMod
Sources: src/consts.rs(L1 - L92)
Interrupt Type Constants
The system defines interrupt constants as bit flags representing different interrupt sources in the RISC-V architecture. These constants are organized by privilege level and interrupt type.
Software Interrupts
| Constant | Value | Description |
|---|---|---|
| USER_SOFT | 1 << 0 | User software interrupt |
| SUPERVISOR_SOFT | 1 << 1 | Supervisor software interrupt |
| VIRTUAL_SUPERVISOR_SOFT | 1 << 2 | Virtual supervisor software interrupt |
| MACHINE_SOFT | 1 << 3 | Machine software interrupt |
Timer Interrupts
| Constant | Value | Description |
|---|---|---|
| USER_TIMER | 1 << 4 | User timer interrupt |
| SUPERVISOR_TIMER | 1 << 5 | Supervisor timer interrupt |
| VIRTUAL_SUPERVISOR_TIMER | 1 << 6 | Virtual supervisor timer interrupt |
| MACHINE_TIMER | 1 << 7 | Machine timer interrupt |
External Interrupts
| Constant | Value | Description |
|---|---|---|
| USER_EXTERNAL | 1 << 8 | User external interrupt |
| SUPERVISOR_EXTERNAL | 1 << 9 | Supervisor external interrupt |
| VIRTUAL_SUPERVISOR_EXTERNAL | 1 << 10 | Virtual supervisor external interrupt |
| MACHINEL_EXTERNAL | 1 << 11 | Machine external interrupt |
| SUPERVISOR_GUEST_EXTERNEL | 1 << 12 | Supervisor guest external interrupt |
Sources: src/consts.rs(L4 - L32)
Exception Type Constants
Exception constants define the various fault and trap conditions that can occur during instruction execution. These are used to identify the specific cause of a trap.
Memory Access Exceptions
| Constant | Value | Description |
|---|---|---|
| INST_ADDR_MISALIGN | 1 << 0 | Instruction address misaligned |
| INST_ACCESSS_FAULT | 1 << 1 | Instruction access fault |
| LOAD_ADDR_MISALIGNED | 1 << 4 | Load address misaligned |
| LOAD_ACCESS_FAULT | 1 << 5 | Load access fault |
| STORE_ADDR_MISALIGNED | 1 << 6 | Store address misaligned |
| STORE_ACCESS_FAULT | 1 << 7 | Store access fault |
Control Flow Exceptions
| Constant | Value | Description |
|---|---|---|
| ILLEGAL_INST | 1 << 2 | Illegal instruction |
| BREAKPOINT | 1 << 3 | Breakpoint exception |
Environment Call Exceptions
| Constant | Value | Description |
|---|---|---|
| ENV_CALL_FROM_U_OR_VU | 1 << 8 | Environment call from U-mode or VU-mode |
| ENV_CALL_FROM_HS | 1 << 9 | Environment call from HS-mode |
| ENV_CALL_FROM_VS | 1 << 10 | Environment call from VS-mode |
| ENV_CALL_FROM_M | 1 << 11 | Environment call from M-mode |
Page Fault Exceptions
| Constant | Value | Description |
|---|---|---|
| INST_PAGE_FAULT | 1 << 12 | Instruction page fault |
| LOAD_PAGE_FAULT | 1 << 13 | Load page fault |
| STORE_PAGE_FAULT | 1 << 15 | Store page fault |
| INST_GUEST_PAGE_FAULT | 1 << 20 | Instruction guest page fault |
| LOAD_GUEST_PAGE_FAULT | 1 << 21 | Load guest page fault |
| STORE_GUEST_PAGE_FAULT | 1 << 23 | Store guest page fault |
Virtualization Exceptions
| Constant | Value | Description |
|---|---|---|
| VIRTUAL_INST | 1 << 22 | Virtual instruction exception |
Sources: src/consts.rs(L34 - L74)
IRQ Processing Constants
The IRQ constants provide standardized values for interrupt request processing and scause register interpretation.
Base IRQ Constants
| Constant | Value | Description |
|---|---|---|
| INTC_IRQ_BASE | 1 << (usize::BITS - 1) | Interrupt bit inscauseregister |
| S_SOFT | INTC_IRQ_BASE + 1 | Supervisor software interrupt inscause |
| S_TIMER | INTC_IRQ_BASE + 5 | Supervisor timer interrupt inscause |
| S_EXT | INTC_IRQ_BASE + 9 | Supervisor external interrupt inscause |
System Limits
| Constant | Value | Description |
|---|---|---|
| MAX_IRQ_COUNT | 1024 | Maximum number of IRQs supported |
| TIMER_IRQ_NUM | S_TIMER | Timer IRQ number alias |
Sources: src/consts.rs(L76 - L91)
Trap Handling Integration
The trap constants are integrated with the VCPU's VM exit handler to provide comprehensive trap processing. The system uses the RISC-V scause register to determine trap types and dispatch to appropriate handlers.
VM Exit Handler Trap Processing
flowchart TD
subgraph subGraph4["Return Values"]
ExitNothing["AxVCpuExitReason::Nothing"]
ExitPageFault["AxVCpuExitReason::NestedPageFault"]
ExitExtInt["AxVCpuExitReason::ExternalInterrupt"]
ExitHypercall["AxVCpuExitReason::Hypercall"]
ExitCpuUp["AxVCpuExitReason::CpuUp"]
ExitSystemDown["AxVCpuExitReason::SystemDown"]
end
subgraph subGraph3["vmexit_handler Function"]
VmExit["vmexit_handler()"]
ReadScause["scause::read()"]
TrapMatch["match scause.cause()"]
subgraph subGraph2["CSR State Capture"]
CaptureScause["trap_csrs.scause = scause::read().bits()"]
CaptureStval["trap_csrs.stval = stval::read()"]
CaptureHtval["trap_csrs.htval = htval::read()"]
CaptureHtinst["trap_csrs.htinst = htinst::read()"]
end
subgraph subGraph1["Interrupt Handling"]
SupervisorTimer["SupervisorTimer"]
SupervisorExternal["SupervisorExternal"]
TimerHandler["Timer Interrupt Processing"]
ExtHandler["External Interrupt Processing"]
end
subgraph subGraph0["Exception Handling"]
VSEnvCall["VirtualSupervisorEnvCall"]
LoadGuestFault["LoadGuestPageFault"]
StoreGuestFault["StoreGuestPageFault"]
SBICall["SBI Call Processing"]
PageFaultHandler["Nested Page Fault"]
end
end
ExtHandler --> ExitExtInt
LoadGuestFault --> PageFaultHandler
PageFaultHandler --> ExitPageFault
ReadScause --> TrapMatch
SBICall --> ExitCpuUp
SBICall --> ExitHypercall
SBICall --> ExitNothing
SBICall --> ExitSystemDown
StoreGuestFault --> PageFaultHandler
SupervisorExternal --> ExtHandler
SupervisorTimer --> TimerHandler
TimerHandler --> ExitNothing
TrapMatch --> LoadGuestFault
TrapMatch --> StoreGuestFault
TrapMatch --> SupervisorExternal
TrapMatch --> SupervisorTimer
TrapMatch --> VSEnvCall
VSEnvCall --> SBICall
VmExit --> CaptureHtinst
VmExit --> CaptureHtval
VmExit --> CaptureScause
VmExit --> CaptureStval
VmExit --> ReadScause
Sources: src/vcpu.rs(L167 - L308)
Specific Trap Handling Cases
The VCPU system handles several critical trap scenarios using these constants:
SBI Environment Calls
When a VirtualSupervisorEnvCall exception occurs, the system processes SBI (Supervisor Binary Interface) calls by examining the extension ID and function ID in guest registers. This includes:
- Legacy SBI calls: Timer, console I/O, and system shutdown
- HSM extension: Hart start, stop, and suspend operations
- Custom hypercalls: Application-specific hypercall processing
- Forwarded calls: Standard SBI extensions handled by RustSBI
Guest Page Faults
The system handles two types of guest page faults:
LoadGuestPageFault: Guest attempted to load from an unmapped or protected pageStoreGuestPageFault: Guest attempted to store to an unmapped or protected page
These faults result in AxVCpuExitReason::NestedPageFault to allow the hypervisor to handle guest memory management.
Timer and External Interrupts
- Supervisor Timer Interrupts: Enable guest timer interrupts via
hvip::set_vstip() - Supervisor External Interrupts: Forward to hypervisor as
AxVCpuExitReason::ExternalInterrupt
Sources: src/vcpu.rs(L184 - L307)
Constant Usage Patterns
The trap constants follow RISC-V specification patterns where:
- Bit Position Encoding: Most constants use
1 << Nformat for bit flag representation - Privilege Level Organization: Constants are grouped by privilege levels (User, Supervisor, Machine)
- Interrupt vs Exception: Clear separation between interrupt (asynchronous) and exception (synchronous) constants
- IRQ Base Calculation: Uses architecture-specific bit width for interrupt base calculation
These patterns ensure compatibility with RISC-V hardware and provide efficient bit-mask operations for trap classification.
Sources: src/consts.rs(L1 - L92)
Development and Tooling
Relevant source files
This document covers the development infrastructure, build configuration, testing framework, and tooling used to develop and maintain the RISC-V VCPU hypervisor system. It includes details on the CI/CD pipeline, dependency management, code generation tools, and documentation workflow.
For information about the core VCPU implementation, see Core VCPU Implementation. For details about system architecture components, see System Architecture.
Build System and Dependencies
The RISC-V VCPU system uses Cargo as its primary build system with a carefully curated set of dependencies that provide RISC-V architecture support, hypervisor functionality, and low-level register manipulation capabilities.
Package Configuration
The project is configured as a Rust library crate targeting the 2024 edition with the package name riscv_vcpu version 0.1.0. The build system is designed specifically for bare-metal RISC-V environments without standard library support.
Dependency Architecture
flowchart TD
subgraph subGraph4["riscv_vcpu Implementation"]
vcpu_lib["riscv_vcpu Library"]
end
subgraph subGraph3["ArceOS Framework Dependencies"]
axerrno["axerrno:0.1.0Error Handling"]
page_table_entry["page_table_entry:0.5Page Table Management"]
memory_addr["memory_addr:0.3.1Memory Addressing"]
axaddrspace["axaddrspaceAddress Space Management"]
axvcpu["axvcpuVCPU Abstractions"]
end
subgraph subGraph2["SBI and Hypervisor Support"]
rustsbi["rustsbi:0.4.0SBI Implementation"]
sbi_rt["sbi-rt:0.0.3SBI Runtime"]
sbi_spec["sbi-spec:0.0.7SBI Specifications"]
end
subgraph subGraph1["RISC-V Architecture Support"]
riscv_crate["riscvCore RISC-V Support"]
riscv_decode["riscv-decodeInstruction Decoding"]
tock_registers["tock-registers:0.8.1Register Abstractions"]
memoffset["memoffsetMemory Layout"]
end
subgraph subGraph0["Core Dependencies"]
log["log:0.4.19Logging Framework"]
cfg_if["cfg-if:1.0Conditional Compilation"]
bitflags["bitflags:2.2Flag Types"]
bit_field["bit_field:0.10Bit Manipulation"]
crate_interface["crate_interface:0.1Interface Definitions"]
end
axaddrspace --> vcpu_lib
axerrno --> vcpu_lib
axvcpu --> vcpu_lib
bit_field --> vcpu_lib
bitflags --> vcpu_lib
cfg_if --> vcpu_lib
crate_interface --> vcpu_lib
log --> vcpu_lib
memoffset --> vcpu_lib
memory_addr --> vcpu_lib
page_table_entry --> vcpu_lib
riscv_crate --> vcpu_lib
riscv_decode --> vcpu_lib
rustsbi --> vcpu_lib
sbi_rt --> vcpu_lib
sbi_spec --> vcpu_lib
tock_registers --> vcpu_lib
Sources: Cargo.toml(L6 - L26)
Key Dependency Categories
| Category | Crates | Purpose |
|---|---|---|
| RISC-V Core | riscv,riscv-decode,tock-registers | Provide RISC-V instruction set support, instruction decoding, and register manipulation primitives |
| SBI Interface | rustsbi,sbi-rt,sbi-spec | Implement Supervisor Binary Interface for guest OS communication |
| ArceOS Integration | axaddrspace,axvcpu,axerrno | Interface with the ArceOS hypervisor framework |
| Low-Level Utilities | bitflags,bit_field,memoffset | Provide bit manipulation and memory layout tools |
CI/CD Pipeline
The continuous integration pipeline ensures code quality, compatibility across Rust toolchains, and automated documentation deployment using GitHub Actions.
Pipeline Architecture
flowchart TD
subgraph subGraph3["Documentation Pipeline"]
doc_build["cargo docDocumentation Generation"]
doc_deploy["GitHub PagesDocumentation Deployment"]
end
subgraph subGraph2["Quality Checks"]
fmt_check["cargo fmt --checkCode Formatting"]
clippy_check["cargo clippyLinting Analysis"]
build_check["cargo buildCompilation Test"]
unit_test["cargo testUnit Testing"]
end
subgraph subGraph1["CI Job Matrix"]
toolchain1["nightly-2024-12-25riscv64gc-unknown-none-elf"]
toolchain2["nightlyriscv64gc-unknown-none-elf"]
end
subgraph subGraph0["Trigger Events"]
push["Push Events"]
pr["Pull Requests"]
end
doc_build --> doc_deploy
pr --> toolchain1
pr --> toolchain2
push --> toolchain1
push --> toolchain2
toolchain1 --> build_check
toolchain1 --> clippy_check
toolchain1 --> doc_build
toolchain1 --> fmt_check
toolchain1 --> unit_test
toolchain2 --> build_check
toolchain2 --> clippy_check
toolchain2 --> fmt_check
toolchain2 --> unit_test
Sources: .github/workflows/ci.yml(L1 - L65)
CI Configuration Details
The pipeline runs two primary jobs: ci for code quality and testing, and doc for documentation generation and deployment.
Code Quality Matrix
The CI system tests against multiple Rust toolchain versions to ensure compatibility:
- Primary Toolchain:
nightly-2024-12-25(stable reference) - Latest Toolchain:
nightly(forward compatibility) - Target Architecture:
riscv64gc-unknown-none-elf(bare-metal RISC-V 64-bit)
Quality Gates
| Stage | Command | Purpose | Failure Handling |
|---|---|---|---|
| Format Check | cargo fmt --all -- --check | Enforce consistent code formatting | Hard failure |
| Linting | cargo clippy --target riscv64gc-unknown-none-elf --all-features | Static analysis and best practices | Continue on error for nightly |
| Build | cargo build --target riscv64gc-unknown-none-elf --all-features | Compilation verification | Continue on error for nightly |
| Unit Tests | cargo test --target x86_64-unknown-linux-gnu | Functional testing | Hard failure |
Sources: .github/workflows/ci.yml(L20 - L32)
Documentation Workflow
The documentation pipeline automatically generates and deploys API documentation to GitHub Pages for the default branch.
flowchart TD
subgraph subGraph1["Deployment Pipeline"]
gh_pages["GitHub PagesBranch: gh-pages"]
auto_deploy["JamesIves/github-pages-deploy-action"]
end
subgraph subGraph0["Documentation Generation"]
source["Source Codewith Doc Comments"]
cargo_doc["cargo doc--no-deps --all-features"]
index_gen["Index Page Generation"]
end
auto_deploy --> gh_pages
cargo_doc --> index_gen
index_gen --> auto_deploy
source --> cargo_doc
Sources: .github/workflows/ci.yml(L34 - L64)
The documentation build includes strict linting with RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs to ensure documentation completeness and link integrity.
Code Generation and Register Definitions
The system employs sophisticated code generation techniques to create type-safe register definitions for RISC-V Control and Status Registers (CSRs).
Register Definition Architecture
flowchart TD
subgraph subGraph2["Runtime Usage"]
csr_access["CSR Read/WriteOperations"]
type_safety["Compile-timeType Safety"]
field_manipulation["BitfieldManipulation"]
end
subgraph subGraph1["Generated CSR Definitions"]
csr_constants["CSR Address ConstantsCSR_HSTATUS: 0x600"]
register_structs["Register Structshstatus::Register"]
field_definitions["Field Definitionsspv, vsxl, vtvm"]
field_values["Field Value EnumsUser, Supervisor"]
end
subgraph subGraph0["Register Definition Generator"]
tock_registers["tock-registersMacro Framework"]
register_bitfields["register_bitfields!Macro Invocation"]
end
csr_constants --> csr_access
field_definitions --> field_manipulation
field_values --> field_manipulation
register_bitfields --> csr_constants
register_bitfields --> field_definitions
register_bitfields --> field_values
register_bitfields --> register_structs
register_structs --> type_safety
tock_registers --> register_bitfields
Sources: def.rs(L1 - L1910)
CSR Definition Structure
The register definitions follow a hierarchical pattern:
- CSR Address Constants: Hexadecimal addresses for each CSR register
- Register Modules: Type-safe wrappers for each register
- Field Definitions: Bit field specifications with masks and offsets
- Value Enumerations: Named constants for field values
Example Register Definition Pattern
The hstatus register demonstrates the complete pattern:
// CSR address constant
pub const CSR_HSTATUS: u16 = 0x600;
// Register module with fields
pub mod hstatus {
pub struct Register;
// Field definitions with bit positions
pub const spv: Field<usize, Register> = Field::new(mask, offset);
// Field value enumerations
pub mod spv {
pub enum Value {
User = 0,
Supervisor = 1,
}
}
}
Sources: def.rs(L37 - L881)
Generated Register Coverage
| Register Category | Examples | Purpose |
|---|---|---|
| Supervisor CSRs | CSR_SSTATUS,CSR_SIE,CSR_SATP | Guest supervisor mode state |
| Virtual Supervisor CSRs | CSR_VSSTATUS,CSR_VSIE,CSR_VSATP | Virtualized supervisor state |
| Hypervisor CSRs | CSR_HSTATUS,CSR_HEDELEG,CSR_HIE | Hypervisor control and delegation |
Testing Framework
The testing strategy combines unit testing for functional verification with CI-based integration testing across multiple toolchain versions.
Test Execution Environment
flowchart TD
subgraph subGraph1["Test Categories"]
functional["Functional TestsCore Logic Verification"]
compilation["Compilation TestsCross-platform Build"]
linting["Static AnalysisClippy Checks"]
formatting["Style Testsrustfmt Validation"]
end
subgraph subGraph0["Test Environments"]
unit_tests["Unit Testsx86_64-unknown-linux-gnu"]
integration_tests["Integration Testsriscv64gc-unknown-none-elf"]
end
integration_tests --> compilation
integration_tests --> formatting
integration_tests --> linting
unit_tests --> functional
Sources: .github/workflows/ci.yml(L30 - L32)
Test Configuration
The test suite runs with specific configurations:
- Unit Tests: Execute on
x86_64-unknown-linux-gnufor host-based testing - Integration Tests: Build verification on
riscv64gc-unknown-none-elftarget - Test Output: Uses
--nocaptureflag for complete test output visibility
Development Workflow Integration
The development workflow integrates multiple tools and processes to ensure code quality and system reliability.
Development Tool Chain
flowchart TD
subgraph Deployment["Deployment"]
crate_publish["Crate Publication"]
gh_pages["GitHub PagesDocumentation"]
end
subgraph subGraph2["Automated Validation"]
ci_pipeline["CI Pipeline"]
quality_gates["Quality Gates"]
doc_generation["Documentation"]
end
subgraph subGraph1["Version Control"]
git_commit["Git Commit"]
git_push["Git Push"]
pull_request["Pull Request"]
end
subgraph subGraph0["Local Development"]
rust_toolchain["Rust Nightly Toolchain"]
cargo_fmt["cargo fmtCode Formatting"]
cargo_clippy["cargo clippyLinting"]
cargo_test["cargo testUnit Testing"]
end
cargo_clippy --> cargo_test
cargo_fmt --> cargo_clippy
cargo_test --> git_commit
ci_pipeline --> quality_gates
doc_generation --> gh_pages
git_commit --> git_push
git_push --> ci_pipeline
pull_request --> ci_pipeline
quality_gates --> crate_publish
quality_gates --> doc_generation
rust_toolchain --> cargo_fmt
Sources: .github/workflows/ci.yml(L1 - L65) Cargo.toml(L1 - L26)
Required Toolchain Components
The development environment requires specific Rust toolchain components:
- Rust Source:
rust-srcfor cross-compilation support - Clippy: Static analysis and linting tool
- Rustfmt: Code formatting tool
- Target Support:
riscv64gc-unknown-none-elfcompilation target
Sources: .github/workflows/ci.yml(L18 - L19)
Overview
Relevant source files
Purpose and Scope
The axdevice crate provides virtual machine device emulation capabilities within the ArceOS hypervisor ecosystem. This document covers the core functionality of device configuration management and MMIO (Memory-Mapped I/O) operation handling for guest virtual machines.
For detailed information about system architecture patterns, see System Architecture. For implementation details of individual components, see Core Components. For integration with other ArceOS crates, see ArceOS Ecosystem Integration.
Device Emulation Framework
The axdevice crate serves as the central device management layer for the ArceOS hypervisor, enabling guest virtual machines to interact with emulated hardware devices through standardized MMIO operations. The crate abstracts device configuration and runtime behavior into two primary components: AxVmDeviceConfig for device setup and AxVmDevices for operational device management.
Core Entity Relationships
flowchart TD AxVmDeviceConfig["AxVmDeviceConfig(Configuration Management)"] AxVmDevices["AxVmDevices(Device Orchestrator)"] EmulatedDeviceConfig["EmulatedDeviceConfig(Individual Device Setup)"] BaseDeviceOps["BaseDeviceOps(Device Interface Trait)"] GuestVM["Guest VM(MMIO Operations)"] DeviceInstances["Device Instances(Console, VirtIO, etc.)"] AxVmDeviceConfig --> AxVmDevices AxVmDeviceConfig --> EmulatedDeviceConfig AxVmDevices --> BaseDeviceOps AxVmDevices --> DeviceInstances DeviceInstances --> BaseDeviceOps GuestVM --> AxVmDevices
Sources: src/lib.rs(L15 - L19) Cargo.toml(L1 - L19)
Main Components
The crate is organized into two fundamental modules that handle distinct aspects of device emulation:
| Component | Module | Primary Responsibility |
|---|---|---|
| AxVmDeviceConfig | config | Device configuration management and initialization parameters |
| AxVmDevices | device | Runtime device operation handling and MMIO request routing |
Component Interaction Flow
sequenceDiagram
participant Initialization as "Initialization"
participant AxVmDeviceConfig as "AxVmDeviceConfig"
participant AxVmDevices as "AxVmDevices"
participant GuestVM as "Guest VM"
participant EmulatedDevice as "Emulated Device"
Initialization ->> AxVmDeviceConfig: "Create configuration"
AxVmDeviceConfig ->> AxVmDevices: "AxVmDevices::new(config)"
AxVmDevices ->> EmulatedDevice: "Initialize device instances"
GuestVM ->> AxVmDevices: "MMIO read/write operations"
AxVmDevices ->> EmulatedDevice: "Route to appropriate device"
EmulatedDevice ->> AxVmDevices: "Return operation result"
AxVmDevices ->> GuestVM: "Complete MMIO operation"
Sources: src/lib.rs(L15 - L16) src/lib.rs(L18 - L19)
ArceOS Hypervisor Integration
The axdevice crate operates as a foundational component within the ArceOS hypervisor stack, integrating with several specialized crates to provide comprehensive device emulation capabilities:
Dependency Architecture
flowchart TD
subgraph subGraph2["External Dependencies"]
log["log(Logging Framework)"]
cfg_if["cfg-if(Conditional Compilation)"]
end
subgraph subGraph1["ArceOS System Dependencies"]
axerrno["axerrno(Error Handling)"]
memory_addr["memory_addr(Memory Addressing)"]
end
subgraph subGraph0["ArceOS Hypervisor Dependencies"]
axvmconfig["axvmconfig(VM Configuration)"]
axaddrspace["axaddrspace(Address Space Management)"]
axdevice_base["axdevice_base(Device Abstraction Layer)"]
end
axdevice["axdevice(This Crate)"]
axdevice --> axaddrspace
axdevice --> axdevice_base
axdevice --> axerrno
axdevice --> axvmconfig
axdevice --> cfg_if
axdevice --> log
axdevice --> memory_addr
Sources: Cargo.toml(L8 - L18)
Runtime Environment
The crate is designed for no_std environments, utilizing the alloc crate for dynamic memory allocation without requiring the full standard library. This design enables deployment in hypervisor contexts where minimal runtime overhead is critical.
The logging infrastructure is integrated throughout the system using the log crate, providing debugging and operational visibility into device emulation activities.
Sources: src/lib.rs(L1) src/lib.rs(L11 - L13)
Development Context
The axdevice crate follows the ArceOS project's modular architecture approach, where specialized functionality is separated into focused crates. The device emulation layer specifically handles the intersection between guest virtual machine memory operations and host-side device implementations.
For build and development procedures, see Development Guide. For detailed component implementation, see Configuration Management and Device Emulation.
Sources: Cargo.toml(L1 - L4) src/lib.rs(L3 - L9)
System Architecture
Relevant source files
Purpose and Scope
This document provides a comprehensive view of the axdevice system architecture, detailing how the configuration and device management components work together to provide virtual machine device emulation within the ArceOS hypervisor ecosystem. The architecture encompasses MMIO operation handling, device lifecycle management, and integration with external ArceOS components.
For detailed information about individual core components, see Core Components. For ArceOS ecosystem integration details, see ArceOS Ecosystem Integration.
High-Level System Architecture
The axdevice crate implements a two-layer architecture consisting of configuration management and device emulation orchestration:
Core Architecture Overview
flowchart TD
subgraph subGraph2["Guest VM Interface"]
MMIO_Read["handle_mmio_read()"]
MMIO_Write["handle_mmio_write()"]
find_dev["find_dev()"]
end
subgraph subGraph1["External Dependencies"]
EmulatedDeviceConfig["EmulatedDeviceConfig(from axvmconfig)"]
BaseDeviceOps["BaseDeviceOps(from axdevice_base)"]
GuestPhysAddr["GuestPhysAddr(from axaddrspace)"]
end
subgraph subGraph0["axdevice Core"]
AxVmDeviceConfig["AxVmDeviceConfigConfiguration Container"]
AxVmDevices["AxVmDevicesDevice Orchestrator"]
emu_devices["emu_devices: Vec<Arc<dyn BaseDeviceOps>>Device Collection"]
end
AxVmDeviceConfig --> AxVmDevices
AxVmDevices --> MMIO_Read
AxVmDevices --> MMIO_Write
AxVmDevices --> emu_devices
AxVmDevices --> find_dev
BaseDeviceOps --> emu_devices
EmulatedDeviceConfig --> AxVmDeviceConfig
GuestPhysAddr --> MMIO_Read
GuestPhysAddr --> MMIO_Write
GuestPhysAddr --> find_dev
Sources: src/config.rs(L5 - L8) src/device.rs(L12 - L16) src/device.rs(L57 - L62) src/device.rs(L65 - L75) src/device.rs(L78 - L92)
Component Interaction Flow
The system follows a clear initialization and runtime operation pattern:
Device Initialization and MMIO Handling Flow
sequenceDiagram
participant AxVmDeviceConfig as "AxVmDeviceConfig"
participant AxVmDevices as "AxVmDevices"
participant emu_devices as "emu_devices"
participant GuestVM as "Guest VM"
participant BaseDeviceOps as "BaseDeviceOps"
Note over AxVmDeviceConfig,BaseDeviceOps: Initialization Phase
AxVmDeviceConfig ->> AxVmDevices: "new(config)"
AxVmDevices ->> AxVmDevices: "init(&mut self, &emu_configs)"
Note over AxVmDevices: "Currently stub implementation"
Note over AxVmDeviceConfig,BaseDeviceOps: Runtime MMIO Operations
GuestVM ->> AxVmDevices: "handle_mmio_read(addr, width)"
AxVmDevices ->> AxVmDevices: "find_dev(addr)"
AxVmDevices ->> emu_devices: "iter().find(|dev| dev.address_range().contains(addr))"
emu_devices -->> AxVmDevices: "Some(Arc<dyn BaseDeviceOps>)"
AxVmDevices ->> BaseDeviceOps: "handle_read(addr, width)"
BaseDeviceOps -->> AxVmDevices: "AxResult<usize>"
AxVmDevices -->> GuestVM: "Return value"
GuestVM ->> AxVmDevices: "handle_mmio_write(addr, width, val)"
AxVmDevices ->> AxVmDevices: "find_dev(addr)"
AxVmDevices ->> BaseDeviceOps: "handle_write(addr, width, val)"
BaseDeviceOps -->> AxVmDevices: "Operation complete"
AxVmDevices -->> GuestVM: "Write acknowledged"
Sources: src/device.rs(L20 - L28) src/device.rs(L30 - L54) src/device.rs(L57 - L62) src/device.rs(L65 - L75) src/device.rs(L78 - L92)
Core Data Structures and Relationships
Configuration and Device Management Structure
classDiagram
class AxVmDeviceConfig {
+emu_configs: Vec~EmulatedDeviceConfig~
+new(emu_configs: Vec~EmulatedDeviceConfig~) Self
}
class AxVmDevices {
-emu_devices: Vec~Arc~dyn BaseDeviceOps~~
+new(config: AxVmDeviceConfig) Self
-init(&mut self, emu_configs: &Vec~EmulatedDeviceConfig~)
+find_dev(ipa: GuestPhysAddr) Option~Arc~dyn BaseDeviceOps~~
+handle_mmio_read(addr: GuestPhysAddr, width: usize) AxResult~usize~
+handle_mmio_write(addr: GuestPhysAddr, width: usize, val: usize)
}
class EmulatedDeviceConfig {
<<external>>
+emu_type: field
}
class BaseDeviceOps {
<<trait>>
+address_range() Range
+handle_read(addr: GuestPhysAddr, width: usize) AxResult~usize~
+handle_write(addr: GuestPhysAddr, width: usize, val: usize)
}
AxVmDeviceConfig *-- EmulatedDeviceConfig : "contains"
AxVmDevices o-- AxVmDeviceConfig : "initialized from"
AxVmDevices *-- BaseDeviceOps : "manages collection"
Sources: src/config.rs(L5 - L16) src/device.rs(L12 - L16) src/device.rs(L20 - L28) src/device.rs(L57 - L62)
MMIO Operation Handling Architecture
The system's core functionality centers around Memory-Mapped I/O (MMIO) operation routing:
MMIO Address Resolution and Device Routing
| Operation | Method | Address Resolution | Device Interaction |
|---|---|---|---|
| Read | handle_mmio_read | find_dev(addr)→address_range().contains(addr) | device.handle_read(addr, width) |
| Write | handle_mmio_write | find_dev(addr)→address_range().contains(addr) | device.handle_write(addr, width, val) |
The address resolution process follows this pattern:
flowchart TD MMIO_Request["MMIO Request(addr: GuestPhysAddr)"] find_dev["find_dev(addr)"] device_iter["emu_devices.iter()"] address_check["dev.address_range().contains(addr)"] device_found["Device FoundArc<dyn BaseDeviceOps>"] device_missing["No Device Foundpanic!"] handle_operation["handle_read() orhandle_write()"] MMIO_Request --> find_dev address_check --> device_found address_check --> device_missing device_found --> handle_operation device_iter --> address_check find_dev --> device_iter
Sources: src/device.rs(L57 - L62) src/device.rs(L65 - L75) src/device.rs(L78 - L92)
Dependency Architecture and External Integration
The system integrates with multiple ArceOS ecosystem components:
External Dependencies and Integration Points
flowchart TD
subgraph subGraph3["Standard Dependencies"]
log_crate["logLogging Macros"]
cfg_if["cfg-ifConditional Compilation"]
end
subgraph subGraph2["ArceOS System Components"]
axerrno["axerrnoAxResult"]
memory_addr["memory_addrMemory Addressing"]
end
subgraph subGraph1["ArceOS Hypervisor Components"]
axvmconfig["axvmconfigEmulatedDeviceConfig"]
axaddrspace["axaddrspaceGuestPhysAddr"]
axdevice_base["axdevice_baseBaseDeviceOps"]
end
subgraph subGraph0["axdevice Implementation"]
Config["AxVmDeviceConfig"]
Devices["AxVmDevices"]
end
axaddrspace --> Devices
axdevice_base --> Devices
axerrno --> Devices
axvmconfig --> Config
cfg_if --> Config
log_crate --> Devices
memory_addr --> Devices
Dependency Details
| Component | Source | Purpose | Usage |
|---|---|---|---|
| EmulatedDeviceConfig | axvmconfig | Device configuration data | Stored inAxVmDeviceConfig.emu_configs |
| GuestPhysAddr | axaddrspace | Guest physical addressing | MMIO operation parameters |
| BaseDeviceOps | axdevice_base | Device operation interface | Stored asArc |
| AxResult | axerrno | Error handling | Return type forhandle_mmio_read |
Sources: Cargo.toml(L8 - L18) src/config.rs(L2) src/device.rs(L6 - L9) src/lib.rs(L11 - L13)
Current Implementation Status
The system architecture includes placeholder implementations for future device types:
Device Type Support (Planned Implementation)
The init method in AxVmDevices contains commented code indicating planned support for multiple device types:
- Console devices (
EmuDeviceTConsole) - VirtIO devices (
EmuDeviceTVirtioBlk,EmuDeviceTVirtioNet,EmuDeviceTVirtioConsole) - Interrupt controllers (
EmuDeviceTGicdV2,EmuDeviceTGICR) - IOMMU (
EmuDeviceTIOMMU) - Other specialized devices (
EmuDeviceTGPPT,EmuDeviceTICCSRE,EmuDeviceTSGIR,EmuDeviceTMeta)
Sources: src/device.rs(L30 - L54)
Core Components
Relevant source files
This page provides an overview of the main functional components that make up the axdevice system. It introduces the two primary subsystems: configuration management through AxVmDeviceConfig and device emulation through AxVmDevices. For detailed information about configuration management, see Configuration Management. For detailed information about device emulation functionality, see Device Emulation.
Component Overview
The axdevice crate is structured around two core components that work together to provide virtual machine device emulation capabilities:
| Component | Primary Type | Responsibility | File Location |
|---|---|---|---|
| Configuration Management | AxVmDeviceConfig | Stores and manages device configuration parameters | src/config.rs |
| Device Emulation | AxVmDevices | Handles MMIO operations and device lifecycle management | src/device.rs |
Configuration Management
The AxVmDeviceConfig struct serves as the central configuration container for all emulated devices in a virtual machine. It maintains a vector of EmulatedDeviceConfig objects that define the parameters for individual devices.
Key characteristics:
- Simple container structure with a single
emu_configsfield src/config.rs(L5 - L8) - Constructor function
new()for initialization src/config.rs(L13 - L15) - Depends on
axvmconfig::EmulatedDeviceConfigfor individual device configurations src/config.rs(L2)
Device Emulation
The AxVmDevices struct represents the runtime device management system for a virtual machine. It maintains collections of emulated devices and provides the interface for handling guest VM memory-mapped I/O operations.
Key characteristics:
- Maintains a vector of
Arc<dyn BaseDeviceOps>for thread-safe device access src/device.rs(L14) - Provides MMIO read/write handlers for guest VM interactions src/device.rs(L65 - L92)
- Implements device lookup by guest physical address src/device.rs(L57 - L62)
Component Architecture
Core Component Structure
flowchart TD
subgraph subGraph1["External Dependencies"]
EmulatedDeviceConfig["EmulatedDeviceConfigaxvmconfig"]
BaseDeviceOps["BaseDeviceOpsaxdevice_base"]
GuestPhysAddr["GuestPhysAddraxaddrspace"]
end
subgraph subGraph0["axdevice Core"]
Config["AxVmDeviceConfigconfig.rs:5"]
Devices["AxVmDevicesdevice.rs:12"]
EmuConfigs["emu_configs: Vec<EmulatedDeviceConfig>config.rs:7"]
EmuDevices["emu_devices: Vec<Arc<dyn BaseDeviceOps>>device.rs:14"]
end
Config --> Devices
Config --> EmuConfigs
Devices --> EmuDevices
Devices --> GuestPhysAddr
EmuConfigs --> EmulatedDeviceConfig
EmuDevices --> BaseDeviceOps
Sources: src/lib.rs(L18 - L19) src/config.rs(L5 - L8) src/device.rs(L12 - L16)
Component Interaction Flow
Initialization and Runtime Flow
Sources: src/config.rs(L13 - L15) src/device.rs(L21 - L28) src/device.rs(L65 - L92)
Public Interface
The axdevice crate exports both core components through its main library interface:
pub use config::AxVmDeviceConfig;
pub use device::AxVmDevices;
Key Public Methods:
| Component | Method | Purpose | Return Type |
|---|---|---|---|
| AxVmDeviceConfig | new(emu_configs) | Create configuration instance | Self |
| AxVmDevices | new(config) | Initialize device manager | Self |
| AxVmDevices | find_dev(ipa) | Locate device by address | Option<Arc |
| AxVmDevices | handle_mmio_read(addr, width) | Process guest read operations | AxResult |
| AxVmDevices | handle_mmio_write(addr, width, val) | Process guest write operations | () |
Sources: src/lib.rs(L18 - L19) src/device.rs(L57 - L92)
Implementation Status
The current implementation provides the foundational structure but has key areas marked for future development:
- Device Initialization: The
init()method contains commented-out code for device type dispatch src/device.rs(L31 - L54) - Device Type Support: Placeholder comments indicate planned support for multiple device types including console, VirtIO, and interrupt controllers src/device.rs(L34 - L47)
- Passthrough Devices: Architecture anticipates future support for passthrough devices src/device.rs(L15)
The MMIO handling functionality is fully implemented and operational, providing the runtime interface needed for guest VM device interactions.
Sources: src/device.rs(L31 - L54) src/device.rs(L15)
Configuration Management
Relevant source files
Purpose and Scope
This document covers the configuration management system in the axdevice crate, specifically focusing on how device configurations are defined, structured, and used to initialize emulated devices in the ArceOS hypervisor. The AxVmDeviceConfig structure serves as the primary interface for managing collections of emulated device configurations that will be instantiated by the device emulation system.
For information about the actual device emulation and MMIO operation handling, see Device Emulation. For broader system architecture context, see System Architecture.
Core Configuration Structure
The configuration management system is built around a simple but effective structure that aggregates individual device configurations into a manageable collection.
AxVmDeviceConfig Structure
The primary configuration container is AxVmDeviceConfig, which maintains a vector of EmulatedDeviceConfig objects imported from the axvmconfig crate.
Configuration Structure Analysis
| Component | Type | Purpose | Location |
|---|---|---|---|
| AxVmDeviceConfig | Struct | Container for multiple device configurations | src/config.rs5-8 |
| emu_configs | Vec | Collection of individual device configurations | src/config.rs7 |
| new() | Constructor | Creates new configuration instance | src/config.rs13-15 |
Sources: src/config.rs(L1 - L17)
Configuration Data Flow
The configuration system follows a straightforward data flow pattern where external configuration data is aggregated and then passed to the device management layer.
Configuration Lifecycle
sequenceDiagram
participant Client as Client
participant AxVmDeviceConfig as AxVmDeviceConfig
participant AxVmDevices as AxVmDevices
participant EmulatedDeviceConfig as EmulatedDeviceConfig
Client ->> AxVmDeviceConfig: "new(emu_configs)"
Note over AxVmDeviceConfig: "Store Vec<EmulatedDeviceConfig>"
AxVmDeviceConfig ->> Client: "return AxVmDeviceConfig instance"
Client ->> AxVmDevices: "new(config)"
AxVmDevices ->> AxVmDeviceConfig: "access emu_configs field"
loop "For each EmulatedDeviceConfig"
AxVmDevices ->> EmulatedDeviceConfig: "read device parameters"
AxVmDevices ->> AxVmDevices: "initialize emulated device"
end
Note over AxVmDevices: "Devices ready for MMIO operations"
Configuration Flow Steps
- Construction:
AxVmDeviceConfig::new()accepts a vector ofEmulatedDeviceConfigobjects - Storage: The configuration vector is stored in the
emu_configsfield - Consumption: Device management system accesses configurations during initialization
- Device Creation: Each configuration drives the creation of corresponding emulated devices
Sources: src/config.rs(L13 - L15) src/lib.rs(L18)
Module Integration
The configuration management integrates seamlessly with the broader axdevice crate structure and external dependencies.
Crate Integration Pattern
flowchart TD
subgraph subGraph4["Consumer Systems"]
AxVmDevices["AxVmDevices (device management)"]
ClientCode["Client Code"]
end
subgraph subGraph3["External Dependencies"]
AxVmConfigCrate["axvmconfig::EmulatedDeviceConfig"]
AllocCrate["alloc::vec::Vec"]
end
subgraph subGraph2["axdevice Crate"]
subgraph src/config.rs["src/config.rs"]
AxVmDeviceConfig["struct AxVmDeviceConfig"]
NewMethod["impl AxVmDeviceConfig::new()"]
end
subgraph src/lib.rs["src/lib.rs"]
LibExports["pub use config::AxVmDeviceConfig"]
ConfigMod["mod config"]
end
end
note1["File: src/lib.rs:15,18"]
note2["File: src/config.rs:2,7"]
AxVmDeviceConfig --> NewMethod
ConfigMod --> AxVmDeviceConfig
LibExports --> AxVmDeviceConfig
LibExports --> AxVmDevices
LibExports --> ClientCode
NewMethod --> AllocCrate
NewMethod --> AxVmConfigCrate
Integration Responsibilities
| Component | Responsibility | Implementation |
|---|---|---|
| Module Declaration | Expose config module | src/lib.rs15 |
| Public Export | MakeAxVmDeviceConfigavailable | src/lib.rs18 |
| Dependency Import | AccessEmulatedDeviceConfigtype | src/config.rs2 |
| Memory Management | UseVecfor dynamic collections | src/config.rs1 |
Sources: src/lib.rs(L15 - L19) src/config.rs(L1 - L2)
Configuration Usage Patterns
The design supports common configuration management patterns expected in a hypervisor device emulation system.
Typical Usage Workflow
flowchart TD
subgraph subGraph2["Device Management Handoff"]
PassToDevices["Pass config to AxVmDevices"]
AccessConfigs["AxVmDevices accesses emu_configs"]
InitDevices["Initialize individual emulated devices"]
end
subgraph subGraph1["AxVmDeviceConfig Creation"]
CallNew["AxVmDeviceConfig::new(emu_configs)"]
StoreConfigs["Store configurations in emu_configs field"]
end
subgraph subGraph0["Configuration Preparation"]
LoadConfigs["Load EmulatedDeviceConfig objects"]
CreateVector["Create Vec"]
end
Start["System Initialization"]
End["Ready for MMIO Operations"]
note1["Constructor: src/config.rs:13-15"]
note2["Field access for device creation"]
AccessConfigs --> InitDevices
CallNew --> StoreConfigs
CreateVector --> CallNew
InitDevices --> End
LoadConfigs --> CreateVector
PassToDevices --> AccessConfigs
Start --> LoadConfigs
StoreConfigs --> PassToDevices
Configuration Management Characteristics
- Simplicity: Minimal wrapper around configuration vector with single constructor
- Flexibility: Accepts any number of device configurations through
Veccollection - Integration: Seamless handoff to device management layer through public field access
- Memory Safety: Uses
alloc::vec::Vecfor safe dynamic memory management inno_stdenvironment
Sources: src/config.rs(L5 - L16) src/lib.rs(L11 - L18)
Error Handling and Validation
The current configuration management implementation follows a minimal approach with validation delegated to consuming systems and external configuration sources.
Validation Strategy
| Validation Level | Responsibility | Implementation |
|---|---|---|
| Type Safety | Rust compiler | Vec |
| Content Validation | axvmconfigcrate | EmulatedDeviceConfiginternal validation |
| Device Creation | AxVmDevices | Validation during device instantiation |
| Runtime Checks | Device implementations | MMIO operation validation |
The simple structure allows higher-level systems to implement appropriate validation and error handling strategies while maintaining a clean separation of concerns.
Sources: src/config.rs(L1 - L17)
Device Emulation
Relevant source files
Purpose and Scope
This document covers the device emulation system implemented by the AxVmDevices struct, which serves as the central orchestrator for all emulated devices within a virtual machine. The system handles Memory-Mapped I/O (MMIO) operations from guest VMs, manages device lifecycles, and routes operations to appropriate device implementations through the BaseDeviceOps trait interface.
For information about device configuration management, see Configuration Management. For broader system integration details, see ArceOS Ecosystem Integration.
AxVmDevices Architecture
The AxVmDevices struct acts as the primary interface between guest virtual machines and emulated devices. It maintains a collection of device implementations and provides unified access to MMIO operations.
Core Structure
flowchart TD
subgraph subGraph2["Configuration Input"]
H["AxVmDeviceConfig"]
I["emu_configs: Vec"]
end
subgraph Dependencies["Dependencies"]
D["axaddrspace::GuestPhysAddr"]
E["axdevice_base::BaseDeviceOps"]
F["axerrno::AxResult"]
G["axvmconfig::EmulatedDeviceConfig"]
end
subgraph subGraph0["AxVmDevices Structure"]
A["AxVmDevices"]
B["emu_devices: Vec>"]
C["TODO: passthrough devices"]
end
A --> B
A --> C
A --> D
A --> E
A --> F
H --> A
H --> I
I --> A
Sources: src/device.rs(L1 - L16)
The AxVmDevices struct currently focuses on emulated devices through the emu_devices field, with placeholders for future passthrough device support.
Device Collection Management
flowchart TD
subgraph subGraph1["Trait Interface"]
E["BaseDeviceOps"]
F["address_range()"]
G["handle_read()"]
H["handle_write()"]
end
subgraph subGraph0["Device Storage"]
A["Vec>"]
B["Arc"]
C["Arc"]
D["Arc"]
end
A --> B
A --> C
A --> D
B --> E
C --> E
D --> E
E --> F
E --> G
E --> H
Sources: src/device.rs(L12 - L16)
Device Lifecycle Management
Initialization Process
The AxVmDevices initialization follows a two-phase approach: structure creation and device initialization.
sequenceDiagram
participant AxVmDeviceConfig as "AxVmDeviceConfig"
participant AxVmDevices as "AxVmDevices"
participant EmulatedDeviceConfig as "EmulatedDeviceConfig"
participant BaseDeviceOps as "BaseDeviceOps"
Note over AxVmDeviceConfig,BaseDeviceOps: Initialization Phase
AxVmDeviceConfig ->> AxVmDevices: "new(config)"
AxVmDevices ->> AxVmDevices: "Create empty emu_devices Vec"
AxVmDevices ->> AxVmDevices: "init(&mut self, &config.emu_configs)"
Note over AxVmDevices,BaseDeviceOps: Device Creation (TODO)
loop "For each EmulatedDeviceConfig"
AxVmDevices ->> EmulatedDeviceConfig: "Read device configuration"
EmulatedDeviceConfig ->> BaseDeviceOps: "Create device instance"
BaseDeviceOps ->> AxVmDevices: "Add to emu_devices"
end
Sources: src/device.rs(L19 - L28) src/device.rs(L30 - L54)
The new() method creates an empty device collection and delegates to the init() method for device-specific initialization. Currently, the init() method contains placeholder code with TODO comments indicating future device type handling.
Device Type Enumeration
The commented code in init() reveals the planned device type architecture:
| Device Type | Description |
|---|---|
| EmuDeviceTConsole | Console device emulation |
| EmuDeviceTGicdV2 | GIC distributor v2 |
| EmuDeviceTGPPT | General Purpose Physical Timer |
| EmuDeviceTVirtioBlk | VirtIO block device |
| EmuDeviceTVirtioNet | VirtIO network device |
| EmuDeviceTVirtioConsole | VirtIO console device |
| EmuDeviceTIOMMU | I/O Memory Management Unit |
| EmuDeviceTICCSRE | Interrupt Controller System Register Enable |
| EmuDeviceTSGIR | Software Generated Interrupt Register |
| EmuDeviceTGICR | GIC redistributor |
| EmuDeviceTMeta | Metadata device |
Sources: src/device.rs(L34 - L46)
MMIO Operation Handling
Device Lookup Mechanism
The find_dev() method provides address-based device resolution using the BaseDeviceOps trait's address_range() method.
flowchart TD
subgraph subGraph0["Device Lookup Process"]
A["find_dev(ipa: GuestPhysAddr)"]
B["Iterate emu_devices"]
C["dev.address_range().contains(ipa)"]
D["Address Match?"]
E["Return Some(Arc)"]
F["Continue iteration"]
G["More devices?"]
H["Return None"]
end
A --> B
B --> C
C --> D
D --> E
D --> F
F --> G
G --> B
G --> H
Sources: src/device.rs(L56 - L62)
MMIO Read Operations
The handle_mmio_read() method processes guest VM read requests and returns data from the appropriate device.
sequenceDiagram
participant GuestVM as "Guest VM"
participant AxVmDevices as "AxVmDevices"
participant Device as "Device"
GuestVM ->> AxVmDevices: "handle_mmio_read(addr, width)"
AxVmDevices ->> AxVmDevices: "find_dev(addr)"
alt "Device found"
AxVmDevices ->> AxVmDevices: "Log operation info"
AxVmDevices ->> Device: "handle_read(addr, width)"
Device ->> AxVmDevices: "Return AxResult<usize>"
AxVmDevices ->> GuestVM: "Return value"
else "No device found"
AxVmDevices ->> AxVmDevices: "panic!(no emul handler)"
end
Sources: src/device.rs(L64 - L75)
The method includes comprehensive logging that outputs the device's address range and the specific IPA (Intermediate Physical Address) being accessed.
MMIO Write Operations
The handle_mmio_write() method processes guest VM write requests to device registers.
sequenceDiagram
participant GuestVM as "Guest VM"
participant AxVmDevices as "AxVmDevices"
participant Device as "Device"
GuestVM ->> AxVmDevices: "handle_mmio_write(addr, width, val)"
AxVmDevices ->> AxVmDevices: "find_dev(addr)"
alt "Device found"
AxVmDevices ->> AxVmDevices: "Log operation info"
AxVmDevices ->> Device: "handle_write(addr, width, val)"
Device ->> AxVmDevices: "Operation complete"
AxVmDevices ->> GuestVM: "Return"
else "No device found"
AxVmDevices ->> AxVmDevices: "panic!(no emul handler)"
end
Sources: src/device.rs(L77 - L93)
Both MMIO operations follow a similar pattern: device lookup, logging, delegation to the device implementation, and error handling via panic for unmapped addresses.
Address Space Integration
Guest Physical Address Handling
The system uses GuestPhysAddr from the axaddrspace crate to represent guest physical memory addresses, ensuring type safety in address operations.
flowchart TD
subgraph subGraph1["Device Address Ranges"]
H["Device 1 Range"]
I["Device 2 Range"]
J["Device N Range"]
end
subgraph subGraph0["Address Resolution"]
A["GuestPhysAddr"]
B["Device Lookup"]
C["BaseDeviceOps::address_range()"]
D["AddressRange::contains()"]
E["Match Found?"]
F["Route to Device"]
G["Panic: No Handler"]
end
A --> B
B --> C
C --> D
C --> H
C --> I
C --> J
D --> E
E --> F
E --> G
Sources: src/device.rs(L6) src/device.rs(L57 - L62)
Error Handling Strategy
The current implementation uses panic-based error handling for unmapped addresses, which provides immediate feedback during development but may require more graceful handling in production environments.
flowchart TD A["MMIO Request"] B["find_dev(addr)"] C["Device Found?"] D["Execute Operation"] E["panic!(no emul handler)"] F["Log Operation"] G["Call BaseDeviceOps Method"] H["Return Result"] A --> B B --> C C --> D C --> E D --> F F --> G G --> H
Sources: src/device.rs(L74) src/device.rs(L88 - L91)
Integration with BaseDeviceOps
The AxVmDevices system relies entirely on the BaseDeviceOps trait for device-specific functionality, creating a clean separation between device management and device implementation.
Trait Method Usage
| Method | Purpose | Usage in AxVmDevices |
|---|---|---|
| address_range() | Define device memory mapping | Used infind_dev()for address resolution |
| handle_read() | Process read operations | Called fromhandle_mmio_read() |
| handle_write() | Process write operations | Called fromhandle_mmio_write() |
Sources: src/device.rs(L60) src/device.rs(L72) src/device.rs(L85)
Device Abstraction Benefits
flowchart TD
subgraph subGraph2["Device Implementations"]
G["Console Device"]
H["VirtIO Devices"]
I["Interrupt Controllers"]
J["Custom Devices"]
end
subgraph subGraph1["BaseDeviceOps (Device Abstraction)"]
D["address_range()"]
E["handle_read()"]
F["handle_write()"]
end
subgraph subGraph0["AxVmDevices (Device Manager)"]
A["Unified MMIO Interface"]
B["Address-based Routing"]
C["Lifecycle Management"]
end
A --> D
B --> D
C --> D
D --> G
D --> H
D --> I
D --> J
E --> G
E --> H
E --> I
E --> J
F --> G
F --> H
F --> I
F --> J
Sources: src/device.rs(L7) src/device.rs(L14)
This architecture enables the AxVmDevices system to remain device-agnostic while providing consistent MMIO handling across all emulated device types.
ArceOS Ecosystem Integration
Relevant source files
Purpose and Scope
This document explains how the axdevice crate integrates with the broader ArceOS hypervisor ecosystem. It covers the modular architecture approach, dependency relationships with other ArceOS crates, and the layered design that enables extensible device emulation within virtual machines.
For information about the internal architecture of axdevice components, see System Architecture. For details about device configuration and emulation implementation, see Core Components.
ArceOS Hypervisor Ecosystem Structure
The axdevice crate operates within a carefully structured ecosystem of ArceOS hypervisor components, each serving specific responsibilities in the virtualization stack.
Ecosystem Dependency Graph
flowchart TD
subgraph subGraph4["External Dependencies"]
log["logLogging Framework"]
cfg_if["cfg-ifConditional Compilation"]
end
subgraph subGraph3["System Foundation Dependencies"]
axerrno["axerrnoError Type Definitions"]
memory_addr["memory_addrMemory Address Types"]
end
subgraph subGraph2["Device Abstraction Dependencies"]
axdevice_base["axdevice_baseDevice Interface Traits"]
axdevice_crates["axdevice_cratesDevice Implementation Framework"]
end
subgraph subGraph1["VM Management Dependencies"]
axvmconfig["axvmconfigVM Configuration"]
axaddrspace["axaddrspaceAddress Space Management"]
end
subgraph subGraph0["Device Management Layer"]
axdevice["axdeviceDevice Orchestration"]
end
axaddrspace --> memory_addr
axdevice --> axaddrspace
axdevice --> axdevice_base
axdevice --> axerrno
axdevice --> axvmconfig
axdevice --> cfg_if
axdevice --> log
axdevice --> memory_addr
axdevice_base --> axdevice_crates
axvmconfig --> axerrno
Sources: Cargo.toml(L1 - L19)
Dependency Architecture
ArceOS Hypervisor-Specific Dependencies
The axdevice crate depends on three key ArceOS hypervisor components, each maintained in separate Git repositories to support modular development:
| Dependency | Repository | Role in axdevice |
|---|---|---|
| axvmconfig | github.com/arceos-hypervisor/axvmconfig.git | Provides VM configuration structures that define device parameters |
| axaddrspace | github.com/arceos-hypervisor/axaddrspace.git | Manages guest physical address space mappings for device MMIO regions |
| axdevice_base | github.com/arceos-hypervisor/axdevice_crates.git | DefinesBaseDeviceOpstrait and device abstraction interfaces |
ArceOS System Foundation Dependencies
The crate relies on foundational ArceOS system components for basic functionality:
axerrno: Provides standardized error types (AxError,AxResult) used throughout the hypervisor ecosystemmemory_addr: Supplies memory address types (GuestPhysAddr,HostVirtAddr) for address space operations
External Ecosystem Dependencies
Standard Rust ecosystem crates provide utility functionality:
log: Enables structured logging for debugging and monitoring device operationscfg-if: Supports conditional compilation for platform-specific device implementations
Sources: Cargo.toml(L8 - L18)
Integration Patterns and Code Entities
Component Integration Flow
flowchart TD
subgraph subGraph4["Error Handling Integration"]
AxError["AxError"]
AxResult["AxResult"]
end
subgraph subGraph3["Address Space Integration"]
GuestPhysAddr["GuestPhysAddr"]
HostVirtAddr["HostVirtAddr"]
address_mapping["Address Mapping"]
end
subgraph subGraph2["Device Implementation Phase"]
BaseDeviceOps["BaseDeviceOps trait"]
device_implementations["Device Implementations"]
end
subgraph subGraph1["Device Management Phase"]
AxVmDevices["AxVmDevices"]
find_dev["find_dev()"]
handle_read["handle_read()"]
handle_write["handle_write()"]
end
subgraph subGraph0["Configuration Phase"]
AxVmDeviceConfig["AxVmDeviceConfig"]
EmulatedDeviceConfig["EmulatedDeviceConfig"]
end
AxError --> AxResult
AxResult --> handle_read
AxResult --> handle_write
AxVmDeviceConfig --> EmulatedDeviceConfig
AxVmDevices --> find_dev
BaseDeviceOps --> device_implementations
EmulatedDeviceConfig --> AxVmDevices
GuestPhysAddr --> find_dev
HostVirtAddr --> address_mapping
address_mapping --> AxVmDevices
find_dev --> handle_read
find_dev --> handle_write
handle_read --> BaseDeviceOps
handle_write --> BaseDeviceOps
Trait and Type Integration Points
The ecosystem integration occurs through specific code entities that provide standardized interfaces:
Configuration Integration:
AxVmDeviceConfigstructure coordinates withaxvmconfigcrate for VM-level device configurationEmulatedDeviceConfiginstances define individual device parameters within the VM configuration
Device Operation Integration:
BaseDeviceOpstrait fromaxdevice_basedefines the interface contract for all emulated devicesfind_dev()method usesGuestPhysAddrfrommemory_addrcrate for address-based device lookuphandle_read()andhandle_write()methods returnAxResult<T>using error types fromaxerrno
Address Space Integration:
GuestPhysAddrandHostVirtAddrtypes frommemory_addrenable precise memory mapping- Integration with
axaddrspacemanages guest physical memory regions assigned to devices
Sources: Cargo.toml(L13 - L18)
Modular Design Benefits
Repository Separation Strategy
The ArceOS hypervisor ecosystem uses a multi-repository approach that provides several architectural advantages:
Development Independence:
- Each component (
axvmconfig,axaddrspace,axdevice_base) can evolve independently - Version pinning through Git dependencies allows controlled integration of component updates
- Teams can work on different aspects of the hypervisor without merge conflicts
Dependency Management:
axdevicespecifiesdefault-features = falseforaxvmconfig, enabling minimal dependency inclusion- Git-based dependencies allow using cutting-edge features before crates.io publication
- Clear separation of concerns prevents circular dependencies between components
Extensibility Framework:
- New device types can be added by implementing
BaseDeviceOpswithout modifying coreaxdevicecode - Additional VM configuration options can be added to
axvmconfigwithout breaking existing device implementations - Address space management improvements in
axaddrspacebenefit all device emulation automatically
Integration Testing and Compatibility
The modular approach enables comprehensive integration testing:
- Each component maintains its own test suite for isolated functionality verification
- Integration tests can verify compatibility between specific versions of ecosystem components
- CI/CD pipelines can test multiple version combinations to ensure ecosystem stability
Sources: Cargo.toml(L16 - L18)
Development Guide
Relevant source files
This document provides comprehensive information for developers working with the axdevice crate, covering build processes, testing procedures, CI/CD workflows, and contribution guidelines. This guide focuses on the practical aspects of developing, building, and maintaining the device emulation components within the ArceOS hypervisor ecosystem.
For information about the system architecture and how components interact, see System Architecture. For details about specific device implementations and configuration management, see Core Components.
Development Environment Setup
The axdevice crate requires a Rust nightly toolchain with specific target support for cross-platform compatibility across multiple architectures supported by the ArceOS hypervisor.
Required Toolchain Components
flowchart TD
subgraph subGraph1["Target Architectures"]
E["x86_64-unknown-linux-gnu"]
F["x86_64-unknown-none"]
G["riscv64gc-unknown-none-elf"]
H["aarch64-unknown-none-softfloat"]
end
subgraph subGraph0["Rust Toolchain Requirements"]
A["rust-toolchain: nightly"]
B["rust-src component"]
C["clippy component"]
D["rustfmt component"]
end
A --> B
A --> C
A --> D
A --> E
A --> F
A --> G
A --> H
Development Environment Architecture
The development environment supports both hosted Linux development and bare-metal hypervisor targets across multiple CPU architectures.
Sources: .github/workflows/ci.yml(L11 - L19)
Target Platform Support
| Target | Purpose | Usage Context |
|---|---|---|
| x86_64-unknown-linux-gnu | Host development and testing | Unit tests and development builds |
| x86_64-unknown-none | Bare-metal x86_64 hypervisor | Production hypervisor deployment |
| riscv64gc-unknown-none-elf | RISC-V bare-metal | RISC-V based hypervisor systems |
| aarch64-unknown-none-softfloat | ARM64 bare-metal | ARM-based hypervisor platforms |
Sources: .github/workflows/ci.yml(L12)
Build and Test Workflow
The development workflow follows a standardized process that ensures code quality and cross-platform compatibility through automated checks and builds.
Development Workflow Diagram
sequenceDiagram
participant Developer as "Developer"
participant LocalEnvironment as "Local Environment"
participant GitHubActionsCI as "GitHub Actions CI"
participant GitHubPages as "GitHub Pages"
Developer ->> LocalEnvironment: "cargo fmt --all -- --check"
LocalEnvironment -->> Developer: "Format validation"
Developer ->> LocalEnvironment: "cargo clippy --all-features"
LocalEnvironment -->> Developer: "Linting results"
Developer ->> LocalEnvironment: "cargo build --all-features"
LocalEnvironment -->> Developer: "Build success"
Developer ->> LocalEnvironment: "cargo test"
LocalEnvironment -->> Developer: "Test results"
Developer ->> GitHubActionsCI: "git push"
GitHubActionsCI ->> GitHubActionsCI: "Format check (cargo fmt)"
GitHubActionsCI ->> GitHubActionsCI: "Clippy analysis"
GitHubActionsCI ->> GitHubActionsCI: "Multi-target builds"
GitHubActionsCI ->> GitHubActionsCI: "Unit tests (x86_64-linux)"
GitHubActionsCI ->> GitHubActionsCI: "Documentation build"
GitHubActionsCI ->> GitHubPages: "Deploy docs (main branch)"
Complete Development and CI Workflow
Sources: .github/workflows/ci.yml(L23 - L30) .github/workflows/ci.yml(L44 - L55)
Local Development Commands
The standard development cycle involves these key commands:
# Code formatting verification
cargo fmt --all -- --check
# Static analysis with custom flags
cargo clippy --target <TARGET> --all-features -- -A clippy::new_without_default
# Cross-platform builds
cargo build --target <TARGET> --all-features
# Unit testing (Linux host only)
cargo test --target x86_64-unknown-linux-gnu -- --nocapture
Sources: .github/workflows/ci.yml(L23 - L30)
CI/CD Pipeline Architecture
The continuous integration system uses GitHub Actions with a matrix strategy to ensure compatibility across all supported target platforms.
CI Pipeline Flow
flowchart TD
subgraph subGraph3["Documentation Job"]
K["cargo doc --no-deps --all-features"]
L["RUSTDOCFLAGS validation"]
M["GitHub Pages deployment"]
end
subgraph subGraph2["Validation Steps"]
F["rustc --version --verbose"]
G["cargo fmt --all -- --check"]
H["cargo clippy (per target)"]
I["cargo build (per target)"]
J["cargo test (x86_64-linux only)"]
end
subgraph subGraph1["CI Job Matrix"]
C["ubuntu-latest runner"]
D["nightly toolchain"]
E["4 target platforms"]
end
subgraph subGraph0["Trigger Events"]
A["push events"]
B["pull_request events"]
end
A --> C
B --> C
C --> D
C --> K
D --> E
E --> F
F --> G
G --> H
H --> I
I --> J
K --> L
L --> M
CI/CD Pipeline Architecture
Sources: .github/workflows/ci.yml(L1 - L31) .github/workflows/ci.yml(L32 - L56)
Matrix Strategy Configuration
The CI system uses a fail-fast: false strategy to ensure all target platforms are tested even if one fails:
strategy:
fail-fast: false
matrix:
rust-toolchain: [nightly]
targets: [x86_64-unknown-linux-gnu, x86_64-unknown-none, riscv64gc-unknown-none-elf, aarch64-unknown-none-softfloat]
Sources: .github/workflows/ci.yml(L8 - L12)
Code Quality Standards
The project enforces strict code quality standards through automated tooling integrated into the CI pipeline.
Linting Configuration
The clippy configuration suppresses specific warnings that are acceptable for the hypervisor context:
cargo clippy --target ${{ matrix.targets }} --all-features -- -A clippy::new_without_default
The -A clippy::new_without_default flag allows new() methods without corresponding Default implementations, which is common in device emulation where default configurations may not be meaningful.
Sources: .github/workflows/ci.yml(L25)
Documentation Standards
Documentation builds use strict flags to ensure comprehensive documentation quality:
RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs
These flags treat broken documentation links and missing documentation as compilation errors, ensuring complete and accurate documentation.
Sources: .github/workflows/ci.yml(L40)
Documentation Generation and Deployment
The documentation system automatically generates and deploys API documentation to GitHub Pages for the main branch.
Documentation Build Process
flowchart TD
subgraph subGraph1["Deployment Conditions"]
D["Branch == default branch"]
E["JamesIves/github-pages-deploy-action@v4"]
F["single-commit: true"]
G["branch: gh-pages"]
end
subgraph subGraph0["Documentation Build"]
A["cargo doc --no-deps --all-features"]
B["Generate index.html redirect"]
C["Extract crate name from cargo tree"]
end
A --> B
B --> C
C --> D
D --> E
E --> F
F --> G
Documentation Generation and Deployment Process
The documentation build creates a redirect page that automatically navigates to the main crate documentation:
printf '<meta http-equiv="refresh" content="0;url=%s/index.html">' $(cargo tree | head -1 | cut -d' ' -f1) > target/doc/index.html
Sources: .github/workflows/ci.yml(L44 - L55)
Project Structure and Ignored Files
The development environment excludes specific files and directories that are generated during the build process or are environment-specific.
Ignored File Categories
| Category | Files | Purpose |
|---|---|---|
| Build Artifacts | /target,.asm,.img,.bin,.elf | Compiled binaries and assembly output |
| Runtime Files | actual.out,qemu.log | Test execution and emulation logs |
| Development Tools | rusty-tags.vi,/.vscode,.DS_Store | Editor and IDE configurations |
| Dependencies | Cargo.lock | Lock file excluded because axdevice is a library |
Sources: .gitignore(L1 - L18)
Contributing Guidelines
Pre-submission Checklist
Before submitting code changes, ensure all of the following pass locally:
- Code Formatting:
cargo fmt --all -- --check - Static Analysis:
cargo clippy --all-features -- -A clippy::new_without_default - Cross-platform Builds: Test builds on all target platforms
- Unit Tests:
cargo test --target x86_64-unknown-linux-gnu -- --nocapture - Documentation:
cargo doc --no-deps --all-features
Target Platform Testing
All changes must build successfully on the four supported target platforms. The CI system will verify this automatically, but local testing on critical platforms is recommended:
# Test core functionality on Linux host
cargo test --target x86_64-unknown-linux-gnu
# Verify bare-metal builds
cargo build --target x86_64-unknown-none --all-features
cargo build --target riscv64gc-unknown-none-elf --all-features
cargo build --target aarch64-unknown-none-softfloat --all-features
Sources: .github/workflows/ci.yml(L27 - L30)
Overview
Relevant source files
Purpose and Scope
The x86_vcpu library provides a complete Intel VMX-based virtualization framework for x86_64 architectures. This library implements virtual CPU management, memory virtualization through Extended Page Tables (EPT), and hardware abstraction for building hypervisors.
The library focuses specifically on Intel VMX technology and provides the core virtualization primitives needed to create and manage virtual machines. For AMD SVM support, see the amd feature flag documented in Project Configuration. For build and development processes, see Development and Configuration.
Sources: src/lib.rs(L1 - L31) README.md(L1 - L29) Cargo.toml(L1 - L28)
System Architecture
The x86_vcpu library is structured around several core subsystems that work together to provide complete virtualization capabilities:
Core System Components
flowchart TD
subgraph ExportedAPI["Public API"]
VmxExports["VmxExitInfoVmxExitReasonVmxInterruptInfoVmxIoExitInfo"]
GeneralExports["GeneralRegistersGuestPageWalkInfohas_hardware_support"]
end
subgraph ExternalInterfaces["External Interfaces"]
PhysFrameIf["PhysFrameIf TraitHardware Abstraction"]
Dependencies["External Dependenciesx86, x86_64, axvcpu"]
end
subgraph LibraryCore["x86_vcpu Library Core"]
LibRS["lib.rsMain Entry Point"]
subgraph MemoryMgmt["Memory Management"]
EPTMod["ept.rsExtended Page Tables"]
FrameMod["frame.rsPhysical Frame Management"]
GuestPageWalk["GuestPageWalkInfoGuest Memory Virtualization"]
end
end
subgraph SupportSystems["Supporting Systems"]
RegsMod["regs.rsGeneralRegisters"]
MsrMod["msr.rsMSR Access"]
VmxDefs["vmx/definitions.rsVMX Types"]
VmxInstr["vmx/instructions.rsVMX Instructions"]
VmxVcpu["VmxArchVCpuVirtual CPU Implementation"]
VmxPerCpu["VmxArchPerCpuStatePer-CPU VMX State"]
end
subgraph VMXEngine["VMX Virtualization Engine"]
RegsMod["regs.rsGeneralRegisters"]
MsrMod["msr.rsMSR Access"]
VmxMod["vmx/mod.rsVMX Module"]
VmxVcpu["VmxArchVCpuVirtual CPU Implementation"]
VmxPerCpu["VmxArchPerCpuStatePer-CPU VMX State"]
end
EPTMod --> GuestPageWalk
FrameMod --> PhysFrameIf
PhysFrameIf --> Dependencies
VmxPerCpu --> VmxMod
VmxVcpu --> VmxMod
Sources: src/lib.rs(L18 - L31) Cargo.toml(L6 - L22)
VMX Feature Compilation Model
flowchart TD
subgraph PublicAPI["Exported Types"]
VmxTypes["VmxExitInfoVmxExitReasonVmxInterruptInfoVmxIoExitInfo"]
CommonTypes["GeneralRegistersGuestPageWalkInfohas_hardware_support"]
end
subgraph ConditionalCode["Conditional Implementation"]
VmxImpl["VMX ImplementationVmxArchVCpuVmxArchPerCpuState"]
VendorAlias["vender aliaspoints to active implementation"]
end
subgraph FeatureGates["Feature-Based Compilation"]
DefaultFeature["default = ['vmx']"]
VmxFeature["vmx feature"]
AmdFeature["amd feature"]
end
DefaultFeature --> VmxFeature
VendorAlias --> VmxTypes
VmxFeature --> VmxImpl
VmxImpl --> CommonTypes
VmxImpl --> VendorAlias
Sources: src/lib.rs(L18 - L27) Cargo.toml(L24 - L27)
Key Components
VMX Virtualization Engine
The core of the library is the VMX virtualization engine, which provides complete Intel VMX support. The VmxArchVCpu structure implements the virtual CPU interface defined by the axvcpu crate, handling VM entry/exit, guest state management, and hardware virtualization features.
For detailed information about VMX components, see VMX Virtualization Engine.
Memory Management
The memory management subsystem handles both host physical memory allocation and guest memory virtualization. The PhysFrameIf trait provides hardware abstraction for physical frame allocation, while the EPT system manages guest virtual-to-physical memory translation.
For memory management details, see Memory Management.
Hardware Abstraction
The library defines clear interfaces for hardware abstraction through the PhysFrameIf trait, allowing integration with different underlying memory management systems.
Interface Requirements
PhysFrameIf Implementation
Users must implement the PhysFrameIf trait to provide physical memory management:
| Method | Purpose |
|---|---|
| alloc_frame() | Allocate a 4KB physical memory frame |
| dealloc_frame(paddr) | Deallocate a physical memory frame |
| phys_to_virt(paddr) | Convert physical address to virtual address |
The implementation uses the crate_interface mechanism for dependency injection, as shown in README.md(L13 - L29)
Sources: README.md(L7 - L29) src/lib.rs(L1 - L6)
Dependency Integration
The library integrates with several external crates:
| Crate Category | Dependencies | Purpose |
|---|---|---|
| Low-level x86 | x86,x86_64,raw-cpuid | Hardware instruction and register access |
| Hypervisor Framework | axvcpu,axaddrspace | Virtual CPU and address space interfaces |
| Utility | bitflags,bit_field,memory_addr | Data structure manipulation |
Sources: Cargo.toml(L6 - L22)
Implementation Approach
The library uses conditional compilation to support different virtualization technologies. Currently, Intel VMX is the primary implementation, with the vmx feature enabled by default. The architecture allows for future AMD SVM support through the amd feature flag.
The modular design separates concerns into distinct subsystems:
- VMX-specific code is isolated in the
vmxmodule - Memory management provides hardware-agnostic abstractions
- Supporting systems offer reusable components for register and MSR access
For specific implementation details of each subsystem, refer to the corresponding sections: Virtual CPU Management, Physical Frame Management, and Supporting Systems.
Sources: src/lib.rs(L18 - L31) Cargo.toml(L24 - L27)
VMX Virtualization Engine
Relevant source files
The VMX Virtualization Engine forms the core of the x86_vcpu hypervisor library, implementing Intel's VMX (Virtual Machine Extensions) technology to enable hardware-assisted virtualization. This system provides the fundamental infrastructure for creating, managing, and executing virtual CPUs using Intel's VT-x capabilities.
For detailed information about physical memory management, see Physical Frame Management. For register and MSR access patterns, see Supporting Systems.
Architecture Overview
The VMX Virtualization Engine consists of several interconnected components that work together to provide complete virtualization functionality. The system is built around the VmxVcpu structure, which represents a virtual CPU and manages the complete virtualization lifecycle.
flowchart TD
subgraph subGraph4["Guest State Management"]
GuestRegs["GeneralRegisters"]
XState["XStateExtended Processor State"]
PendingEvents["VecDeque<pending_events>"]
end
subgraph subGraph3["Per-CPU State"]
PerCpuState["VmxPerCpuState(vmx/percpu.rs)"]
HwEnable["hardware_enable()"]
HwDisable["hardware_disable()"]
end
subgraph subGraph2["VMX Data Management"]
VmxRegion["VmxRegionVMCS Memory"]
IOBitmap["IOBitmapPort Interception"]
MsrBitmap["MsrBitmapMSR Control"]
VmcsFields["VMCS Field Access(vmx/vmcs.rs)"]
end
subgraph subGraph1["Core Virtualization Engine"]
VmxVcpu["VmxVcpu<H>(vmx/vcpu.rs)"]
VmExecution["VM Executionvmx_launch/vmx_resume"]
VmExitHandler["VM Exit Handlerbuiltin_vmexit_handler()"]
end
subgraph subGraph0["VMX Module Interface"]
VmxMod["VmxModFacade(vmx/mod.rs)"]
HwSupport["has_hardware_support()"]
RevisionId["read_vmcs_revision_id()"]
end
PerCpuState --> HwDisable
PerCpuState --> HwEnable
VmExecution --> VmExitHandler
VmxMod --> PerCpuState
VmxMod --> VmxVcpu
VmxVcpu --> GuestRegs
VmxVcpu --> IOBitmap
VmxVcpu --> MsrBitmap
VmxVcpu --> PendingEvents
VmxVcpu --> VmcsFields
VmxVcpu --> VmxRegion
VmxVcpu --> XState
Sources: src/vmx/mod.rs(L1 - L36) src/vmx/vcpu.rs(L143 - L159)
Core Components and Interactions
The VMX engine operates through a collection of specialized components that handle different aspects of virtualization. The central VmxVcpu structure coordinates these components to provide a complete virtual machine implementation.
flowchart TD
subgraph subGraph3["VMCS Management"]
VmcsSetup["setup_vmcs_host()setup_vmcs_guest()setup_vmcs_control()"]
VmcsFields["VMCS Field AccessVmcsGuestNW::RIPVmcsControl32::PINBASED_EXEC_CONTROLSVmcsHost64::IA32_PAT"]
end
subgraph subGraph2["VM Exit Handling"]
ExitInfo["exit_info() -> VmxExitInfo"]
BuiltinHandler["builtin_vmexit_handler()"]
ExitTypes["Exit Types- INTERRUPT_WINDOW- PREEMPTION_TIMER- XSETBV- CR_ACCESS- CPUID"]
end
subgraph subGraph1["VM Lifecycle Operations"]
NewVcpu["new() -> AxResult<Self>"]
SetupVcpu["setup(ept_root, entry)"]
BindVcpu["bind_to_current_processor()"]
RunVcpu["inner_run() -> Option<VmxExitInfo>"]
UnbindVcpu["unbind_from_current_processor()"]
end
subgraph subGraph0["VmxVcpu Structure"]
CoreFields["Core Fieldsguest_regs: GeneralRegistershost_stack_top: u64launched: bool"]
DataStructs["Data Structuresvmcs: VmxRegion<H>io_bitmap: IOBitmap<H>msr_bitmap: MsrBitmap<H>"]
StateFields["State Managementpending_events: VecDequexstate: XStateentry: Option<GuestPhysAddr>ept_root: Option<HostPhysAddr>"]
end
BuiltinHandler --> ExitTypes
CoreFields --> NewVcpu
DataStructs --> SetupVcpu
ExitInfo --> BuiltinHandler
RunVcpu --> ExitInfo
SetupVcpu --> VmcsSetup
StateFields --> RunVcpu
VmcsSetup --> VmcsFields
Sources: src/vmx/vcpu.rs(L144 - L159) src/vmx/vcpu.rs(L238 - L280) src/vmx/vcpu.rs(L890 - L906)
VM Execution Lifecycle
The virtual machine execution follows a well-defined lifecycle that involves hardware initialization, VMCS configuration, guest execution, and VM exit handling. This process is managed through careful coordination between the hypervisor and VMX hardware.
Sources: src/vmx/vcpu.rs(L162 - L180) src/vmx/vcpu.rs(L238 - L280) src/vmx/vcpu.rs(L822 - L856)
Hardware Support and Abstraction
The VMX engine provides hardware abstraction through well-defined interfaces that detect VMX capabilities and manage processor-specific features. The system validates hardware support and configures VMX features based on detected capabilities.
| Component | Purpose | Key Functions |
|---|---|---|
| Hardware Detection | Validate VMX support | has_hardware_support() |
| VMCS Revision | Get processor VMCS version | read_vmcs_revision_id() |
| Per-CPU State | Manage processor VMX state | hardware_enable(),hardware_disable() |
| Error Handling | Convert VMX errors to AxError | as_axerr() |
The hardware abstraction layer ensures compatibility across different Intel processors while providing a uniform interface for VMX operations. The VmxBasic structure reads processor capabilities, and the system adapts its behavior based on detected features such as RDTSCP, INVPCID, and XSAVES support.
flowchart TD
subgraph subGraph1["VMX Configuration"]
RevisionId["revision_id"]
Controls["VMX Control MSRsIA32_VMX_PINBASED_CTLSIA32_VMX_PROCBASED_CTLSIA32_VMX_PROCBASED_CTLS2"]
Features["Feature AdaptationRDTSCPINVPCIDXSAVES"]
end
subgraph subGraph0["Hardware Detection"]
CpuId["raw_cpuid::CpuId"]
FeatureCheck["get_feature_info().has_vmx()"]
VmxBasic["VmxBasic::read()"]
end
subgraph subGraph2["Error Handling"]
VmFail["x86::vmx::VmFail"]
ErrorConv["as_axerr()"]
AxError["axerrno::AxError"]
end
CpuId --> FeatureCheck
ErrorConv --> AxError
FeatureCheck --> Controls
RevisionId --> Features
VmFail --> ErrorConv
VmxBasic --> RevisionId
Sources: src/vmx/mod.rs(L16 - L27) src/vmx/mod.rs(L29 - L35) src/vmx/vcpu.rs(L606 - L663)
Module Interface and Exports
The VMX module provides a clean interface through its mod.rs facade, exporting the essential types and functions needed by the broader hypervisor system. The module structure separates concerns between different aspects of VMX functionality while providing unified access through type aliases.
| Export | Source | Purpose |
|---|---|---|
| VmxExitReason | definitions::VmxExitReason | VM exit reason enumeration |
| VmxArchPerCpuState | percpu::VmxPerCpuState | Per-CPU VMX state management |
| VmxArchVCpu | vcpu::VmxVcpu | Main virtual CPU implementation |
| VmxExitInfo | vmcs::VmxExitInfo | VM exit information structure |
| VmxInterruptInfo | vmcs::VmxInterruptInfo | Interrupt exit details |
| VmxIoExitInfo | vmcs::VmxIoExitInfo | I/O instruction exit details |
The module implements a layered architecture where higher-level abstractions build upon lower-level VMX primitives, providing both flexibility for advanced users and simplicity for common virtualization tasks.
Sources: src/vmx/mod.rs(L1 - L14) src/vmx/mod.rs(L11 - L14)
Virtual CPU Management
Relevant source files
This document covers the implementation and lifecycle management of virtual CPUs (vCPUs) in the x86_vcpu hypervisor library. It focuses on the VmxVcpu structure and its role in orchestrating VM execution, handling VM exits, and managing guest/host state transitions. For VMX data structures and memory management details, see VMX Data Structures and Physical Frame Management.
VmxVcpu Structure Overview
The VmxVcpu<H: AxVCpuHal> struct serves as the central abstraction for managing virtual CPU execution within the VMX virtualization framework. It encapsulates all necessary state and control structures required for guest execution.
flowchart TD
subgraph CoreMethods["Core VmxVcpu Methods"]
RunMethod["inner_run()"]
UnbindMethod["unbind_from_current_processor()"]
VmcsRegion["vmcs: VmxRegion"]
IOBitmap["io_bitmap: IOBitmap"]
subgraph StateManagement["State Management"]
XStateSwitch["XState::switch_to_guest/host()"]
RegisterAccess["regs()/regs_mut()"]
VmcsAccess["VMCS field access"]
NewMethod["new()"]
SetupMethod["setup()"]
BindMethod["bind_to_current_processor()"]
GuestRegs["guest_regs: GeneralRegisters"]
HostStack["host_stack_top: u64"]
LaunchState["launched: bool"]
end
end
subgraph VmxVcpuStruct["VmxVcpu Structure"]
XStateSwitch["XState::switch_to_guest/host()"]
RegisterAccess["regs()/regs_mut()"]
VmcsAccess["VMCS field access"]
NewMethod["new()"]
SetupMethod["setup()"]
BindMethod["bind_to_current_processor()"]
RunMethod["inner_run()"]
UnbindMethod["unbind_from_current_processor()"]
GuestRegs["guest_regs: GeneralRegisters"]
HostStack["host_stack_top: u64"]
LaunchState["launched: bool"]
VmcsRegion["vmcs: VmxRegion"]
IOBitmap["io_bitmap: IOBitmap"]
MsrBitmap["msr_bitmap: MsrBitmap"]
PendingEvents["pending_events: VecDeque<(u8, Option)>"]
XStateData["xstate: XState"]
EntryPoint["entry: Option"]
EptRoot["ept_root: Option"]
end
Sources: src/vmx/vcpu.rs(L144 - L159)
Key Components
| Component | Type | Purpose |
|---|---|---|
| guest_regs | GeneralRegisters | Guest general-purpose register state |
| host_stack_top | u64 | Host stack pointer for VM exit handling |
| vmcs | VmxRegion | Virtual Machine Control Structure memory region |
| io_bitmap | IOBitmap | I/O port access control bitmap |
| msr_bitmap | MsrBitmap | MSR access control bitmap |
| pending_events | VecDeque<(u8, Option | Queue for interrupt/exception injection |
| xstate | XState | Extended processor state management |
Sources: src/vmx/vcpu.rs(L144 - L159)
Virtual CPU Lifecycle
The virtual CPU follows a well-defined lifecycle from creation through execution to teardown. Each phase involves specific initialization and state management operations.
sequenceDiagram
participant ClientCode as "Client Code"
participant VmxVcpuH as "VmxVcpu<H>"
participant VMCSManager as "VMCS Manager"
participant VMXHardware as "VMX Hardware"
ClientCode ->> VmxVcpuH: "new()"
VmxVcpuH ->> VmxVcpuH: "Initialize guest_regs, vmcs, bitmaps"
VmxVcpuH ->> VmxVcpuH: "Create XState::new()"
ClientCode ->> VmxVcpuH: "setup(ept_root, entry)"
VmxVcpuH ->> VMCSManager: "setup_vmcs_guest()"
VmxVcpuH ->> VMCSManager: "setup_vmcs_control()"
ClientCode ->> VmxVcpuH: "bind_to_current_processor()"
VmxVcpuH ->> VMXHardware: "vmx::vmptrld(vmcs.phys_addr())"
VmxVcpuH ->> VMCSManager: "setup_vmcs_host()"
loop "VM Execution"
ClientCode ->> VmxVcpuH: "inner_run()"
VmxVcpuH ->> VmxVcpuH: "inject_pending_events()"
VmxVcpuH ->> VMXHardware: "vmlaunch/vmresume"
VMXHardware -->> VmxVcpuH: "VM Exit"
VmxVcpuH ->> VmxVcpuH: "builtin_vmexit_handler()"
end
ClientCode ->> VmxVcpuH: "unbind_from_current_processor()"
VmxVcpuH ->> VMXHardware: "vmx::vmclear(vmcs.phys_addr())"
Sources: src/vmx/vcpu.rs(L162 - L217) src/vmx/vcpu.rs(L238 - L280)
Creation and Setup
The VmxVcpu::new() method initializes a new virtual CPU instance with default state:
- Creates default
GeneralRegistersandXState - Allocates
VmxRegionwith proper VMX revision ID - Initializes I/O and MSR bitmaps with passthrough configuration
- Sets up pending events queue
Sources: src/vmx/vcpu.rs(L162 - L180)
The setup() method configures the virtual CPU for a specific guest:
- Calls
setup_vmcs()to configure VMCS guest, host, and control fields - Sets guest entry point and EPT root page table address
Sources: src/vmx/vcpu.rs(L183 - L186)
Processor Binding
Before execution, the virtual CPU must be bound to a physical processor using bind_to_current_processor():
- Loads the VMCS using
vmx::vmptrld() - Configures host state in VMCS via
setup_vmcs_host()
Sources: src/vmx/vcpu.rs(L194 - L204)
VM Execution Cycle
The core execution mechanism is implemented in inner_run(), which orchestrates the complete VM entry/exit cycle including guest execution and exit handling.
Sources: src/vmx/vcpu.rs(L238 - L280)
VM Entry Mechanisms
The hypervisor uses naked assembly functions for precise control over VM entry:
vmx_launch(): Initial VM entry usingvmlaunchinstructionvmx_resume(): Subsequent VM entries usingvmresumeinstruction- Both functions save host state and restore guest state before entry
Sources: src/vmx/vcpu.rs(L822 - L840)
VM Exit Handling
When a VM exit occurs, vmx_exit() handles the transition back to host mode:
- Saves guest registers to the
GeneralRegistersstructure - Restores host stack pointer from
host_stack_top - Returns control to
inner_run()for exit processing
Sources: src/vmx/vcpu.rs(L842 - L856)
Built-in Exit Handling
The VmxVcpu implements built-in handlers for common VM exit reasons that can be handled transparently without external intervention.
flowchart TD VMExit["VM Exit"] BuiltinHandler["builtin_vmexit_handler()"] ExitReason["exit_reason"] IntWindow["set_interrupt_window(false)"] PreemptTimer["handle_vmx_preemption_timer()"] XSetBV["handle_xsetbv()"] CRAccess["handle_cr()"] CPUID["handle_cpuid()"] External["Return None - external handling"] Success["Some(Ok(_))"] None["None"] BuiltinHandler --> ExitReason CPUID --> Success CRAccess --> Success ExitReason --> CPUID ExitReason --> CRAccess ExitReason --> External ExitReason --> IntWindow ExitReason --> PreemptTimer ExitReason --> XSetBV External --> None IntWindow --> Success PreemptTimer --> Success VMExit --> BuiltinHandler XSetBV --> Success
Sources: src/vmx/vcpu.rs(L890 - L906)
Exit Handler Implementations
| Exit Reason | Handler Method | Purpose |
|---|---|---|
| INTERRUPT_WINDOW | set_interrupt_window(false) | Disable interrupt window exiting after pending interrupt injection |
| PREEMPTION_TIMER | handle_vmx_preemption_timer() | Reset VMX preemption timer value |
| XSETBV | handle_xsetbv() | Handle guest XCR0 modifications for extended state |
| CR_ACCESS | handle_cr() | Handle guest control register access (CR0, CR4) |
| CPUID | handle_cpuid() | Virtualize CPUID instruction with hypervisor-specific responses |
Sources: src/vmx/vcpu.rs(L908 - L1042)
CPUID Virtualization
The handle_cpuid() method provides comprehensive CPUID virtualization:
- Masks VMX feature bit to hide hardware virtualization from guest
- Sets hypervisor present bit to indicate virtualized environment
- Provides custom hypervisor identification strings
- Handles extended state enumeration with proper XState context switching
Sources: src/vmx/vcpu.rs(L955 - L1042)
State Management
Extended Processor State (XState)
The XState structure manages extended processor state including XCR0 and IA32_XSS registers for both guest and host contexts.
flowchart TD
subgraph XStateStruct["XState Structure"]
LoadGuest["load_guest_xstate()"]
LoadHost["load_host_xstate()"]
SwitchGuest["switch_to_guest()"]
SwitchHost["switch_to_host()"]
HostXCR0["host_xcr0: u64"]
GuestXCR0["guest_xcr0: u64"]
HostXSS["host_xss: u64"]
GuestXSS["guest_xss: u64"]
XSaveAvail["xsave_available: bool"]
XSavesAvail["xsaves_available: bool"]
subgraph VMExecution["VM Execution Integration"]
subgraph StateSwitching["State Switching Methods"]
LoadGuest["load_guest_xstate()"]
LoadHost["load_host_xstate()"]
SwitchGuest["switch_to_guest()"]
SwitchHost["switch_to_host()"]
HostXCR0["host_xcr0: u64"]
GuestXCR0["guest_xcr0: u64"]
end
end
end
Sources: src/vmx/vcpu.rs(L31 - L138)
Register Access Interface
The VmxVcpu provides multiple interfaces for accessing guest processor state:
regs()andregs_mut(): Access general-purpose registersstack_pointer()andset_stack_pointer(): Guest RSP accessrip()andadvance_rip(): Guest instruction pointer managementget_cpu_mode(): Determine current guest CPU mode (Real, Protected, Compatibility, Mode64)
Sources: src/vmx/vcpu.rs(L307 - L399)
Event Injection
The pending events mechanism allows queueing interrupts and exceptions for injection:
queue_event(): Add interrupt/exception to pending queueinject_pending_events(): Process queue before VM entryset_interrupt_window(): Enable interrupt window exiting when guest interrupts are blocked
Sources: src/vmx/vcpu.rs(L402 - L404) src/vmx/vcpu.rs(L871 - L888)
Integration with AxArchVCpu Trait
The VmxVcpu implements the AxArchVCpu trait to provide a standardized interface for the broader hypervisor framework.
| Trait Method | Implementation | Purpose |
|---|---|---|
| new() | VmxVcpu::new() | Create new virtual CPU instance |
| set_entry() | Setsentryfield | Configure guest entry point |
| set_ept_root() | Setsept_rootfield | Configure EPT root page table |
| setup() | Callssetup_vmcs() | Initialize VMCS with entry and EPT root |
| run() | Callsinner_run()and converts exit info | Execute guest and return standardized exit reasons |
| bind()/unbind() | Processor binding/unbinding | Manage processor affinity |
Sources: src/vmx/vcpu.rs(L1144 - L1255)
This integration enables the VMX-specific virtual CPU implementation to work seamlessly with the broader ArceOS hypervisor architecture while maintaining VMX-specific optimizations and capabilities.
Sources: src/vmx/vcpu.rs(L1 - L1256)
VMX Data Structures
Relevant source files
This document covers the fundamental VMX data structures that support Intel VMX virtualization operations. These structures include memory-backed control regions (VmxRegion), access control bitmaps (IOBitmap, MsrBitmap), hardware capability reporting (VmxBasic), and page table pointers (EPTPointer). These structures work together to configure and control virtual machine execution.
For information about how these structures are used in VCPU management, see Virtual CPU Management. For details about VMCS field access patterns, see VMCS Field Management.
Core VMX Memory Structures
The hypervisor manages three primary memory-backed VMX structures that control virtual machine behavior. Each structure is backed by one or more 4KB physical frames managed through the PhysFrame<H> abstraction.
VmxRegion
The VmxRegion<H: AxVCpuHal> struct represents VMCS/VMXON regions as defined in Intel SDM Vol. 3C, Section 24.2. These regions are 4KB memory areas that store VMX control structures.
flowchart TD VmxRegion["VmxRegion<H>"] PhysFrame["PhysFrame<H>"] RevisionHeader["Revision Header (32-bit)"] RevisionID["revision_id (bits 0-30)"] ShadowIndicator["shadow_indicator (bit 31)"] PhysFrame --> RevisionHeader RevisionHeader --> RevisionID RevisionHeader --> ShadowIndicator VmxRegion --> PhysFrame
The constructor VmxRegion::new() initializes the region with a revision identifier and shadow indicator bit as required by VMX specifications. The phys_addr() method provides the physical address needed for VMX instructions.
IOBitmap
The IOBitmap<H: AxVCpuHal> controls I/O port access interception using two 4KB frames covering the complete 16-bit I/O port space as specified in Intel SDM Vol. 3C, Section 25.6.4.
| Frame | Port Range | Size |
|---|---|---|
| io_bitmap_a_frame | 0x0000-0x7FFF | 4KB |
| io_bitmap_b_frame | 0x8000-0xFFFF | 4KB |
flowchart TD IOBitmap["IOBitmap<H>"] BitmapA["io_bitmap_a_frame"] BitmapB["io_bitmap_b_frame"] PortsLow["Ports 0x0000-0x7FFF"] PortsHigh["Ports 0x8000-0xFFFF"] BitLogic["1 bit = intercept0 bit = passthrough"] BitmapA --> PortsLow BitmapB --> PortsHigh IOBitmap --> BitmapA IOBitmap --> BitmapB PortsHigh --> BitLogic PortsLow --> BitLogic
The set_intercept() method configures individual port interception by setting bits in the appropriate bitmap frame. The passthrough_all() constructor creates zero-filled bitmaps for unrestricted I/O access, while intercept_all() creates bitmaps with all bits set.
MsrBitmap
The MsrBitmap<H: AxVCpuHal> controls Model-Specific Register access using a single 4KB frame divided into four 1KB regions for different MSR ranges and access types.
| Offset | Region | MSR Range | Access Type |
|---|---|---|---|
| 0x000 | Read Low | 0x0000_0000-0x0000_1FFF | Read |
| 0x400 | Read High | 0xC000_0000-0xC000_1FFF | Read |
| 0x800 | Write Low | 0x0000_0000-0x0000_1FFF | Write |
| 0xC00 | Write High | 0xC000_0000-0xC000_1FFF | Write |
The set_read_intercept() and set_write_intercept() methods configure MSR access control by manipulating bits in the appropriate region of the bitmap frame.
Sources: src/vmx/structs.rs(L13 - L163)
Hardware Capability Structures
VmxBasic Capabilities
The VmxBasic struct provides access to the IA32_VMX_BASIC MSR, which reports fundamental VMX capabilities. This structure implements the MsrReadWrite trait for MSR access.
flowchart TD VmxBasic["VmxBasic"] MSR["IA32_VMX_BASIC MSR"] RevisionID["revision_id (bits 0-30)"] RegionSize["region_size (bits 32-44)"] AddrWidth["is_32bit_address (bit 48)"] MemType["mem_type (bits 50-53)"] IOExitInfo["io_exit_info (bit 54)"] FlexControls["vmx_flex_controls (bit 55)"] VmxBasic --> AddrWidth VmxBasic --> FlexControls VmxBasic --> IOExitInfo VmxBasic --> MSR VmxBasic --> MemType VmxBasic --> RegionSize VmxBasic --> RevisionID
Key fields include:
revision_id: 31-bit VMCS revision identifierregion_size: Required allocation size for VMXON/VMCS regionsmem_type: Required memory type (typicallyVMX_MEMORY_TYPE_WRITE_BACK = 6)vmx_flex_controls: Indicates flexible control support
FeatureControl Management
The FeatureControl struct manages the IA32_FEATURE_CONTROL MSR through FeatureControlFlags bitflags. This MSR controls VMX enablement at the processor level.
| Flag | Purpose |
|---|---|
| LOCKED | Prevents further MSR modification |
| VMXON_ENABLED_INSIDE_SMX | Enables VMX inside SMX mode |
| VMXON_ENABLED_OUTSIDE_SMX | Enables VMX outside SMX mode |
The write() method preserves reserved bits while updating control flags, ensuring proper MSR manipulation.
Sources: src/vmx/structs.rs(L165 - L240)
Extended Page Table Structures
EPTPointer Configuration
The EPTPointer bitflags struct configures Extended Page Table parameters as defined in Intel SDM Vol. 3C, Section 24.6.11. This structure combines page table root address with control flags.
flowchart TD EPTPointer["EPTPointer"] MemoryType["Memory Type (bits 0-2)"] WalkLength["Page Walk Length (bits 3-5)"] AccessedDirty["Enable Accessed/Dirty (bit 6)"] TableAddr["Page Table Root Address (bits 12-51)"] UC["MEM_TYPE_UC = 0"] WB["MEM_TYPE_WB = 6"] Length4["WALK_LENGTH_4 = 3"] Enabled["ENABLE_ACCESSED_DIRTY"] AccessedDirty --> Enabled EPTPointer --> AccessedDirty EPTPointer --> MemoryType EPTPointer --> TableAddr EPTPointer --> WalkLength MemoryType --> UC MemoryType --> WB WalkLength --> Length4
The from_table_phys() constructor creates a properly configured EPT pointer from a physical page table root address, setting standard flags for write-back memory type, 4-level page walks, and accessed/dirty bit support.
Structure Relationships and Usage Patterns
The VMX data structures work together to establish virtual machine execution context:
flowchart TD
subgraph subGraph3["VM Execution"]
VMCS["VMCS Fields"]
VMEntry["VM Entry/Exit"]
end
subgraph subGraph2["Hardware Interface"]
VmxBasic["VmxBasic"]
FeatureControl["FeatureControl"]
EPTPointer["EPTPointer"]
end
subgraph subGraph1["VMX Control Structures"]
VmxRegion["VmxRegion"]
IOBitmap["IOBitmap"]
MsrBitmap["MsrBitmap"]
end
subgraph subGraph0["Physical Memory"]
PhysFrames["PhysFrame<H> Allocations"]
end
EPTPointer --> VMCS
FeatureControl --> VMEntry
IOBitmap --> VMCS
MsrBitmap --> VMCS
PhysFrames --> IOBitmap
PhysFrames --> MsrBitmap
PhysFrames --> VmxRegion
VMCS --> VMEntry
VmxBasic --> VmxRegion
VmxRegion --> VMCS
These structures are typically initialized during VCPU setup and configured based on virtualization requirements. The physical frame allocation through AxVCpuHal ensures proper memory management across different hypervisor implementations.
Sources: src/vmx/structs.rs(L242 - L270)
VMCS Field Management
Relevant source files
This document covers the Virtual Machine Control Structure (VMCS) field management system within the VMX virtualization engine. The VMCS is Intel's hardware structure that controls virtual machine behavior, and this module provides type-safe access patterns, control field manipulation algorithms, and VM exit information gathering mechanisms.
For Virtual CPU lifecycle management and VM execution flow, see Virtual CPU Management. For the underlying VMX data structures that contain VMCS regions, see VMX Data Structures.
VMCS Field Organization
The VMCS contains hundreds of fields organized into distinct categories based on their purpose and access patterns. The hypervisor provides type-safe enums for each category, ensuring correct field access and preventing runtime errors.
flowchart TD
subgraph subGraph5["Access Implementations"]
ReadImpl["vmcs_read! macrogenerates .read() methods"]
WriteImpl["vmcs_write! macrogenerates .write() methods"]
ROFields["define_vmcs_fields_ro!read-only field traits"]
RWFields["define_vmcs_fields_rw!read-write field traits"]
end
subgraph subGraph4["VMCS Field Categories"]
subgraph subGraph3["Read-Only Data Fields"]
ReadOnly32["VmcsReadOnly32EXIT_REASON, VM_INSTRUCTION_ERROR"]
ReadOnly64["VmcsReadOnly64GUEST_PHYSICAL_ADDR"]
ReadOnlyNW["VmcsReadOnlyNWEXIT_QUALIFICATION, GUEST_LINEAR_ADDR"]
end
subgraph subGraph2["Host State Fields"]
Host16["VmcsHost16ES_SELECTOR, TR_SELECTOR"]
Host32["VmcsHost32IA32_SYSENTER_CS"]
Host64["VmcsHost64IA32_PAT, IA32_EFER"]
HostNW["VmcsHostNWCR0, RSP, RIP"]
end
subgraph subGraph1["Guest State Fields"]
Guest16["VmcsGuest16ES_SELECTOR, CS_SELECTOR"]
Guest32["VmcsGuest32ES_LIMIT, INTERRUPTIBILITY_STATE"]
Guest64["VmcsGuest64IA32_EFER, IA32_PAT"]
GuestNW["VmcsGuestNWCR0, RSP, RIP, RFLAGS"]
end
subgraph subGraph0["Control Fields"]
Control16["VmcsControl16VPID, EPTP_INDEX"]
Control32["VmcsControl32EXEC_CONTROLS, EXCEPTION_BITMAP"]
Control64["VmcsControl64IO_BITMAP_A_ADDR, MSR_BITMAPS_ADDR"]
ControlNW["VmcsControlNWCR0_GUEST_HOST_MASK, CR4_READ_SHADOW"]
end
end
Control16 --> RWFields
Control32 --> RWFields
Control64 --> RWFields
ControlNW --> RWFields
Guest16 --> RWFields
Guest32 --> RWFields
Guest64 --> RWFields
GuestNW --> RWFields
Host16 --> RWFields
Host32 --> RWFields
Host64 --> RWFields
HostNW --> RWFields
ROFields --> ReadImpl
RWFields --> ReadImpl
RWFields --> WriteImpl
ReadOnly32 --> ROFields
ReadOnly64 --> ROFields
ReadOnlyNW --> ROFields
Sources: src/vmx/vmcs.rs(L85 - L486)
Field Access Patterns
The system uses code generation macros to create consistent, type-safe access patterns for all VMCS fields. These macros handle the underlying vmread and vmwrite instructions while providing error handling and architecture-specific adaptations.
Read/Write Macro Implementation
flowchart TD
subgraph subGraph3["Architecture Handling"]
Arch64["64-bit: Direct access"]
Arch32["32-bit: Split high/low"]
end
subgraph subGraph2["Hardware Instructions"]
VmreadInstr["vmx::vmread()"]
VmwriteInstr["vmx::vmwrite()"]
end
subgraph subGraph1["Generated Methods"]
ReadMethod[".read() -> AxResult<T>"]
WriteMethod[".write(value: T) -> AxResult"]
end
subgraph subGraph0["Field Access Macros"]
VmcsRead["vmcs_read! macro"]
VmcsWrite["vmcs_write! macro"]
DefineRO["define_vmcs_fields_ro!"]
DefineRW["define_vmcs_fields_rw!"]
end
DefineRO --> ReadMethod
DefineRW --> ReadMethod
DefineRW --> WriteMethod
ReadMethod --> VmreadInstr
VmcsRead --> ReadMethod
VmcsWrite --> WriteMethod
VmreadInstr --> Arch32
VmreadInstr --> Arch64
VmwriteInstr --> Arch32
VmwriteInstr --> Arch64
WriteMethod --> VmwriteInstr
The vmcs_read! and vmcs_write! macros generate implementations that automatically handle 32-bit vs 64-bit architecture differences. On 32-bit systems, 64-bit fields require two separate hardware accesses to read the high and low portions.
Sources: src/vmx/vmcs.rs(L19 - L83)
VM Exit Information Gathering
When a VM exit occurs, the hypervisor must gather information about why the exit happened and the guest's state. The VMCS provides read-only fields containing this information, which the system abstracts into structured data types.
sequenceDiagram
participant GuestVM as "Guest VM"
participant VMXHardware as "VMX Hardware"
participant VMCSFields as "VMCS Fields"
participant exit_info as "exit_info()"
participant ExitHandler as "Exit Handler"
GuestVM ->> VMXHardware: "Instruction causes VM exit"
VMXHardware ->> VMCSFields: "Populate exit fields"
Note over VMCSFields: EXIT_REASON<br>EXIT_QUALIFICATION<br>VMEXIT_INSTRUCTION_LEN<br>Guest RIP
ExitHandler ->> exit_info: "Call exit_info()"
exit_info ->> VMCSFields: "VmcsReadOnly32::EXIT_REASON.read()"
exit_info ->> VMCSFields: "VmcsReadOnly32::VMEXIT_INSTRUCTION_LEN.read()"
exit_info ->> VMCSFields: "VmcsGuestNW::RIP.read()"
exit_info ->> ExitHandler: "VmxExitInfo struct"
ExitHandler ->> ExitHandler: "Process exit reason"
Exit Information Structures
The system defines several structured types for different categories of VM exit information:
| Structure | Purpose | Key Fields |
|---|---|---|
| VmxExitInfo | General exit information | exit_reason,entry_failure,guest_rip |
| VmxInterruptInfo | Interrupt/exception details | vector,int_type,err_code |
| VmxIoExitInfo | I/O instruction exits | port,access_size,is_in |
| CrAccessInfo | Control register access | cr_number,access_type,gpr |
Sources: src/vmx/vmcs.rs(L488 - L582) src/vmx/vmcs.rs(L645 - L774)
Control Field Management
Control fields determine VM behavior and require careful management to ensure compatibility with the underlying hardware. The set_control() function implements the Intel-specified algorithm for safely setting control bits while respecting hardware capabilities.
flowchart TD
subgraph subGraph1["Capability Check"]
MSRRead["capability_msr.read()"]
Allowed0["allowed0 = cap[31:0](must be 1)"]
Allowed1["allowed1 = cap[63:32](may be 1)"]
Flexible["flexible = !allowed0 & allowed1"]
end
subgraph subGraph0["Control Setting Algorithm"]
Input["set_control(control, msr, old, set, clear)"]
ReadMSR["Read capability MSR"]
ExtractBits["Extract allowed0/allowed1"]
ValidateConflict["Validate set & clear don't conflict"]
ValidateAllowed1["Validate set bits allowed in allowed1"]
ValidateAllowed0["Validate clear bits allowed in allowed0"]
CalculateValue["Calculate final value:fixed1 | default | set"]
WriteVMCS["Write to VMCS field"]
end
Allowed0 --> Flexible
Allowed1 --> Flexible
CalculateValue --> WriteVMCS
ExtractBits --> Allowed0
ExtractBits --> Allowed1
ExtractBits --> ValidateConflict
Flexible --> CalculateValue
Input --> ReadMSR
MSRRead --> ExtractBits
ReadMSR --> MSRRead
ValidateAllowed0 --> CalculateValue
ValidateAllowed1 --> ValidateAllowed0
ValidateConflict --> ValidateAllowed1
The algorithm follows Intel SDM Volume 3C, Section 31.5.1, Algorithm 3, ensuring that control bits are set correctly based on processor capabilities.
Sources: src/vmx/vmcs.rs(L589 - L631)
Event Injection
The hypervisor can inject interrupts and exceptions into the guest using VMCS entry control fields. The inject_event() function handles the complex logic of setting up proper event injection based on the event type.
Event Injection Flow
flowchart TD
subgraph subGraph1["VMCS Fields Updated"]
EntryErrCode["VMENTRY_EXCEPTION_ERR_CODE"]
EntryInstrLen["VMENTRY_INSTRUCTION_LEN"]
EntryIntInfo["VMENTRY_INTERRUPTION_INFO_FIELD"]
end
subgraph subGraph0["Event Injection Process"]
InjectCall["inject_event(vector, err_code)"]
DetermineType["Determine VmxInterruptionType from vector"]
HandleError["Handle error code:- Required by vector type?- Use provided or exit code"]
CreateInfo["Create VmxInterruptInfo"]
WriteErrorCode["Write VMENTRY_EXCEPTION_ERR_CODE"]
HandleSoft["For soft interrupts:Set VMENTRY_INSTRUCTION_LEN"]
WriteIntInfo["Write VMENTRY_INTERRUPTION_INFO_FIELD"]
end
CreateInfo --> WriteErrorCode
DetermineType --> HandleError
HandleError --> CreateInfo
HandleSoft --> EntryInstrLen
HandleSoft --> WriteIntInfo
InjectCall --> DetermineType
WriteErrorCode --> EntryErrCode
WriteErrorCode --> HandleSoft
WriteIntInfo --> EntryIntInfo
The VmxInterruptInfo structure encodes the interrupt information according to Intel specifications, with specific bit fields for vector, interruption type, error code validity, and overall validity.
Sources: src/vmx/vmcs.rs(L677 - L694) src/vmx/vmcs.rs(L515 - L534)
Specialized Information Extraction
The system provides specialized functions for extracting detailed information from different types of VM exits:
I/O Exit Information
- Function:
io_exit_info() - Purpose: Extract port number, access size, direction for I/O instruction exits
- Fields: Port number, access size (1-4 bytes), IN/OUT direction, string/repeat flags
EPT Violation Information
- Function:
ept_violation_info() - Purpose: Extract guest physical address and access type for memory violations
- Returns:
NestedPageFaultInfowith fault address and access flags
Control Register Access
- Function:
cr_access_info() - Purpose: Decode control register access attempts (MOV, CLTS, LMSW)
- Fields: CR number, access type, GPR involved, LMSW source data
EFER Management
- Function:
update_efer() - Purpose: Handle guest IA32_EFER updates, particularly long mode transitions
- Actions: Set LONG_MODE_ACTIVE bit, update VM-entry controls
Sources: src/vmx/vmcs.rs(L696 - L774)
Per-CPU VMX State
Relevant source files
This document covers the per-CPU VMX (Virtual Machine Extensions) state management system, which handles processor-specific initialization, enablement, and disablement of Intel's VMX virtualization technology. This system ensures that each CPU core in a multi-processor system can independently manage its VMX capabilities while maintaining proper hardware validation and state tracking.
For information about VMCS field management and virtual machine control structures, see VMCS Field Management. For details about VMX data structures like VmxRegion, see VMX Data Structures.
VmxPerCpuState Structure
The VmxPerCpuState<H: AxVCpuHal> struct serves as the central management entity for VMX state on individual processor cores. It maintains the essential components needed for VMX operation including hardware compatibility information and memory regions.
flowchart TD
subgraph subGraph2["Hardware Abstractions"]
AxVCpuHal["AxVCpuHal traitHardware abstraction layer"]
VmxRegionImpl["VmxRegion implementation4KB aligned memory"]
end
subgraph subGraph1["AxArchPerCpu Trait Implementation"]
NewMethod["new(cpu_id: usize)Initialize per-CPU state"]
IsEnabled["is_enabled()Check VMX status"]
HwEnable["hardware_enable()Enable VMX on CPU"]
HwDisable["hardware_disable()Disable VMX on CPU"]
end
subgraph subGraph0["VmxPerCpuState Structure"]
VmxPerCpu["VmxPerCpuState<H>"]
RevisionId["vmcs_revision_id: u32VMCS compatibility identifier"]
VmxRegion["vmx_region: VmxRegion<H>Memory region for VMXON"]
end
VmxPerCpu --> HwDisable
VmxPerCpu --> HwEnable
VmxPerCpu --> IsEnabled
VmxPerCpu --> NewMethod
VmxPerCpu --> RevisionId
VmxPerCpu --> VmxRegion
VmxRegion --> VmxRegionImpl
VmxRegionImpl --> AxVCpuHal
Sources: src/vmx/percpu.rs(L17 - L29)
The structure contains two primary fields:
| Field | Type | Purpose |
|---|---|---|
| vmcs_revision_id | u32 | VMCS revision identifier ensuring software-hardware compatibility |
| vmx_region | VmxRegion | Memory region used for VMXON instruction execution |
Hardware Enable Process
The hardware_enable() method implements a comprehensive VMX initialization sequence that validates hardware support, configures control registers, and executes the VMXON instruction to enter VMX operation mode.
sequenceDiagram
participant Caller as "Caller"
participant VmxPerCpuState as "VmxPerCpuState"
participant CPUHardware as "CPU Hardware"
participant ModelSpecificRegisters as "Model Specific Registers"
participant ControlRegisters as "Control Registers"
Caller ->> VmxPerCpuState: hardware_enable()
VmxPerCpuState ->> CPUHardware: has_hardware_support()
alt No VMX support
CPUHardware -->> VmxPerCpuState: false
VmxPerCpuState -->> Caller: AxError::Unsupported
end
VmxPerCpuState ->> ControlRegisters: Check CR4.VMXE
alt Already enabled
ControlRegisters -->> VmxPerCpuState: VMX enabled
VmxPerCpuState -->> Caller: AxError::ResourceBusy
end
VmxPerCpuState ->> VmxPerCpuState: XState::enable_xsave()
VmxPerCpuState ->> ModelSpecificRegisters: Read FeatureControl MSR
VmxPerCpuState ->> ModelSpecificRegisters: Enable VMXON if unlocked
VmxPerCpuState ->> ModelSpecificRegisters: Validate CR0/CR4 against VMX_FIXED MSRs
alt Invalid control registers
ModelSpecificRegisters -->> VmxPerCpuState: Validation failed
VmxPerCpuState -->> Caller: AxError::BadState
end
VmxPerCpuState ->> ModelSpecificRegisters: Read IA32_VMX_BASIC
VmxPerCpuState ->> VmxPerCpuState: Validate VMX capabilities
VmxPerCpuState ->> VmxPerCpuState: Create VmxRegion with revision_id
VmxPerCpuState ->> ControlRegisters: Set CR4.VMXE
VmxPerCpuState ->> CPUHardware: vmxon(vmx_region.phys_addr())
CPUHardware -->> VmxPerCpuState: Success
VmxPerCpuState -->> Caller: Ok(())
Sources: src/vmx/percpu.rs(L43 - L117)
Hardware Validation Steps
The enable process performs several critical validation steps:
- VMX Support Detection: Verifies CPU supports VMX extensions src/vmx/percpu.rs(L44 - L46)
- Feature Control MSR: Enables VMXON capability in IA32_FEATURE_CONTROL src/vmx/percpu.rs(L55 - L64)
- Control Register Validation: Ensures CR0 and CR4 comply with VMX requirements src/vmx/percpu.rs(L67 - L81)
- VMX Basic Capabilities: Validates memory type, addressing, and control features src/vmx/percpu.rs(L84 - L99)
Control Register Validation
The implementation uses a macro-based approach to validate control registers against VMX fixed MSRs:
flowchart TD
subgraph subGraph1["Control Register Validation"]
CR0["CR0 Register"]
ValidationMacro["cr_is_valid! macro(!fixed0 | value != 0) && (fixed1 | !value != 0)"]
CR4["CR4 Register"]
subgraph subGraph0["VMX Fixed MSRs"]
Fixed0["IA32_VMX_CR0_FIXED0Must be set bits"]
Fixed1["IA32_VMX_CR0_FIXED1Must be clear bits"]
Fixed0_CR4["IA32_VMX_CR4_FIXED0"]
Fixed1_CR4["IA32_VMX_CR4_FIXED1"]
end
end
CR0 --> ValidationMacro
CR4 --> ValidationMacro
Fixed0 --> ValidationMacro
Fixed0_CR4 --> ValidationMacro
Fixed1 --> ValidationMacro
Fixed1_CR4 --> ValidationMacro
Sources: src/vmx/percpu.rs(L67 - L81)
Hardware Disable Process
The hardware_disable() method provides a safe teardown sequence for VMX operation, executing VMXOFF and restoring the processor to non-VMX state.
flowchart TD Start["hardware_disable()"] CheckEnabled["Check is_enabled()"] Error["Return BadState error"] VmxOff["Execute vmxoff instruction"] ClearCR4["Clear CR4.VMXE bit"] UninitRegion["vmx_region = VmxRegion::uninit()"] Success["Return Ok(())"] VmxOffError["Return BadState error"] CheckEnabled --> Error CheckEnabled --> VmxOff ClearCR4 --> UninitRegion Start --> CheckEnabled UninitRegion --> Success VmxOff --> ClearCR4 VmxOff --> VmxOffError
Sources: src/vmx/percpu.rs(L119 - L140)
State Management and Lifecycle
The VmxPerCpuState follows a clear lifecycle pattern that ensures proper resource management and state transitions.
| Method | Purpose | Key Operations |
|---|---|---|
| new() | Initialize uninitialized state | Setsvmcs_revision_idto 0, creates uninitializedVmxRegion |
| is_enabled() | Check current VMX status | Reads CR4.VMXE flag |
| hardware_enable() | Enable VMX on current CPU | Hardware validation, VMXON execution, state setup |
| hardware_disable() | Disable VMX on current CPU | VMXOFF execution, CR4 cleanup, resource deallocation |
Sources: src/vmx/percpu.rs(L31 - L140)
Integration with Hardware Abstraction
The per-CPU VMX state integrates with the broader hardware abstraction system through the AxVCpuHal trait and AxArchPerCpu interface.
flowchart TD
subgraph subGraph1["Hardware Abstraction Integration"]
AxArchPerCpu["AxArchPerCpu traitGeneric per-CPU interface"]
VmxPerCpuImpl["VmxPerCpuState<H>VMX-specific implementation"]
AxVCpuHal["AxVCpuHal traitHardware abstraction layer"]
subgraph Dependencies["Dependencies"]
VmxRegionDep["VmxRegion<H>Memory management"]
FeatureControl["FeatureControl MSRHardware feature control"]
VmxBasic["VmxBasic MSRVMX capabilities"]
HasSupport["has_hardware_support()Hardware detection"]
end
end
AxArchPerCpu --> VmxPerCpuImpl
VmxPerCpuImpl --> AxVCpuHal
VmxPerCpuImpl --> FeatureControl
VmxPerCpuImpl --> HasSupport
VmxPerCpuImpl --> VmxBasic
VmxPerCpuImpl --> VmxRegionDep
VmxRegionDep --> AxVCpuHal
Sources: src/vmx/percpu.rs(L1 - L11) src/vmx/percpu.rs(L31 - L37)
The system leverages several key abstractions:
- AxArchPerCpu: Provides a generic interface for per-CPU architecture-specific state
- AxVCpuHal: Hardware abstraction layer for memory management and physical addressing
- VmxRegion: RAII-managed memory regions for VMX operations
This design enables the VMX per-CPU state to operate independently on each processor core while maintaining consistent interfaces for higher-level hypervisor components.
Memory Management
Relevant source files
Purpose and Scope
This document covers the memory management systems in the x86_vcpu hypervisor library, which handle both host physical memory allocation and guest memory virtualization. The memory management architecture provides two primary functions: RAII-based physical frame allocation for VMX data structures and Extended Page Table (EPT) support for guest memory virtualization.
For details on how memory management integrates with specific VMX structures, see VMX Data Structures. For information about the hardware abstraction layer that enables memory management, see Physical Frame Management.
Memory Management Architecture
The memory management system operates at two distinct levels: host physical memory management through the PhysFrame abstraction, and guest memory virtualization through EPT mechanisms.
Memory Management Overview
flowchart TD
subgraph subGraph3["External Systems"]
GuestOS["Guest Operating System"]
HostOS["Host Operating System"]
end
subgraph subGraph2["VMX Memory Consumers"]
VmxRegion["VmxRegion"]
IOBitmap["IOBitmap"]
MsrBitmap["MsrBitmap"]
end
subgraph subGraph1["Guest Memory Virtualization"]
GuestPageWalkInfo["GuestPageWalkInfo"]
EPTPointer["EPT Pointer"]
end
subgraph subGraph0["Host Memory Management"]
AxVCpuHal["AxVCpuHal Trait"]
PhysFrame["PhysFrame<H>"]
end
AxVCpuHal --> PhysFrame
GuestOS --> GuestPageWalkInfo
GuestPageWalkInfo --> EPTPointer
HostOS --> AxVCpuHal
IOBitmap --> PhysFrame
MsrBitmap --> PhysFrame
VmxRegion --> PhysFrame
Sources: src/frame.rs(L1 - L63) src/ept.rs(L1 - L28)
Physical Memory Abstraction
The PhysFrame<H: AxVCpuHal> struct provides RAII-based management of 4KB physical memory pages. Each frame is automatically deallocated when dropped, preventing memory leaks in the hypervisor.
PhysFrame Lifecycle Management
flowchart TD
subgraph Deallocation["Deallocation"]
drop["Drop::drop()"]
dealloc_frame["H::dealloc_frame()"]
end
subgraph subGraph2["Access Methods"]
start_paddr["start_paddr()"]
as_mut_ptr["as_mut_ptr()"]
fill["fill(byte)"]
end
subgraph subGraph1["PhysFrame State"]
allocated["Allocated Framestart_paddr: Some(addr)"]
uninitialized["Uninitialized Framestart_paddr: None"]
end
subgraph subGraph0["Allocation Methods"]
alloc["PhysFrame::alloc()"]
alloc_zero["PhysFrame::alloc_zero()"]
uninit["PhysFrame::uninit()"]
end
alloc --> allocated
alloc_zero --> allocated
allocated --> as_mut_ptr
allocated --> drop
allocated --> fill
allocated --> start_paddr
drop --> dealloc_frame
uninit --> uninitialized
Sources: src/frame.rs(L18 - L62)
Hardware Abstraction Interface
The AxVCpuHal trait defines the interface that must be implemented by the underlying system to support physical memory operations:
| Method | Purpose | Return Type |
|---|---|---|
| alloc_frame() | Allocate a 4KB physical frame | Option |
| dealloc_frame(addr) | Deallocate a physical frame | () |
| phys_to_virt(addr) | Convert physical to virtual address | VirtAddr |
Sources: src/frame.rs(L6) src/frame.rs(L20 - L21) src/frame.rs(L47) src/frame.rs(L58)
Guest Memory Virtualization
Guest memory virtualization is handled through the Extended Page Tables (EPT) mechanism and guest page walk information tracking.
Guest Page Walk Information Structure
flowchart TD
subgraph subGraph1["Access Control Flags"]
nxe["nxe: boolMSR_IA32_EFER_NXE_BIT"]
width["width: u32Page table width"]
subgraph subGraph3["Security Features"]
is_smap_on["is_smap_on: boolSupervisor mode access prevention"]
is_smep_on["is_smep_on: boolSupervisor mode execution protection"]
pse["pse: boolCR4.PSE for 32bit paging"]
wp["wp: boolCR0.WP"]
is_user_mode["is_user_mode_access: bool"]
is_write["is_write_access: bool"]
is_inst_fetch["is_inst_fetch: bool"]
top_entry["top_entry: usizeTop level paging structure"]
level["level: usizePage table level"]
end
end
subgraph subGraph0["GuestPageWalkInfo Fields"]
subgraph subGraph2["Paging Control Flags"]
is_smap_on["is_smap_on: boolSupervisor mode access prevention"]
is_smep_on["is_smep_on: boolSupervisor mode execution protection"]
pse["pse: boolCR4.PSE for 32bit paging"]
wp["wp: boolCR0.WP"]
nxe["nxe: boolMSR_IA32_EFER_NXE_BIT"]
is_user_mode["is_user_mode_access: bool"]
is_write["is_write_access: bool"]
is_inst_fetch["is_inst_fetch: bool"]
top_entry["top_entry: usizeTop level paging structure"]
level["level: usizePage table level"]
width["width: u32Page table width"]
end
end
Sources: src/ept.rs(L3 - L27)
Memory Management Integration
The memory management system integrates with VMX structures by providing the underlying physical memory backing for virtualization data structures.
VMX Structure Memory Dependencies
| VMX Structure | Memory Requirement | Purpose |
|---|---|---|
| VmxRegion | 1xPhysFrame | VMXON/VMCS memory region |
| IOBitmap | 2xPhysFrame | I/O port interception bitmap (64KB) |
| MsrBitmap | 1xPhysFrame | MSR access control bitmap (4KB) |
The PAGE_SIZE_4K constant from the memory_addr crate defines the standard 4KB frame size used throughout the system src/frame.rs(L8)
Memory Safety Guarantees
The PhysFrame implementation provides several safety guarantees:
- RAII Management: Automatic deallocation on drop prevents memory leaks src/frame.rs(L55 - L62)
- Initialization Validation:
start_paddr()panics on uninitialized frames src/frame.rs(L42 - L44) - Zero-fill Support:
alloc_zero()provides zero-initialized frames src/frame.rs(L29 - L33) - Address Validation: Allocation asserts non-zero physical addresses src/frame.rs(L22)
Sources: src/frame.rs(L10 - L16) src/frame.rs(L18 - L53) src/frame.rs(L55 - L62)
Physical Frame Management
Relevant source files
Purpose and Scope
The Physical Frame Management system provides RAII-based allocation and management of 4KB-aligned physical memory pages for VMX operations. This system abstracts physical memory allocation through the AxVCpuHal trait and ensures automatic cleanup through Rust's ownership model. The PhysFrame<H> type serves as the fundamental building block for VMX data structures including VMCS regions, I/O bitmaps, and MSR bitmaps.
For information about how these physical frames are used in Extended Page Tables, see Extended Page Tables and Guest Memory. For details on the broader VMX data structures that consume these frames, see VMX Data Structures.
Core Architecture
PhysFrame RAII Wrapper
The PhysFrame<H: AxVCpuHal> struct provides a type-safe wrapper around 4KB physical memory pages with automatic resource management:
flowchart TD
subgraph subGraph2["Trait Implementations"]
Drop["Drop::drop()"]
Debug["Debug"]
end
subgraph subGraph1["Core Methods"]
Alloc["alloc()"]
AllocZero["alloc_zero()"]
Uninit["unsafe uninit()"]
StartPaddrMethod["start_paddr()"]
AsMutPtr["as_mut_ptr()"]
Fill["fill(byte: u8)"]
end
subgraph subGraph0["PhysFrame Structure"]
PhysFrameStruct["PhysFrame<H>"]
StartPaddr["start_paddr: Option<HostPhysAddr>"]
Marker["_marker: PhantomData<H>"]
end
PhysFrameStruct --> Alloc
PhysFrameStruct --> AllocZero
PhysFrameStruct --> AsMutPtr
PhysFrameStruct --> Debug
PhysFrameStruct --> Drop
PhysFrameStruct --> Fill
PhysFrameStruct --> Marker
PhysFrameStruct --> StartPaddr
PhysFrameStruct --> StartPaddrMethod
PhysFrameStruct --> Uninit
Sources: src/frame.rs(L12 - L16) src/frame.rs(L18 - L53)
Hardware Abstraction Layer
The AxVCpuHal trait defines the interface between the frame allocator and the underlying platform:
flowchart TD
subgraph subGraph1["PhysFrame Operations"]
PhysFrameAlloc["PhysFrame::alloc()"]
PhysFrameDrop["PhysFrame::drop()"]
PhysFrameAccess["PhysFrame::as_mut_ptr()"]
end
subgraph subGraph0["AxVCpuHal Trait Interface"]
AxVCpuHal["AxVCpuHal"]
AllocFrame["alloc_frame() -> Option<HostPhysAddr>"]
DeallocFrame["dealloc_frame(paddr: HostPhysAddr)"]
PhysToVirt["phys_to_virt(paddr: HostPhysAddr) -> VirtAddr"]
end
AxVCpuHal --> AllocFrame
AxVCpuHal --> DeallocFrame
AxVCpuHal --> PhysToVirt
PhysFrameAccess --> PhysToVirt
PhysFrameAlloc --> AllocFrame
PhysFrameDrop --> DeallocFrame
Sources: src/frame.rs(L6) src/frame.rs(L20 - L21) src/frame.rs(L47) src/frame.rs(L58)
Memory Allocation Lifecycle
Allocation Methods
The system provides three allocation strategies:
| Method | Purpose | Zero-filled | Safety |
|---|---|---|---|
| alloc() | Standard allocation | No | Safe |
| alloc_zero() | Zero-initialized allocation | Yes | Safe |
| uninit() | Uninitialized frame | N/A | Unsafe |
Sources: src/frame.rs(L19 - L27) src/frame.rs(L29 - L33) src/frame.rs(L35 - L40)
Automatic Deallocation
The Drop implementation ensures memory is automatically returned to the system:
flowchart TD
subgraph subGraph1["Drop Implementation Details"]
CheckPaddr["Check start_paddr.is_some()"]
CallDealloc["H::dealloc_frame(start_paddr)"]
LogDealloc["debug! log deallocation"]
end
subgraph subGraph0["RAII Lifecycle"]
Creation["PhysFrame::alloc()"]
Usage["Memory Usage Phase"]
Destruction["PhysFrame goes out of scope"]
DropCall["Drop::drop() called"]
Deallocation["HAL::dealloc_frame()"]
end
CallDealloc --> LogDealloc
CheckPaddr --> CallDealloc
Creation --> Usage
Destruction --> DropCall
DropCall --> CheckPaddr
DropCall --> Deallocation
Usage --> Destruction
Sources: src/frame.rs(L55 - L62)
Memory Access Patterns
Address Translation and Access
The frame system provides both physical and virtual access to allocated memory:
flowchart TD
subgraph subGraph2["HAL Translation"]
PhysToVirt["phys_to_virt()"]
end
subgraph subGraph1["PhysFrame Access Methods"]
StartPaddr["start_paddr()"]
AsMutPtr["as_mut_ptr()"]
Fill["fill(byte)"]
end
subgraph subGraph0["Address Types"]
HostPhysAddr["HostPhysAddr"]
VirtAddr["VirtAddr"]
MutPtr["*mut u8"]
end
AsMutPtr --> PhysToVirt
Fill --> AsMutPtr
PhysToVirt --> VirtAddr
StartPaddr --> HostPhysAddr
VirtAddr --> MutPtr
Sources: src/frame.rs(L42 - L44) src/frame.rs(L46 - L48) src/frame.rs(L50 - L52)
Memory Operations
The system supports direct memory manipulation through safe abstractions:
| Operation | Method | Safety | Purpose |
|---|---|---|---|
| Get physical address | start_paddr() | Safe | VMX structure setup |
| Get virtual pointer | as_mut_ptr() | Safe | Direct memory access |
| Fill with pattern | fill(byte) | Safe | Zero-initialization |
Integration with VMX Components
VMX Data Structure Usage
Physical frames serve as the foundation for critical VMX data structures:
flowchart TD
subgraph PhysFrame<H>["PhysFrame<H>"]
CoreFrame["PhysFrame<H>4KB alignedRAII managed"]
end
subgraph subGraph1["Frame Requirements"]
VmxReq["1x 4KB frameVMXON/VMCS region"]
IOReq["2x 4KB framesPort 0-0xFFFF bitmap"]
MsrReq["1x 4KB frameMSR access bitmap"]
EPTReq["Multiple 4KB framesPage table hierarchy"]
end
subgraph subGraph0["PhysFrame Consumers"]
VmxRegion["VmxRegion"]
IOBitmap["IOBitmap"]
MsrBitmap["MsrBitmap"]
EPTStructures["EPT Page Tables"]
end
EPTReq --> CoreFrame
EPTStructures --> EPTReq
IOBitmap --> IOReq
IOReq --> CoreFrame
MsrBitmap --> MsrReq
MsrReq --> CoreFrame
VmxRegion --> VmxReq
VmxReq --> CoreFrame
Sources: src/frame.rs(L8) (PAGE_SIZE_4K constant)
Error Handling
The allocation system uses AxResult<T> for error propagation:
flowchart TD
subgraph subGraph0["Error Flow"]
AllocCall["PhysFrame::alloc()"]
HALCall["H::alloc_frame()"]
HALResult["Option<HostPhysAddr>"]
ErrorCheck["Check for None"]
ErrorResult["Err(NoMemory)"]
SuccessResult["Ok(PhysFrame)"]
end
AllocCall --> HALCall
ErrorCheck --> ErrorResult
ErrorCheck --> SuccessResult
HALCall --> HALResult
HALResult --> ErrorCheck
Sources: src/frame.rs(L19 - L27) src/frame.rs(L4) (AxResult import)
Constants and Configuration
The system uses standardized page sizing:
| Constant | Value | Source | Purpose |
|---|---|---|---|
| PAGE_SIZE | PAGE_SIZE_4K | memory_addrcrate | Standard frame size |
| Frame alignment | 4KB | Hardware requirement | VMX compatibility |
Sources: src/frame.rs(L8)
Extended Page Tables and Guest Memory
Relevant source files
Purpose and Scope
This document covers the Extended Page Tables (EPT) implementation and guest memory virtualization mechanisms in the x86_vcpu hypervisor. EPT provides hardware-assisted second-level address translation, enabling efficient memory virtualization for guest virtual machines. The document focuses on the GuestPageWalkInfo structure and how guest memory access information is captured and processed.
For physical memory allocation and the underlying frame management system, see Physical Frame Management. For overall VMX virtual machine management that utilizes EPT, see Virtual CPU Management.
Extended Page Tables Overview
Extended Page Tables (EPT) is Intel's hardware-assisted memory virtualization technology that provides a second level of address translation. In a virtualized environment, memory accesses undergo a two-stage translation process:
- Guest Virtual → Guest Physical: Performed by the guest OS page tables
- Guest Physical → Host Physical: Performed by EPT page tables managed by the hypervisor
This two-level translation allows guests to manage their own virtual memory while the hypervisor maintains control over physical memory allocation.
Memory Translation Architecture
flowchart TD
subgraph subGraph2["Hardware Translation"]
MMU["Memory Management Unit"]
PageWalk["Hardware Page Walk"]
end
subgraph subGraph1["Hypervisor Memory Management"]
EPTTables["EPT Page Tables"]
HostPA["Host Physical Address"]
PhysFrame["PhysFrame Management"]
end
subgraph subGraph0["Guest Virtual Machine"]
GuestVA["Guest Virtual Address"]
GuestPT["Guest Page Tables"]
GuestPA["Guest Physical Address"]
end
GuestPageWalkInfo["GuestPageWalkInfoCapture access details"]
EPTTables --> HostPA
GuestPA --> EPTTables
GuestPA --> PageWalk
GuestPT --> GuestPA
GuestVA --> GuestPT
HostPA --> PhysFrame
MMU --> HostPA
PageWalk --> GuestPageWalkInfo
PageWalk --> MMU
Sources: src/ept.rs(L1 - L28)
Guest Page Walk Information
The GuestPageWalkInfo structure captures detailed information about guest memory access attempts, particularly during EPT violations (nested page faults). This information is essential for the hypervisor to understand the guest's memory access patterns and handle them appropriately.
GuestPageWalkInfo Structure
flowchart TD
subgraph subGraph3["GuestPageWalkInfo Fields"]
TopEntry["top_entry: usizeTop level paging structure entry"]
Level["level: usizeGuest page table level"]
Width["width: u32Guest page table width"]
subgraph subGraph2["Security Features"]
SMAP["is_smap_on: boolSupervisor Mode Access Prevention"]
SMEP["is_smep_on: boolSupervisor Mode Execution Prevention"]
end
subgraph subGraph1["Control Register State"]
PSE["pse: boolCR4.PSE for 32bit paging"]
WP["wp: boolCR0.WP write protection"]
NXE["nxe: boolMSR_IA32_EFER_NXE_BIT"]
end
subgraph subGraph0["Access Type Information"]
UserMode["is_user_mode_access: boolUser vs kernel access"]
WriteAccess["is_write_access: boolRead vs write operation"]
InstFetch["is_inst_fetch: boolInstruction fetch access"]
end
end
AccessInfo["Memory Access Context"]
InstFetch --> AccessInfo
Level --> AccessInfo
NXE --> AccessInfo
PSE --> AccessInfo
SMAP --> AccessInfo
SMEP --> AccessInfo
TopEntry --> AccessInfo
UserMode --> AccessInfo
WP --> AccessInfo
Width --> AccessInfo
WriteAccess --> AccessInfo
Sources: src/ept.rs(L3 - L27)
Key Fields and Their Purpose
| Field | Purpose | Usage Context |
|---|---|---|
| top_entry | Physical address of the top-level page table | Identifies the root of guest page table hierarchy |
| level | Current page table level being accessed | Determines page size and translation depth |
| width | Page table entry width (32/64-bit) | Architecture-specific page table format |
| is_user_mode_access | User vs supervisor mode access | Privilege level validation |
| is_write_access | Read vs write operation | Access permission checking |
| is_inst_fetch | Instruction fetch vs data access | Execution permission validation |
| pse | Page Size Extension enabled | Support for large pages (2MB/4MB) |
| wp | Write Protection enabled | CR0.WP supervisor write protection |
| nxe | No-Execute bit enabled | Execution prevention support |
| is_smap_on | SMAP protection active | Prevents supervisor access to user pages |
| is_smep_on | SMEP protection active | Prevents supervisor execution of user pages |
Sources: src/ept.rs(L4 - L26)
EPT Integration with VMX System
flowchart TD
subgraph subGraph2["Physical Memory"]
PhysFrame["PhysFrame"]
AxVCpuHal["AxVCpuHal Trait"]
HostMemory["Host Physical Memory"]
end
subgraph subGraph1["Memory Management Layer"]
EPTPointer["EPT Pointer(VMCS Field)"]
EPTPageTables["EPT Page Tables"]
GuestPageWalkInfo["GuestPageWalkInfo"]
end
subgraph subGraph0["VMX Virtual Machine"]
VmxVcpu["VmxVcpu"]
VMExit["VM Exit Handler"]
EPTViolation["EPT Violation Handler"]
end
note1["EPT violations trigger VM exitswith GuestPageWalkInfo details"]
AxVCpuHal --> HostMemory
EPTPageTables --> PhysFrame
EPTPointer --> EPTPageTables
EPTViolation --> GuestPageWalkInfo
EPTViolation --> note1
GuestPageWalkInfo --> VMExit
PhysFrame --> AxVCpuHal
VMExit --> EPTViolation
VmxVcpu --> EPTPointer
Sources: src/ept.rs(L1 - L28)
Memory Access Handling Flow
The GuestPageWalkInfo structure plays a crucial role in handling memory access violations and implementing memory virtualization policies:
EPT Violation Processing
- Guest Memory Access: Guest attempts to access memory through its virtual address space
- Hardware Translation: MMU performs guest virtual → guest physical translation
- EPT Lookup: Hardware attempts guest physical → host physical translation via EPT
- EPT Violation: If EPT entry is missing or access violates permissions, VM exit occurs
- Information Capture: Hardware populates
GuestPageWalkInfowith access details - Hypervisor Handling: VMX exit handler processes the violation using captured information
Security Feature Integration
The structure captures several x86 security features that affect memory access:
- SMAP (Supervisor Mode Access Prevention): Prevents supervisor code from accessing user-mode pages
- SMEP (Supervisor Mode Execution Prevention): Prevents supervisor code from executing user-mode pages
- NXE (No-Execute): Enables execution prevention for marked pages
- WP (Write Protection): Controls supervisor write access to read-only pages
These features are essential for maintaining security boundaries in virtualized environments.
Sources: src/ept.rs(L19 - L26)
Current Implementation Status
The current EPT implementation in the codebase provides the foundational data structure for capturing guest page walk information. The GuestPageWalkInfo struct serves as the interface between the hardware EPT violation mechanism and the hypervisor's memory management logic.
The minimal implementation suggests that:
- EPT page table management may be handled in other components
- The focus is on capturing and processing EPT violation information
- Integration with the broader VMX system occurs through other modules
For the complete memory virtualization picture, this EPT information works in conjunction with the physical frame management system documented in Physical Frame Management and the VMX virtual machine control structures covered in VMX Data Structures.
Sources: src/ept.rs(L1 - L28)
Supporting Systems
Relevant source files
This document covers the foundational support systems that enable VMX functionality in the x86_vcpu hypervisor library. These systems provide low-level abstractions for register management, Model-Specific Register (MSR) access, VMX type definitions, and instruction wrappers that are consumed by the core VMX virtualization engine.
For information about the main VMX virtualization components, see VMX Virtualization Engine. For memory management abstractions, see Memory Management.
Architecture Overview
The supporting systems form the foundation layer beneath the VMX virtualization engine, providing essential abstractions for hardware interaction and type safety.
flowchart TD
subgraph subGraph6["Hardware Layer"]
X86Hardware["x86_64 Hardware"]
VmxInstructions["VMX Instructions"]
Msrs["Model-Specific Registers"]
end
subgraph subGraph5["Supporting Systems Layer"]
subgraph subGraph4["VMX Instructions"]
InveptWrapper["invept wrapper"]
VmxStatusHandling["VMX status handling"]
end
subgraph subGraph3["VMX Definitions"]
VmxExitReason["VmxExitReason enum"]
VmxInterruptionType["VmxInterruptionType enum"]
VmxInstructionError["VmxInstructionError struct"]
end
subgraph subGraph2["MSR Access"]
MsrEnum["Msr enum"]
MsrReadWrite["MsrReadWrite trait"]
RdmsrWrapper["rdmsr wrapper"]
WrmsrWrapper["wrmsr wrapper"]
end
subgraph subGraph1["Register Management"]
GeneralRegisters["GeneralRegisters"]
SaveRegsMacro["save_regs_to_stack!"]
RestoreRegsMacro["restore_regs_from_stack!"]
end
end
subgraph subGraph0["VMX Virtualization Engine"]
VmxVcpu["VmxVcpu"]
VmxModule["vmx::mod"]
VmcsManager["vmcs management"]
end
GeneralRegisters --> RestoreRegsMacro
GeneralRegisters --> SaveRegsMacro
InveptWrapper --> VmxInstructions
MsrEnum --> RdmsrWrapper
MsrEnum --> WrmsrWrapper
MsrReadWrite --> MsrEnum
RdmsrWrapper --> Msrs
RestoreRegsMacro --> X86Hardware
SaveRegsMacro --> X86Hardware
VmcsManager --> VmxInstructionError
VmxModule --> VmxExitReason
VmxVcpu --> GeneralRegisters
VmxVcpu --> MsrEnum
WrmsrWrapper --> Msrs
Sources: src/regs.rs(L1 - L197) src/msr.rs(L1 - L74) src/vmx/definitions.rs(L1 - L275)
Register Management System
The register management system provides abstractions for handling x86-64 general-purpose registers and register state transitions during VM exits and entries.
GeneralRegisters Structure
The GeneralRegisters struct represents the complete set of x86-64 general-purpose registers in a VM context:
| Register | Purpose | Index |
|---|---|---|
| rax | Return values, accumulator | 0 |
| rcx | Counter, function arguments | 1 |
| rdx | I/O operations, function arguments | 2 |
| rbx | Base pointer, preserved across calls | 3 |
| rbp | Frame pointer | 5 |
| rsi | Source index for string operations | 6 |
| rdi | Destination index for string operations | 7 |
| r8-r15 | Additional 64-bit registers | 8-15 |
The structure excludes rsp (index 4) as it requires special handling during context switches. The get_reg_of_index() and set_reg_of_index() methods provide indexed access to registers, enabling dynamic register manipulation based on instruction operands.
Register Save/Restore Macros
flowchart TD
subgraph subGraph2["Stack Layout"]
StackTop["Stack Top"]
RaxSlot["rax"]
RcxSlot["rcx"]
RdxSlot["rdx"]
RbxSlot["rbx"]
RspGap["8-byte gap (rsp)"]
RbpSlot["rbp"]
OtherRegs["rsi, rdi, r8-r15"]
StackBottom["Stack Bottom"]
end
subgraph subGraph1["VM Entry Flow"]
RestoreMacro["restore_regs_from_stack!"]
VmEntry["VM Entry Event"]
RegistersRestored["Registers Restored"]
end
subgraph subGraph0["VM Exit Flow"]
VmExit["VM Exit Event"]
SaveMacro["save_regs_to_stack!"]
StackState["Register State on Stack"]
end
OtherRegs --> StackBottom
RaxSlot --> RcxSlot
RbpSlot --> OtherRegs
RbxSlot --> RspGap
RcxSlot --> RdxSlot
RdxSlot --> RbxSlot
RestoreMacro --> VmEntry
RspGap --> RbpSlot
SaveMacro --> StackState
StackState --> RestoreMacro
StackState --> StackTop
StackTop --> RaxSlot
VmEntry --> RegistersRestored
VmExit --> SaveMacro
The macros implement precise stack-based register preservation:
save_regs_to_stack!pushes registers in reverse order (r15 to rax)- An 8-byte gap maintains stack alignment where
rspwould be stored restore_regs_from_stack!pops registers in forward order (rax to r15)
Sources: src/regs.rs(L1 - L197)
MSR Access System
The MSR access system provides type-safe abstractions for reading and writing Model-Specific Registers critical to VMX operation.
MSR Enumeration
The Msr enum defines VMX-related MSRs with their canonical addresses:
flowchart TD
subgraph subGraph3["MSR Access Methods"]
ReadMethod["Msr::read()"]
WriteMethod["Msr::write()"]
RdmsrCall["x86::msr::rdmsr()"]
WrmsrCall["x86::msr::wrmsr()"]
end
subgraph subGraph2["System MSRs"]
FeatureControl["IA32_FEATURE_CONTROL (0x3a)"]
Efer["IA32_EFER (0xc0000080)"]
Pat["IA32_PAT (0x277)"]
FsBase["IA32_FS_BASE (0xc0000100)"]
GsBase["IA32_GS_BASE (0xc0000101)"]
end
subgraph subGraph0["VMX Control MSRs"]
PinbasedMsr["IA32_VMX_PINBASED_CTLS (0x481)"]
ProcbasedMsr["IA32_VMX_PROCBASED_CTLS (0x482)"]
ExitMsr["IA32_VMX_EXIT_CTLS (0x483)"]
EntryMsr["IA32_VMX_ENTRY_CTLS (0x484)"]
subgraph subGraph1["VMX Capability MSRs"]
MiscMsr["IA32_VMX_MISC (0x485)"]
Cr0Fixed0["IA32_VMX_CR0_FIXED0 (0x486)"]
Cr0Fixed1["IA32_VMX_CR0_FIXED1 (0x487)"]
Cr4Fixed0["IA32_VMX_CR4_FIXED0 (0x488)"]
Cr4Fixed1["IA32_VMX_CR4_FIXED1 (0x489)"]
EptVpidCap["IA32_VMX_EPT_VPID_CAP (0x48c)"]
BasicMsr["IA32_VMX_BASIC (0x480)"]
end
end
BasicMsr --> ReadMethod
FeatureControl --> WriteMethod
ReadMethod --> RdmsrCall
WriteMethod --> WrmsrCall
MsrReadWrite Trait Pattern
The MsrReadWrite trait provides a type-safe pattern for MSR access:
trait MsrReadWrite {
const MSR: Msr;
fn read_raw() -> u64;
unsafe fn write_raw(flags: u64);
}
This pattern enables implementing types to associate with specific MSRs while providing consistent read/write interfaces. The trait methods delegate to the underlying Msr::read() and Msr::write() implementations.
Sources: src/msr.rs(L1 - L74)
VMX Type Definitions
The VMX definitions provide comprehensive type safety for VMX exit handling and interrupt processing.
Exit Reason Classification
flowchart TD
subgraph subGraph3["Control/Memory Exits (28-49)"]
CrAccess["CR_ACCESS (28)"]
DrAccess["DR_ACCESS (29)"]
IoInstruction["IO_INSTRUCTION (30)"]
MsrRead["MSR_READ (31)"]
MsrWrite["MSR_WRITE (32)"]
EptViolation["EPT_VIOLATION (48)"]
EptMisconfig["EPT_MISCONFIG (49)"]
end
subgraph subGraph2["Instruction Exits (10-27)"]
CpuidExit["CPUID (10)"]
HltExit["HLT (12)"]
VmcallExit["VMCALL (18)"]
VmxInstructionExits["VMX Instructions (19-26)"]
end
subgraph subGraph1["Exception/Interrupt (0-8)"]
ExceptionNmi["EXCEPTION_NMI (0)"]
ExternalInt["EXTERNAL_INTERRUPT (1)"]
TripleFault["TRIPLE_FAULT (2)"]
InitSignal["INIT (3)"]
SipiSignal["SIPI (4)"]
end
subgraph subGraph0["VmxExitReason Categories"]
ExceptionCategory["Exception/Interrupt Exits"]
InstructionCategory["Instruction-Based Exits"]
ControlCategory["Control Register Exits"]
SystemCategory["System Management Exits"]
end
ControlCategory --> CrAccess
ControlCategory --> EptViolation
ExceptionCategory --> ExceptionNmi
ExceptionCategory --> ExternalInt
InstructionCategory --> CpuidExit
InstructionCategory --> VmcallExit
Interruption Type System
The VmxInterruptionType enum classifies interrupt and exception events for VM-entry and VM-exit processing:
| Type | Value | Description | Error Code |
|---|---|---|---|
| External | 0 | External hardware interrupt | No |
| NMI | 2 | Non-maskable interrupt | No |
| HardException | 3 | Hardware exception (#PF, #GP) | Some vectors |
| SoftIntr | 4 | Software interrupt (INT n) | No |
| PrivSoftException | 5 | Privileged software exception (INT1) | No |
| SoftException | 6 | Software exception (INT3, INTO) | No |
The vector_has_error_code() method determines if specific exception vectors push error codes, while from_vector() automatically classifies interruption types based on vector numbers.
Error Handling
The VmxInstructionError struct provides human-readable descriptions for VMX instruction failures, mapping error codes to detailed explanations per the Intel SDM specifications.
Sources: src/vmx/definitions.rs(L1 - L275)
Integration Patterns
The supporting systems integrate with the core VMX engine through several key patterns:
flowchart TD
subgraph subGraph3["Integration Flow"]
VmxVcpu["VmxVcpu"]
subgraph subGraph2["Exit Processing"]
ExitReasonCheck["exit_reason matching"]
InterruptClassify["interruption classification"]
ErrorDiagnostics["instruction error handling"]
end
subgraph subGraph1["MSR Integration"]
MsrOperations["MSR read/write operations"]
VmcsFieldAccess["VMCS field access"]
ControlRegSetup["Control register setup"]
end
subgraph subGraph0["Register Integration"]
GuestRegs["guest_registers: GeneralRegisters"]
RegAccess["get_reg_of_index()"]
RegModify["set_reg_of_index()"]
end
end
ExitReasonCheck --> ErrorDiagnostics
ExitReasonCheck --> InterruptClassify
GuestRegs --> RegAccess
GuestRegs --> RegModify
MsrOperations --> ControlRegSetup
MsrOperations --> VmcsFieldAccess
VmxVcpu --> ExitReasonCheck
VmxVcpu --> GuestRegs
VmxVcpu --> MsrOperations
The supporting systems provide the essential building blocks that enable the VMX virtualization engine to:
- Maintain guest register state across VM transitions
- Configure VMX controls through MSR capabilities
- Process VM exits with type-safe reason classification
- Handle errors with detailed diagnostic information
Sources: src/regs.rs(L1 - L197) src/msr.rs(L1 - L74) src/vmx/definitions.rs(L1 - L275)
Register Management
Relevant source files
Purpose and Scope
The register management system provides abstractions for handling x86-64 general-purpose registers within the hypervisor context. This module defines the GeneralRegisters structure for storing and manipulating register state, along with assembly macros for efficient stack-based register preservation. These components are essential for managing guest CPU state during VM exits and entries in the VMX virtualization engine.
For information about model-specific register (MSR) access, see MSR Access. For VMX-specific register handling during VM exits, see Virtual CPU Management.
GeneralRegisters Structure
The GeneralRegisters struct provides a C-compatible representation of the x86-64 general-purpose register set. This structure serves as the primary container for guest register state during hypervisor operations.
flowchart TD
subgraph subGraph1["Key Features"]
CRepr["#[repr(C)]C ABI Compatible"]
Debug["#[derive(Debug)]Debug output"]
Default["#[derive(Default)]Zero initialization"]
Clone["#[derive(Clone)]Copy semantics"]
end
subgraph subGraph0["GeneralRegisters Structure"]
RAX["rax: u64Return values"]
RCX["rcx: u64Loop counter"]
RDX["rdx: u64I/O operations"]
RBX["rbx: u64Base pointer"]
RSP["_unused_rsp: u64(Padding)"]
RBP["rbp: u64Frame pointer"]
RSI["rsi: u64Source index"]
RDI["rdi: u64Dest index"]
R8["r8: u64GP register"]
R9["r9: u64GP register"]
R10["r10: u64GP register"]
R11["r11: u64GP register"]
R12["r12: u64GP register"]
R13["r13: u64GP register"]
R14["r14: u64GP register"]
R15["r15: u64GP register"]
end
CRepr --> RAX
Clone --> RAX
Debug --> RAX
Default --> RAX
Register Layout and Characteristics:
| Register | Purpose | Index | Notes |
|---|---|---|---|
| rax | Return values, accumulator | 0 | Primary return register |
| rcx | Loop counter, 4th argument | 1 | Often used in loops |
| rdx | I/O operations, 3rd argument | 2 | Data register |
| rbx | Base pointer, callee-saved | 3 | Preserved across calls |
| _unused_rsp | Stack pointer (unused) | 4 | Excluded from direct access |
| rbp | Frame pointer, callee-saved | 5 | Stack frame base |
| rsi | Source index, 2nd argument | 6 | String operations source |
| rdi | Destination index, 1st argument | 7 | String operations destination |
| r8-r15 | Extended registers | 8-15 | Additional 64-bit registers |
Sources: src/regs.rs(L1 - L40)
Index-Based Register Access
The GeneralRegisters struct provides index-based access methods that map numerical indices to specific registers, following the x86-64 register encoding scheme used in instruction operands.
flowchart TD
subgraph subGraph1["Access Methods"]
GetReg["get_reg_of_index(index: u8) -> u64"]
SetReg["set_reg_of_index(index: u8, value: u64)"]
end
subgraph subGraph0["Register Index Mapping"]
I0["Index 0"]
RAX["rax"]
I1["Index 1"]
RCX["rcx"]
I2["Index 2"]
RDX["rdx"]
I3["Index 3"]
RBX["rbx"]
I4["Index 4"]
SKIP["(unused - rsp)"]
I5["Index 5"]
RBP["rbp"]
I6["Index 6"]
RSI["rsi"]
I7["Index 7"]
RDI["rdi"]
I8["Index 8"]
R8["r8"]
I9["Index 9"]
R9["r9"]
I10["Index 10"]
R10["r10"]
I11["Index 11"]
R11["r11"]
I12["Index 12"]
R12["r12"]
I13["Index 13"]
R13["r13"]
I14["Index 14"]
R14["r14"]
I15["Index 15"]
R15["r15"]
end
GetReg --> SetReg
I0 --> GetReg
I0 --> RAX
I1 --> RCX
I10 --> R10
I11 --> R11
I12 --> R12
I13 --> R13
I14 --> R14
I15 --> R15
I2 --> RDX
I3 --> RBX
I4 --> SKIP
I5 --> RBP
I6 --> RSI
I7 --> RDI
I8 --> R8
I9 --> R9
Key Implementation Details:
- Index Range: Valid indices are 0-15, excluding index 4 (RSP)
- Panic Behavior: Both methods panic on invalid indices or attempts to access RSP
- Performance: Direct match statements provide efficient O(1) access
- Safety: Index validation prevents undefined behavior
Usage Pattern:
// Reading a register by index
let rax_value = registers.get_reg_of_index(0); // Gets RAX
// Writing a register by index
registers.set_reg_of_index(8, 0x12345678); // Sets R8
Sources: src/regs.rs(L42 - L152)
Stack-Based Register Preservation
The module provides assembly macros for efficient bulk register save and restore operations, essential for hypervisor context switching and interrupt handling.
sequenceDiagram
participant HypervisorCode as "Hypervisor Code"
participant StackMemory as "Stack Memory"
participant CPURegisters as "CPU Registers"
Note over HypervisorCode: save_regs_to_stack!()
HypervisorCode ->> StackMemory: push r15
HypervisorCode ->> StackMemory: push r14
HypervisorCode ->> StackMemory: push r13
HypervisorCode ->> StackMemory: push r12
HypervisorCode ->> StackMemory: push r11
HypervisorCode ->> StackMemory: push r10
HypervisorCode ->> StackMemory: push r9
HypervisorCode ->> StackMemory: push r8
HypervisorCode ->> StackMemory: push rdi
HypervisorCode ->> StackMemory: push rsi
HypervisorCode ->> StackMemory: push rbp
HypervisorCode ->> StackMemory: sub rsp, 8 (skip rsp)
HypervisorCode ->> StackMemory: push rbx
HypervisorCode ->> StackMemory: push rdx
HypervisorCode ->> StackMemory: push rcx
HypervisorCode ->> StackMemory: push rax
Note over HypervisorCode: Critical section executes
Note over HypervisorCode: restore_regs_from_stack!()
StackMemory ->> CPURegisters: pop rax
StackMemory ->> CPURegisters: pop rcx
StackMemory ->> CPURegisters: pop rdx
StackMemory ->> CPURegisters: pop rbx
HypervisorCode ->> HypervisorCode: add rsp, 8 (skip rsp)
StackMemory ->> CPURegisters: pop rbp
StackMemory ->> CPURegisters: pop rsi
StackMemory ->> CPURegisters: pop rdi
StackMemory ->> CPURegisters: pop r8
StackMemory ->> CPURegisters: pop r9
StackMemory ->> CPURegisters: pop r10
StackMemory ->> CPURegisters: pop r11
StackMemory ->> CPURegisters: pop r12
StackMemory ->> CPURegisters: pop r13
StackMemory ->> CPURegisters: pop r14
StackMemory ->> CPURegisters: pop r15
Macro Implementation:
| Macro | Purpose | Stack Operations | RSP Handling |
|---|---|---|---|
| save_regs_to_stack! | Preserve all GP registers | 15 push operations + 1 subtract | sub rsp, 8to skip RSP slot |
| restore_regs_from_stack! | Restore all GP registers | 15 pop operations + 1 add | add rsp, 8to skip RSP slot |
Key Features:
- Order Preservation: Restore operations reverse the save order exactly
- RSP Handling: Stack pointer excluded from save/restore, space reserved with arithmetic
- Atomicity: Complete register set saved/restored as a unit
- Performance: Inline assembly provides minimal overhead
Sources: src/regs.rs(L154 - L196)
Integration with VMX System
The register management components integrate tightly with the VMX virtualization engine to support guest state management during VM transitions.
flowchart TD
subgraph subGraph2["VMX Components"]
VmxVcpu["VmxVcpu"]
VMCS["VMCS Fields"]
ExitHandler["Exit Handlers"]
end
subgraph subGraph1["Register Operations"]
SaveMacro["save_regs_to_stack!()"]
RestoreMacro["restore_regs_from_stack!()"]
GeneralRegs["GeneralRegisters"]
IndexAccess["Index-based Access"]
end
subgraph subGraph0["VMX Context Usage"]
VmExit["VM Exit Handler"]
VmEntry["VM Entry Preparation"]
GuestState["Guest Register State"]
HostState["Host Register State"]
end
ExitHandler --> IndexAccess
GeneralRegs --> IndexAccess
GuestState --> GeneralRegs
RestoreMacro --> HostState
SaveMacro --> GuestState
VMCS --> GuestState
VmEntry --> RestoreMacro
VmExit --> SaveMacro
VmxVcpu --> VmEntry
VmxVcpu --> VmExit
Integration Points:
- VM Exit Processing: Register state captured using stack macros during VM exits
- Guest State Management:
GeneralRegistersstores guest CPU state between VM operations - Instruction Emulation: Index-based access enables register modification for emulated instructions
- Context Switching: Stack preservation ensures host state integrity during guest execution
Performance Considerations:
- Zero-Copy: Direct register field access avoids unnecessary copying
- Cache Efficiency: C-compatible layout optimizes memory access patterns
- Assembly Integration: Macros provide efficient bulk operations for critical paths
Sources: src/regs.rs(L1 - L196)
Model-Specific Register Access
Relevant source files
This document covers the Model-Specific Register (MSR) access abstraction layer in the x86_vcpu hypervisor library. MSRs are processor-specific configuration and status registers that control various aspects of CPU behavior, particularly important for VMX virtualization features.
The MSR access system provides a type-safe abstraction over x86 MSR operations, with a focus on VMX-related registers and other MSRs essential for hypervisor operation. For information about VMX-specific data structures and configuration, see VMX Data Structures. For details about VMCS field management, see VMCS Field Management.
MSR Access Architecture
The MSR access system is built around two main components: the Msr enum that defines specific MSR constants, and the MsrReadWrite trait that provides a higher-level abstraction for typed MSR access.
flowchart TD
subgraph subGraph3["VMX Usage"]
VmxInit["VMX InitializationFeature Detection"]
VmxConfig["VMX ConfigurationControl Settings"]
GuestState["Guest StateBase Registers"]
end
subgraph subGraph2["Hardware Interface"]
X86Crate["x86 craterdmsr() / wrmsr()"]
Hardware["x86 MSR HardwarePhysical Registers"]
end
subgraph subGraph1["MSR Access Layer"]
MsrEnum["Msr enumMSR Definitions"]
MsrReadWriteTrait["MsrReadWrite traitType-safe Access"]
subgraph subGraph0["Core Methods"]
ReadMethod["read()Safe MSR Read"]
WriteMethod["write()Unsafe MSR Write"]
ReadRaw["read_raw()Trait Default"]
WriteRaw["write_raw()Trait Default"]
end
end
GuestState --> MsrEnum
MsrEnum --> ReadMethod
MsrEnum --> WriteMethod
MsrReadWriteTrait --> ReadRaw
MsrReadWriteTrait --> WriteRaw
ReadMethod --> X86Crate
ReadRaw --> ReadMethod
VmxConfig --> MsrEnum
VmxInit --> MsrEnum
WriteMethod --> X86Crate
WriteRaw --> WriteMethod
X86Crate --> Hardware
Sources: src/msr.rs(L1 - L74)
MSR Definitions and Categories
The Msr enum defines specific MSR constants organized by functional categories. Each MSR is represented as a variant with its corresponding hardware register number.
VMX Control and Capability MSRs
The largest category of MSRs relates to VMX virtualization capabilities and control settings:
| MSR Name | Value | Purpose |
|---|---|---|
| IA32_VMX_BASIC | 0x480 | Basic VMX capabilities |
| IA32_VMX_PINBASED_CTLS | 0x481 | Pin-based VM execution controls |
| IA32_VMX_PROCBASED_CTLS | 0x482 | Primary processor-based controls |
| IA32_VMX_EXIT_CTLS | 0x483 | VM exit controls |
| IA32_VMX_ENTRY_CTLS | 0x484 | VM entry controls |
| IA32_VMX_PROCBASED_CTLS2 | 0x48b | Secondary processor-based controls |
| IA32_VMX_EPT_VPID_CAP | 0x48c | EPT and VPID capabilities |
flowchart TD
subgraph subGraph5["VMX MSR Categories"]
subgraph subGraph2["True Controls"]
TruePinBased["IA32_VMX_TRUE_PINBASED_CTLS0x48d"]
TrueProcBased["IA32_VMX_TRUE_PROCBASED_CTLS0x48e"]
TrueExit["IA32_VMX_TRUE_EXIT_CTLS0x48f"]
TrueEntry["IA32_VMX_TRUE_ENTRY_CTLS0x490"]
end
subgraph subGraph1["Control Capabilities"]
PinBased["IA32_VMX_PINBASED_CTLS0x481"]
ProcBased["IA32_VMX_PROCBASED_CTLS0x482"]
ProcBased2["IA32_VMX_PROCBASED_CTLS20x48b"]
ExitCtls["IA32_VMX_EXIT_CTLS0x483"]
EntryCtls["IA32_VMX_ENTRY_CTLS0x484"]
end
subgraph subGraph0["Basic VMX Info"]
VmxBasic["IA32_VMX_BASIC0x480Core VMX Features"]
VmxMisc["IA32_VMX_MISC0x485Miscellaneous Data"]
end
subgraph subGraph4["Memory Virtualization"]
EptVpid["IA32_VMX_EPT_VPID_CAP0x48cEPT & VPID Features"]
Cr0Fixed0["IA32_VMX_CR0_FIXED00x486"]
end
subgraph subGraph3["Fixed Settings"]
EptVpid["IA32_VMX_EPT_VPID_CAP0x48cEPT & VPID Features"]
Cr0Fixed0["IA32_VMX_CR0_FIXED00x486"]
Cr0Fixed1["IA32_VMX_CR0_FIXED10x487"]
Cr4Fixed0["IA32_VMX_CR4_FIXED00x488"]
Cr4Fixed1["IA32_VMX_CR4_FIXED10x489"]
end
end
EntryCtls --> TrueEntry
ExitCtls --> TrueExit
PinBased --> TruePinBased
ProcBased --> ProcBased2
ProcBased --> TrueProcBased
VmxBasic --> EntryCtls
VmxBasic --> ExitCtls
VmxBasic --> PinBased
VmxBasic --> ProcBased
Sources: src/msr.rs(L8 - L28)
System Configuration MSRs
Additional MSRs handle general x86 system configuration:
- Feature Control:
IA32_FEATURE_CONTROL(0x3a) - Controls processor feature enablement - Memory Management:
IA32_PAT(0x277) - Page Attribute Table configuration - Extended State:
IA32_XSS(0xda0) - Extended State Save configuration
Long Mode and Segment MSRs
MSRs for 64-bit mode operation and segment base addresses:
- Mode Control:
IA32_EFER(0xc0000080) - Extended Feature Enable Register - System Call:
IA32_STAR,IA32_LSTAR,IA32_CSTAR,IA32_FMASK- System call configuration - Segment Bases:
IA32_FS_BASE,IA32_GS_BASE,IA32_KERNEL_GSBASE- Segment base addresses
Sources: src/msr.rs(L8 - L40)
MSR Access Interface
The Msr enum provides direct read and write methods for MSR access:
Read Operations
The read() method provides safe MSR reading:
#![allow(unused)] fn main() { pub fn read(self) -> u64 }
This method is marked as #[inline(always)] for performance and uses the x86 crate's rdmsr function internally. Reading MSRs is generally safe as it does not modify processor state.
Write Operations
The write() method provides unsafe MSR writing:
#![allow(unused)] fn main() { pub unsafe fn write(self, value: u64) }
Writing to MSRs is marked as unsafe because incorrect values can cause system instability, crashes, or security vulnerabilities. The caller must ensure that the write operation has no unsafe side effects.
Sources: src/msr.rs(L42 - L59)
MsrReadWrite Trait
The MsrReadWrite trait provides a higher-level abstraction for types that represent specific MSR functionality:
flowchart TD
subgraph subGraph3["MsrReadWrite Trait Pattern"]
TraitDef["MsrReadWrite traitGeneric MSR Access"]
subgraph subGraph2["Implementing Types"]
VmxStructs["VMX Control StructuresType-safe Access"]
OtherTypes["Other MSR WrappersDomain-specific Logic"]
end
subgraph subGraph1["Default Implementations"]
ReadRawMethod["read_raw() -> u64Self::MSR.read()"]
WriteRawMethod["write_raw(flags: u64)Self::MSR.write(flags)"]
end
subgraph subGraph0["Required Definition"]
MsrConst["const MSR: MsrAssociated MSR"]
end
end
OtherTypes --> TraitDef
ReadRawMethod --> MsrConst
TraitDef --> MsrConst
TraitDef --> ReadRawMethod
TraitDef --> WriteRawMethod
VmxStructs --> TraitDef
WriteRawMethod --> MsrConst
The trait defines:
- Associated MSR:
const MSR: Msr- Links the implementing type to a specific MSR - Raw Read:
read_raw() -> u64- Default implementation callsSelf::MSR.read() - Raw Write:
unsafe write_raw(flags: u64)- Default implementation callsSelf::MSR.write(flags)
This pattern allows creating type-safe wrappers around specific MSRs while maintaining the same underlying access interface.
Sources: src/msr.rs(L61 - L73)
Integration with VMX System
The MSR access system integrates closely with the VMX virtualization engine:
flowchart TD
subgraph subGraph1["Guest Configuration"]
GuestBase["Guest Segment BasesIA32_*S_BASE"]
GuestEfer["Guest EFERIA32_EFER"]
GuestPat["Guest PATIA32_PAT"]
end
subgraph subGraph0["VMX Initialization Flow"]
FeatureCheck["Feature DetectionIA32_FEATURE_CONTROL"]
BasicCaps["Capability ReadingIA32_VMX_BASIC"]
ControlCaps["Control CapabilitiesIA32_VMX_*_CTLS"]
FixedBits["Fixed Bit RequirementsIA32_VMX_CR*_FIXED*"]
end
BasicCaps --> ControlCaps
ControlCaps --> FixedBits
FeatureCheck --> BasicCaps
FixedBits --> GuestBase
FixedBits --> GuestEfer
FixedBits --> GuestPat
The MSR system enables:
- Hardware Capability Discovery - Reading VMX capability MSRs to determine supported features
- Control Register Validation - Using fixed bit MSRs to validate CR0/CR4 settings
- Guest State Management - Configuring guest segment bases and control registers
- Feature Control - Managing processor feature enablement through dedicated MSRs
Sources: src/msr.rs(L8 - L40)
VMX Definitions and Types
Relevant source files
Purpose and Scope
This document covers the core VMX (Virtual Machine Extensions) definitions and enumerations that form the foundation of Intel VMX virtualization. These types define the various exit reasons, error conditions, and interrupt classifications used throughout the VMX virtualization engine.
For information about how these definitions are used in practice, see Virtual CPU Management and VMCS Field Management. For low-level VMX instruction wrappers that utilize these types, see VMX Instructions.
VM Instruction Error Types
The VmxInstructionError struct provides human-readable descriptions for VMX instruction failures. VMX operations can fail for various reasons, and this type maps the numeric error codes to descriptive strings.
Error Classification
VMX Instruction Error Handling
flowchart TD
subgraph subGraph1["Error Categories"]
StateErrors["State Errors• VMLAUNCH with non-clear VMCS• VMRESUME with non-launched VMCS• VMXON in VMX root operation"]
AddressErrors["Address Errors• VMCLEAR with invalid address• VMPTRLD with invalid address• VMCLEAR with VMXON pointer"]
ConfigErrors["Configuration Errors• Invalid control fields• Invalid host-state fields• Read-only VMCS component write"]
AccessErrors["Access Errors• Unsupported VMCS component• Invalid operand to INVEPT/INVVPID• VMREAD/VMWRITE errors"]
end
subgraph subGraph0["VMX Operation Flow"]
VmxOp["VMX Instruction"]
Success["Operation Success"]
Error["VmxInstructionError"]
ErrorCode["u32 Error Code"]
ErrorStr["Human Readable String"]
end
Error --> AccessErrors
Error --> AddressErrors
Error --> ConfigErrors
Error --> ErrorCode
Error --> StateErrors
ErrorCode --> ErrorStr
VmxOp --> Error
VmxOp --> Success
The error mapping covers 28 distinct error conditions defined in the Intel SDM Volume 3C, Section 30.4. Key error categories include:
| Error Type | Examples | Impact |
|---|---|---|
| State Violations | VMLAUNCH with non-clear VMCS (4) | VM entry failure |
| Address Issues | Invalid physical addresses (2, 18) | Memory access violations |
| Configuration | Invalid control fields (7, 8) | VM setup failure |
| Access Control | Read-only component write (13) | VMCS corruption prevention |
Sources: src/vmx/definitions.rs(L3 - L60)
VM Exit Reasons
The VmxExitReason enum defines all possible reasons for VM exits in Intel VMX. This comprehensive enumeration covers 70 distinct exit reasons ranging from basic exceptions to advanced virtualization features.
Exit Reason Categories
VM Exit Reason Classification
flowchart TD
subgraph subGraph1["Exit Handling Flow"]
VmExit["VM Exit Occurs"]
ExitReason["Read Exit Reason"]
Handler["Route to Handler"]
Builtin["Built-in Handler"]
External["External Handler"]
end
subgraph subGraph0["VmxExitReason Categories"]
Exceptions["Exception/Interrupt Exits• EXCEPTION_NMI (0)• EXTERNAL_INTERRUPT (1)• TRIPLE_FAULT (2)• INIT (3), SIPI (4)"]
Instructions["Instruction-Specific Exits• CPUID (10)• HLT (12)• RDTSC (16)• VMCALL (18)• CR_ACCESS (28)• MSR_READ/WRITE (31/32)"]
Virtualization["Virtualization Features• EPT_VIOLATION (48)• EPT_MISCONFIG (49)• APIC_ACCESS (44)• VIRTUALIZED_EOI (45)• PREEMPTION_TIMER (52)"]
Advanced["Advanced Instructions• INVEPT (50)• INVVPID (53)• XSETBV (55)• XSAVES/XRSTORS (63/64)• ENCLS (60)"]
end
Advanced --> ExitReason
Exceptions --> ExitReason
ExitReason --> Handler
Handler --> Builtin
Handler --> External
Instructions --> ExitReason
Virtualization --> ExitReason
VmExit --> ExitReason
Common Exit Reasons
The most frequently handled exit reasons include:
- CPUID (10): Guest queries processor capabilities
- CR_ACCESS (28): Control register modifications
- MSR_READ/WRITE (31/32): Model-specific register access
- EPT_VIOLATION (48): Memory access violations in nested paging
- IO_INSTRUCTION (30): I/O port access
- HLT (12): CPU halt instruction
Sources: src/vmx/definitions.rs(L62 - L208)
Interruption Types
The VmxInterruptionType enum classifies interrupt and exception types for both VM-Entry and VM-Exit interruption information fields as defined in Intel SDM Volume 3C, Sections 24.8.3 and 24.9.2.
Interruption Classification
VMX Interruption Type System
flowchart TD
subgraph subGraph2["Error Code Vectors"]
ErrorVectors["Vectors with Error Codes• Double Fault (8)• Invalid TSS (10)• Segment Not Present (11)• Stack Fault (12)• General Protection (13)• Page Fault (14)• Alignment Check (17)"]
end
subgraph subGraph1["Vector Processing"]
Vector["Interrupt Vector"]
TypeDetermination["from_vector()"]
ErrorCheck["vector_has_error_code()"]
SoftCheck["is_soft()"]
end
subgraph subGraph0["VmxInterruptionType Variants"]
External["External (0)Hardware interruptsVectors 32-255"]
NMI["NMI (2)Non-maskable interruptVector 2"]
HardException["HardException (3)CPU exceptionsVectors 0-21"]
SoftIntr["SoftIntr (4)Software interruptINT n instruction"]
PrivSoft["PrivSoftException (5)Privileged softwareINT1/Debug"]
SoftException["SoftException (6)Software exceptionsINT3, INTO"]
Other["Other (7)Special events"]
end
HardException --> ErrorVectors
PrivSoft --> SoftCheck
SoftException --> SoftCheck
SoftIntr --> SoftCheck
TypeDetermination --> External
TypeDetermination --> HardException
TypeDetermination --> NMI
TypeDetermination --> PrivSoft
TypeDetermination --> SoftException
TypeDetermination --> SoftIntr
Vector --> ErrorCheck
Vector --> TypeDetermination
Vector Determination Logic
The VmxInterruptionType provides several utility methods:
| Method | Purpose | Return Type |
|---|---|---|
| from_vector(vector: u8) | Determines interruption type from vector number | VmxInterruptionType |
| vector_has_error_code(vector: u8) | Checks if vector pushes error code | bool |
| is_soft() | Identifies software-generated interrupts | bool |
Key classification rules:
- Vectors 0-21: Hardware exceptions (except special cases)
- Vector 1: Debug exception (privileged software)
- Vector 2: NMI (non-maskable interrupt)
- Vectors 3, 4: Software exceptions (INT3, INTO)
- Vectors 32-255: External interrupts
Sources: src/vmx/definitions.rs(L210 - L275)
Integration with VMX System
These definitions form the foundation for VMX operation throughout the hypervisor:
VMX Definitions Usage Flow
flowchart TD
subgraph subGraph2["Usage Scenarios"]
VmExit["VM Exit Processing"]
EventInject["Interrupt Injection"]
ErrorHandle["Error Handling"]
VmEntry["VM Entry Setup"]
end
subgraph subGraph1["Definition Types"]
VmxExitReason["VmxExitReason"]
VmxInterruptionType["VmxInterruptionType"]
VmxInstructionError["VmxInstructionError"]
end
subgraph subGraph0["VMX Core Components"]
VmxVcpu["VmxVcpu"]
VmcsManager["VMCS Manager"]
ExitHandler["Exit Handler"]
EventInjection["Event Injection"]
end
ErrorHandle --> VmxInstructionError
EventInject --> VmxInterruptionType
EventInjection --> VmxInterruptionType
ExitHandler --> VmxExitReason
VmEntry --> VmxInterruptionType
VmExit --> VmxExitReason
VmcsManager --> VmxInstructionError
VmxVcpu --> ErrorHandle
VmxVcpu --> EventInject
VmxVcpu --> VmEntry
VmxVcpu --> VmExit
These enumerations are used extensively in:
- Exit handling: Determining which handler to invoke based on
VmxExitReason - Event injection: Setting up interruption information with
VmxInterruptionType - Error reporting: Providing meaningful error messages via
VmxInstructionError - VMCS configuration: Validating and setting control fields
Sources: src/vmx/definitions.rs(L1 - L275)
VMX Instructions
Relevant source files
This document covers the low-level VMX instruction wrappers and hardware status handling mechanisms that provide primitive access to Intel VMX functionality. These instructions form the foundation for higher-level VMX operations throughout the hypervisor.
For broader VMX system functionality, see VMX Virtualization Engine. For VMX-specific data types and enumerations, see VMX Definitions and Types.
Purpose and Scope
The VMX instructions module provides direct wrappers around Intel VMX hardware instructions, focusing on proper status handling and type-safe interfaces. Currently, this module specifically implements Extended Page Table (EPT) invalidation through the INVEPT instruction and establishes patterns for VMX result handling that can be extended to other VMX instructions.
Sources: src/vmx/instructions.rs(L1 - L51)
VMX Status Handling Mechanism
VMX instructions communicate their success or failure through CPU flags rather than traditional return values. The vmx_capture_status function implements the standard pattern for interpreting these flags according to Intel SDM conventions.
Status Interpretation Flow
flowchart TD VMXInstr["VMX Instruction Execution"] ReadFlags["vmx_capture_status()"] CheckZF["Check Zero Flag (ZF)"] CheckCF["Check Carry Flag (CF)"] Success["Ok(())"] VmFailValid["VmFail::VmFailValid"] VmFailInvalid["VmFail::VmFailInvalid"] CheckCF --> Success CheckCF --> VmFailInvalid CheckZF --> CheckCF CheckZF --> VmFailValid ReadFlags --> CheckZF VMXInstr --> ReadFlags
The function uses #[inline(always)] to ensure no intervening code can clobber the RFLAGS register between instruction execution and status reading. This critical timing requirement prevents race conditions that could corrupt status information.
| Flag State | Result | Meaning |
|---|---|---|
| ZF = 1 | VmFail::VmFailValid | Instruction failed with valid VMCS |
| CF = 1 | VmFail::VmFailInvalid | Instruction failed with invalid VMCS |
| ZF = 0, CF = 0 | Ok(()) | Instruction succeeded |
Sources: src/vmx/instructions.rs(L5 - L22)
INVEPT Instruction Support
The INVEPT (Invalidate Extended Page Tables) instruction invalidates cached translations derived from EPT structures. This is essential for maintaining memory coherency when EPT mappings change.
INVEPT Types and Implementation
The invept function constructs an INVEPT descriptor containing the EPT pointer and executes the instruction through inline assembly. The descriptor format follows Intel SDM specifications with the EPTP in the first 64-bit word and a reserved zero in the second word.
INVEPT Operation Types
| Type | Value | Scope | Use Case |
|---|---|---|---|
| SingleContext | 1 | Specific EPTP (bits 51:12) | Invalidate translations for one guest |
| Global | 2 | All EPTPs | System-wide invalidation |
Sources: src/vmx/instructions.rs(L24 - L50)
Integration with VMX System
The VMX instructions module serves as the hardware abstraction foundation for higher-level VMX operations throughout the hypervisor system.
VMX Instruction Integration Points
flowchart TD
subgraph subGraph2["External Dependencies"]
X86Crate["x86 crateRFlags, VmFail types"]
CoreAsm["core::arch::asm"]
end
subgraph subGraph1["VMX Instructions Layer"]
InstructionsModule["instructions.rs"]
InveptWrapper["invept()"]
StatusHandler["vmx_capture_status()"]
end
subgraph subGraph0["High-Level VMX Components"]
VmxVcpu["VmxVcpusrc/vmx/vcpu.rs"]
EPTSystem["EPT Systemsrc/ept.rs"]
VMCSManager["VMCS Managersrc/vmx/vmcs.rs"]
end
EPTSystem --> InveptWrapper
InveptWrapper --> CoreAsm
InveptWrapper --> StatusHandler
StatusHandler --> X86Crate
VMCSManager --> StatusHandler
VmxVcpu --> InveptWrapper
The instruction wrappers provide type-safe interfaces that prevent common errors in VMX programming:
- Result Type Safety: All VMX instructions return
Result<()>with standardized error handling - Descriptor Management: Complex instruction descriptors are constructed internally with correct formatting
- Timing Guarantees: Critical timing requirements for status reading are enforced through compiler attributes
Sources: src/vmx/instructions.rs(L1 - L51)
Extension Pattern for Additional VMX Instructions
The current implementation establishes patterns that can be extended for other VMX instructions:
- Status Handling: Use
vmx_capture_status()after any VMX instruction - Type Enums: Define instruction-specific enums with appropriate
#[repr()]attributes - Descriptor Construction: Build instruction descriptors as arrays with proper field ordering
- Inline Assembly: Use direct
asm!macros with appropriate register constraints
This foundation enables safe, efficient access to the complete VMX instruction set while maintaining the hypervisor's type safety and performance requirements.
Sources: src/vmx/instructions.rs(L1 - L51)
Development and Configuration
Relevant source files
This document covers the development environment setup, build configuration, and continuous integration pipeline for the x86_vcpu hypervisor library. It provides guidance on project dependencies, feature flags, build toolchain requirements, and the automated testing and documentation deployment processes.
For information about the VMX-specific code organization and module structure, see VMX Virtualization Engine. For details about the physical frame allocation interface that integrates with external systems, see Physical Frame Management.
Project Structure and Dependencies
The x86_vcpu library is configured as a Rust 2024 edition crate with carefully selected dependencies that support bare-metal x86_64 virtualization. The project uses a modular architecture with optional feature flags to support different virtualization technologies.
Dependency Architecture
flowchart TD
subgraph subGraph5["Interface System"]
CrateInterface["crate_interface (0.1)"]
end
subgraph subGraph4["ArceOS Ecosystem"]
AxErrno["axerrno (0.1.0)"]
AxAddrspace["axaddrspace (git)"]
AxVcpu["axvcpu (git)"]
end
subgraph subGraph3["Memory Management"]
PageTableEntry["page_table_entry (0.5)"]
MemoryAddr["memory_addr (0.3.1)"]
end
subgraph subGraph2["Utility Crates"]
Log["log (0.4.19)"]
CfgIf["cfg-if (1.0)"]
Bitflags["bitflags (2.2)"]
BitField["bit_field (0.10)"]
NumericEnum["numeric-enum-macro (0.2)"]
end
subgraph subGraph1["Low-Level x86 Support"]
X86["x86 (0.52)"]
X86_64["x86_64 (0.15)"]
RawCpuid["raw-cpuid (11.0)"]
end
subgraph subGraph0["x86_vcpu Core"]
MainCrate["x86_vcpu(edition 2024)"]
end
MainCrate --> AxAddrspace
MainCrate --> AxErrno
MainCrate --> AxVcpu
MainCrate --> BitField
MainCrate --> Bitflags
MainCrate --> CfgIf
MainCrate --> CrateInterface
MainCrate --> Log
MainCrate --> MemoryAddr
MainCrate --> NumericEnum
MainCrate --> PageTableEntry
MainCrate --> RawCpuid
MainCrate --> X86
MainCrate --> X86_64
Sources: Cargo.toml(L6 - L22)
Feature Configuration
The project supports conditional compilation through feature flags defined in the Cargo.toml configuration:
| Feature | Purpose | Default |
|---|---|---|
| vmx | Enables Intel VMX (Virtual Machine Extensions) support | ✓ |
| amd | Enables AMD SVM (Secure Virtual Machine) support | ✗ |
The default feature set includes only vmx, making Intel VMX the primary supported virtualization technology. The amd feature can be enabled for AMD SVM support in future implementations.
Sources: Cargo.toml(L24 - L27)
Build System and Toolchain Requirements
The x86_vcpu library requires specific Rust toolchain configurations and targets to support bare-metal x86_64 development. The build process is designed for no-std environments with custom memory management.
Toolchain Configuration
The project uses Rust nightly toolchains with the following requirements:
- Primary Toolchain:
nightly-2024-12-25(pinned for stability) - Development Toolchain:
nightly(latest for development) - Target Architecture:
x86_64-unknown-none(bare-metal) - Required Components:
rust-src,clippy,rustfmt
Build Target Specifications
flowchart TD
subgraph subGraph3["Build Outputs"]
LibraryBuild["x86_vcpu Library"]
Documentation["API Documentation"]
end
subgraph subGraph2["Build Components"]
RustSrc["rust-src(Core Library Source)"]
Clippy["clippy(Linting)"]
Rustfmt["rustfmt(Formatting)"]
end
subgraph subGraph1["Target Platform"]
X86Target["x86_64-unknown-none(Bare Metal)"]
end
subgraph subGraph0["Rust Toolchain Matrix"]
Nightly2024["nightly-2024-12-25(Stable Build)"]
NightlyLatest["nightly(Development)"]
end
Clippy --> LibraryBuild
LibraryBuild --> Documentation
Nightly2024 --> X86Target
NightlyLatest --> X86Target
RustSrc --> LibraryBuild
Rustfmt --> LibraryBuild
X86Target --> Clippy
X86Target --> RustSrc
X86Target --> Rustfmt
Sources: .github/workflows/ci.yml(L11 - L19)
Continuous Integration Pipeline
The CI/CD system uses GitHub Actions to ensure code quality, build verification, and automatic documentation deployment. The pipeline runs on multiple Rust toolchain versions to ensure compatibility.
CI Workflow Structure
flowchart TD
subgraph subGraph3["Documentation Job"]
DocCheckout["actions/checkout@v4"]
DocToolchain["dtolnay/rust-toolchainnightly-2024-12-25"]
DocBuild["cargo doc --no-deps --all-features"]
PagesDeploy["JamesIves/github-pages-deploy-action@v4"]
end
subgraph subGraph2["Build Pipeline Steps"]
Checkout["actions/checkout@v4"]
ToolchainSetup["dtolnay/rust-toolchain"]
VersionCheck["rustc --version --verbose"]
FormatCheck["cargo fmt --all -- --check"]
ClippyLint["cargo clippy --target x86_64-unknown-none --all-features"]
Build["cargo build --target x86_64-unknown-none --all-features"]
UnitTest["cargo test --target x86_64-unknown-linux-gnu"]
end
subgraph subGraph1["CI Job Matrix"]
Job1["ubuntu-latestnightly-2024-12-25x86_64-unknown-none"]
Job2["ubuntu-latestnightlyx86_64-unknown-none"]
end
subgraph subGraph0["Trigger Events"]
Push["git push"]
PullRequest["pull_request"]
end
Build --> UnitTest
Checkout --> ToolchainSetup
ClippyLint --> Build
DocBuild --> PagesDeploy
DocCheckout --> DocToolchain
DocToolchain --> DocBuild
FormatCheck --> ClippyLint
Job1 --> Checkout
Job2 --> Checkout
PullRequest --> Job1
PullRequest --> Job2
Push --> DocCheckout
Push --> Job1
Push --> Job2
ToolchainSetup --> VersionCheck
VersionCheck --> FormatCheck
Sources: .github/workflows/ci.yml(L1 - L62)
Quality Assurance Steps
The CI pipeline enforces several quality checks:
- Code Formatting: Uses
cargo fmtwith--checkflag to ensure consistent code style - Linting: Runs
cargo clippywith--all-featuresand custom lint configuration - Build Verification: Compiles the library for the target architecture
- Unit Testing: Executes tests when applicable target platforms are available
The pipeline includes error tolerance for the latest nightly toolchain to prevent CI failures from bleeding-edge compiler changes.
Sources: .github/workflows/ci.yml(L23 - L34)
Documentation Deployment
The documentation system automatically builds and deploys API documentation to GitHub Pages:
- Documentation Flags:
-D rustdoc::broken_intra_doc_links -D missing-docs - Build Command:
cargo doc --no-deps --all-features - Deployment: Automatic deployment to
gh-pagesbranch on main branch updates - Index Generation: Creates redirect index.html pointing to the main crate documentation
Sources: .github/workflows/ci.yml(L36 - L61)
Development Workflow
Local Development Setup
- Install Rust Toolchain:
rustup toolchain install nightly-2024-12-25
rustup component add rust-src clippy rustfmt --toolchain nightly-2024-12-25
rustup target add x86_64-unknown-none --toolchain nightly-2024-12-25
- Build Commands:
cargo +nightly-2024-12-25 build --target x86_64-unknown-none --all-features
cargo +nightly-2024-12-25 clippy --target x86_64-unknown-none --all-features
cargo +nightly-2024-12-25 fmt --all
- Documentation Generation:
cargo +nightly-2024-12-25 doc --no-deps --all-features --open
Ignored Files and Build Artifacts
The .gitignore configuration excludes build outputs and development artifacts:
- Build Outputs:
/target,*.asm,*.img,*.bin,*.elf - Log Files:
actual.out,qemu.log - IDE Configuration:
/.vscode - System Files:
.DS_Store - Dependency Lock:
Cargo.lock(ignored because x86_vcpu is a library)
Sources: .gitignore(L1 - L18)
Project Configuration
Relevant source files
This document covers the project configuration, dependency management, and build requirements for the x86_vcpu hypervisor library. It explains the Cargo.toml configuration, feature flags for different virtualization technologies, and the crate interface requirements that users must implement.
For information about the build system and CI/CD pipeline, see Build System and CI.
Core Dependencies
The x86_vcpu crate is built upon a carefully selected set of dependencies that provide low-level x86 hardware access, memory management, and hypervisor framework integration.
Dependency Architecture
flowchart TD
subgraph subGraph5["ArceOS Hypervisor Framework"]
axaddrspace["axaddrspace (git)"]
axvcpu["axvcpu (git)"]
end
subgraph subGraph4["Interface System"]
crate_interface["crate_interface = \0.1\"]
end
subgraph subGraph3["Memory Management"]
page_table_entry["page_table_entry = \0.5\"]
memory_addr["memory_addr = \0.3.1\"]
axerrno["axerrno = \0.1.0\"]
end
subgraph subGraph2["Utility Crates"]
log["log = \0.4.19\"]
cfg_if["cfg-if = \1.0\"]
bitflags["bitflags = \2.2\"]
bit_field["bit_field = \0.10\"]
numeric_enum["numeric-enum-macro = \0.2\"]
end
subgraph subGraph1["Low-Level x86 Support"]
x86["x86 = \0.52\"]
x86_64["x86_64 = \0.15\"]
raw_cpuid["raw-cpuid = \11.0\"]
end
subgraph subGraph0["x86_vcpu Project"]
Cargo["Cargo.toml"]
LibMain["lib.rs"]
end
Cargo --> axaddrspace
Cargo --> axerrno
Cargo --> axvcpu
Cargo --> bit_field
Cargo --> bitflags
Cargo --> cfg_if
Cargo --> crate_interface
Cargo --> log
Cargo --> memory_addr
Cargo --> numeric_enum
Cargo --> page_table_entry
Cargo --> raw_cpuid
Cargo --> x86
Cargo --> x86_64
LibMain --> crate_interface
LibMain --> x86
LibMain --> x86_64
Sources: Cargo.toml(L6 - L22)
Dependency Categories
| Category | Crates | Purpose |
|---|---|---|
| x86 Hardware | x86,x86_64,raw-cpuid | Low-level x86/x86_64 instruction access, CPUID feature detection |
| Bit Manipulation | bitflags,bit_field,numeric-enum-macro | Hardware register field manipulation, flag definitions |
| Memory Management | page_table_entry,memory_addr,axerrno | Page table abstractions, address types, error handling |
| Interface System | crate_interface | Trait-based dependency injection for hardware abstraction |
| Hypervisor Framework | axaddrspace,axvcpu | ArceOS hypervisor ecosystem integration |
| Utilities | log,cfg-if | Logging and conditional compilation |
Sources: Cargo.toml(L6 - L22)
Feature Flags
The crate uses feature flags to support different virtualization technologies while maintaining a single codebase.
Feature Configuration
Sources: Cargo.toml(L24 - L27)
Available Features
| Feature | Default | Description |
|---|---|---|
| vmx | ✓ | Intel VMX (Virtual Machine Extensions) support |
| amd | ✗ | AMD SVM (Secure Virtual Machine) support |
The default feature set includes vmx, making Intel VMX the primary supported virtualization technology. Users can explicitly enable AMD support by specifying features = ["amd"] in their Cargo.toml or disable VMX with default-features = false.
Sources: Cargo.toml(L24 - L27)
Crate Interface Requirements
The x86_vcpu crate requires users to implement the PhysFrameIf trait to provide hardware abstraction for physical memory management.
Interface Implementation Pattern
flowchart TD
subgraph subGraph3["Hardware Layer"]
PhysicalMemory["Physical Memory"]
VirtualMemory["Virtual Memory Mapping"]
end
subgraph subGraph2["Required Methods"]
alloc_frame["alloc_frame() -> Option"]
dealloc_frame["dealloc_frame(paddr: PhysAddr)"]
phys_to_virt["phys_to_virt(paddr: PhysAddr) -> VirtAddr"]
end
subgraph subGraph1["x86_vcpu Crate"]
PhysFrameIf["PhysFrameIf trait"]
frame_rs["frame.rs"]
PhysFrame["PhysFrame"]
end
subgraph subGraph0["User Application"]
UserImpl["PhysFrameIfImpl struct"]
ImplMacro["#[crate_interface::impl_interface]"]
end
ImplMacro --> PhysFrameIf
PhysFrameIf --> alloc_frame
PhysFrameIf --> dealloc_frame
PhysFrameIf --> frame_rs
PhysFrameIf --> phys_to_virt
UserImpl --> ImplMacro
alloc_frame --> PhysicalMemory
dealloc_frame --> PhysicalMemory
frame_rs --> PhysFrame
phys_to_virt --> VirtualMemory
Sources: README.md(L7 - L29)
Implementation Requirements
Users must provide a concrete implementation of the PhysFrameIf trait using the crate_interface::impl_interface macro. The trait defines three essential methods:
alloc_frame(): Allocates a 4KB-aligned physical memory frame, returningSome(PhysAddr)on success orNoneif allocation failsdealloc_frame(paddr: PhysAddr): Deallocates a previously allocated physical frame at the specified addressphys_to_virt(paddr: PhysAddr) -> VirtAddr: Converts a physical address to its corresponding virtual address in the current address space
Sources: README.md(L13 - L29)
Example Implementation Structure
The README provides a template showing the required implementation pattern:
#![allow(unused)] fn main() { // From README.md:13-29 struct PhysFrameIfImpl; #[crate_interface::impl_interface] impl axvm::PhysFrameIf for PhysFrameIfImpl { fn alloc_frame() -> Option<PhysAddr> { /* implementation */ } fn dealloc_frame(paddr: PhysAddr) { /* implementation */ } fn phys_to_virt(paddr: PhysAddr) -> VirtAddr { /* implementation */ } } }
The implementation must handle physical memory allocation and mapping according to the host system's memory management requirements. A reference implementation can be found in the [ArceOS project](https://github.com/arceos-hypervisor/x86_vcpu/blob/2cc42349/ArceOS project)
Sources: README.md(L9)
Project Metadata
The project uses Rust edition 2024 and follows semantic versioning with the current version 0.1.0, indicating it is in initial development phase.
Sources: Cargo.toml(L1 - L4)
Build System and CI
Relevant source files
This document covers the continuous integration pipeline, build matrix configuration, code quality enforcement, and documentation deployment for the x86_vcpu hypervisor library. The build system is designed to ensure code quality across multiple Rust toolchain versions while providing automated documentation deployment.
For information about project dependencies and feature configuration, see Project Configuration.
CI Pipeline Overview
The x86_vcpu project uses GitHub Actions for continuous integration with a dual-job pipeline that handles both code validation and documentation deployment. The pipeline is triggered on push and pull_request events to ensure all changes are properly validated.
CI Workflow Structure
flowchart TD
subgraph subGraph3["Doc Job Steps"]
DocCheckout["actions/checkout@v4"]
DocRustSetup["dtolnay/rust-toolchain@nightly"]
DocBuild["cargo doc --no-deps --all-features"]
GHPagesDeploy["JamesIves/github-pages-deploy-action@v4"]
end
subgraph subGraph2["CI Job Steps"]
Checkout["actions/checkout@v4"]
RustSetup["dtolnay/rust-toolchain@nightly"]
VersionCheck["rustc --version --verbose"]
FormatCheck["cargo fmt --all -- --check"]
ClippyCheck["cargo clippy --target x86_64-unknown-none"]
Build["cargo build --target x86_64-unknown-none"]
UnitTest["cargo test --target x86_64-unknown-linux-gnu"]
end
subgraph subGraph1["CI Pipeline Jobs"]
CIJob["ci jobCode Quality & Build"]
DocJob["doc jobDocumentation Generation"]
end
subgraph subGraph0["GitHub Events"]
Push["push event"]
PR["pull_request event"]
end
Build --> UnitTest
CIJob --> Checkout
Checkout --> RustSetup
ClippyCheck --> Build
DocBuild --> GHPagesDeploy
DocCheckout --> DocRustSetup
DocJob --> DocCheckout
DocRustSetup --> DocBuild
FormatCheck --> ClippyCheck
PR --> CIJob
PR --> DocJob
Push --> CIJob
Push --> DocJob
RustSetup --> VersionCheck
VersionCheck --> FormatCheck
Sources: .github/workflows/ci.yml(L1 - L62)
Build Matrix Strategy
The CI pipeline employs a matrix strategy to test against multiple Rust toolchain versions, ensuring compatibility across both stable nightly builds and the latest nightly releases.
Toolchain Matrix Configuration
| Matrix Dimension | Values |
|---|---|
| rust-toolchain | nightly-2024-12-25,nightly |
| targets | x86_64-unknown-none |
| Runner | ubuntu-latest |
The matrix configuration uses fail-fast: false to ensure all combinations are tested even if one fails. The nightly toolchain uses continue-on-error: true for all steps, allowing bleeding-edge compatibility testing without blocking releases.
Target Architecture Rationale
The primary build target x86_64-unknown-none is specifically chosen for bare-metal hypervisor deployment:
- No standard library: Suitable for kernel-level virtualization code
- x86_64 architecture: Aligns with Intel VMX requirements
- Bare-metal execution: Matches hypervisor runtime environment
Sources: .github/workflows/ci.yml(L8 - L19)
Code Quality Enforcement
The CI pipeline implements comprehensive code quality checks through automated tools and formatting validation.
Quality Check Pipeline
flowchart TD
subgraph subGraph1["Exception Handling"]
ContinueOnError["continue-on-error: nightly toolchain"]
ClippyExceptions["-A clippy::new_without_defaultAllowed lint suppressions"]
end
subgraph subGraph0["Code Quality Checks"]
FormatCheck["cargo fmt --all -- --checkRust formatting validation"]
ClippyLints["cargo clippy --target x86_64-unknown-noneLint analysis with exception rules"]
BuildValidation["cargo build --target x86_64-unknown-noneCompilation verification"]
end
ClippyExceptions --> ClippyLints
ClippyLints --> BuildValidation
ContinueOnError --> BuildValidation
ContinueOnError --> ClippyLints
ContinueOnError --> FormatCheck
FormatCheck --> ClippyLints
Quality Check Configuration
The clippy configuration includes specific lint suppressions appropriate for hypervisor code:
clippy::new_without_default: Suppressed due to specialized constructor requirements in VMX structures- All features enabled: Ensures comprehensive linting across feature gates
- Target-specific linting: Validates against bare-metal target constraints
Sources: .github/workflows/ci.yml(L22 - L30)
Testing Strategy
The testing strategy accommodates the bare-metal nature of the hypervisor library while providing comprehensive validation where possible.
Test Execution Matrix
| Test Type | Target | Condition | Purpose |
|---|---|---|---|
| Unit Tests | x86_64-unknown-linux-gnu | Linux-compatible targets only | Validate core logic |
| Build Tests | x86_64-unknown-none | All targets | Ensure bare-metal compilation |
| Integration Tests | Not applicable | N/A | Hardware-dependent functionality |
The unit tests are conditionally executed only for Linux-compatible targets since the primary x86_64-unknown-none target cannot execute tests in the CI environment.
Sources: .github/workflows/ci.yml(L31 - L34)
Documentation Generation and Deployment
The documentation system provides automated API documentation generation and GitHub Pages deployment with strict documentation quality enforcement.
Documentation Build Pipeline
flowchart TD
subgraph subGraph2["Deployment Strategy"]
BranchCheck["github.ref == default-branch"]
GHPagesDeployment["JamesIves/github-pages-deploy-action@v4"]
SingleCommit["single-commit: true"]
end
subgraph subGraph1["Quality Enforcement"]
BrokenLinks["-D rustdoc::broken_intra_doc_links"]
MissingDocs["-D missing-docs"]
end
subgraph subGraph0["Documentation Generation"]
DocBuild["cargo doc --no-deps --all-features"]
IndexGeneration["printf redirect to generated docs"]
QualityCheck["RUSTDOCFLAGS validation"]
end
BranchCheck --> GHPagesDeployment
BrokenLinks --> QualityCheck
DocBuild --> IndexGeneration
GHPagesDeployment --> SingleCommit
IndexGeneration --> BranchCheck
MissingDocs --> QualityCheck
QualityCheck --> DocBuild
Documentation Quality Standards
The documentation build enforces strict quality standards through RUSTDOCFLAGS:
- Broken intra-doc links: Treated as build failures to ensure documentation integrity
- Missing documentation: All public APIs must be documented
- All features enabled: Comprehensive documentation across all feature gates
Deployment Configuration
Documentation deployment uses the following strategy:
- Branch restriction: Only deploys from the default branch
- Single commit mode: Maintains clean documentation history
- Target folder:
target/docwith generated index redirect
Sources: .github/workflows/ci.yml(L36 - L61)
Build Artifact Management
The build system excludes specific artifacts and temporary files to maintain a clean repository while supporting the ArceOS hypervisor development workflow.
Excluded Artifacts
| Category | Patterns | Purpose |
|---|---|---|
| Build Output | /target,.asm,.img,.bin,.elf | Standard Rust and ArceOS build artifacts |
| Runtime Files | actual.out,qemu.log | Hypervisor testing and emulation logs |
| Development Tools | /.vscode,.DS_Store,rusty-tags.vi | Editor and system-specific files |
| Dependency Lock | Cargo.lock | Library projects exclude lock files |
The exclusion of Cargo.lock follows Rust library best practices, allowing downstream consumers to resolve their own dependency versions while maintaining compatibility constraints defined in Cargo.toml.
Sources: .gitignore(L1 - L19)
Overview
Relevant source files
Purpose and Scope
This document provides an overview of the arm_vgic crate, which implements a Virtual Generic Interrupt Controller (VGIC) for ARM-based virtualized systems within the ArceOS hypervisor ecosystem. The crate enables guest virtual machines to interact with a virtualized ARM Generic Interrupt Controller (GIC) interface, providing essential interrupt management capabilities for ARM virtualization scenarios.
This overview covers the crate's architecture, main components, and integration points with the broader ArceOS framework. For detailed implementation specifics of individual components, see Core Components. For dependency analysis and build configuration details, see Dependencies and Integration.
System Purpose and Role
The arm_vgic crate serves as a critical virtualization component that bridges guest operating systems and the physical ARM GIC hardware. It provides:
- Virtual interrupt controller emulation for ARM guest VMs
- Memory-mapped register interface compatible with ARM GIC specifications
- Integration with ArceOS device framework through standardized interfaces
- Thread-safe state management for multi-core virtualization scenarios
The crate operates as an intermediary layer that intercepts guest VM accesses to GIC registers and translates them into appropriate operations on the underlying physical hardware or virtualized state.
Sources: Cargo.toml(L1 - L18) src/lib.rs(L1 - L10)
ArceOS Ecosystem Integration
Hypervisor Stack Positioning
flowchart TD
subgraph Physical_Hardware["Physical Hardware"]
ARM_CPU["ARM CPU Cores"]
Physical_GIC["Physical GIC Hardware"]
Memory_HW["Physical Memory"]
end
subgraph ArceOS_Hypervisor["ArceOS Hypervisor Layer"]
subgraph Hardware_Abstraction["Hardware Abstraction"]
arm_gicv2["arm_gicv2Physical GIC Driver"]
memory_addr["memory_addrAddress Types"]
end
subgraph Framework_Services["Framework Services"]
axdevice_base["axdevice_baseDevice Framework"]
axaddrspace["axaddrspaceMemory Management"]
axerrno["axerrnoError Handling"]
end
subgraph Device_Emulation["Device Emulation"]
arm_vgic["arm_vgic crateVirtual GIC Controller"]
OtherDevices["Other Virtual Devices"]
end
end
subgraph Guest_VM["Guest Virtual Machine"]
GuestOS["Guest Operating System"]
GuestDrivers["Device Drivers"]
GuestKernel["Kernel Interrupt Handlers"]
end
GuestDrivers --> arm_vgic
GuestKernel --> arm_vgic
arm_gicv2 --> Physical_GIC
arm_vgic --> arm_gicv2
arm_vgic --> axaddrspace
arm_vgic --> axdevice_base
axaddrspace --> Memory_HW
Sources: Cargo.toml(L7 - L17)
Core Architecture Components
Main Code Entity Structure
flowchart TD
subgraph consts_module["consts.rs Module"]
register_offsets["VGICD Register Offsets"]
irq_limits["SGI/PPI/SPI ID Limits"]
lr_config["List Register Configuration"]
end
subgraph devops_impl_module["devops_impl.rs Module"]
BaseDeviceOps_impl["BaseDeviceOps implementation"]
address_range["Address Range: 0x800_0000-0x800_FFFF"]
dispatch_logic["Width-based Dispatch"]
end
subgraph vgicc_module["vgicc.rs Module"]
Vgicc_struct["Vgicc struct"]
link_registers["Link Register Arrays"]
processor_state["Saved Processor State"]
end
subgraph vgic_module["vgic.rs Module"]
Vgic_struct["Vgic struct"]
VgicInner_struct["VgicInner struct"]
handle_read_functions["handle_read8/16/32"]
handle_write_functions["handle_write8/16/32"]
end
subgraph lib_rs["src/lib.rs - Public API"]
pub_use_vgic["pub use vgic::Vgic"]
mod_declarations["mod devops_implmod vgicmod constsmod vgicc"]
end
BaseDeviceOps_impl --> handle_read_functions
BaseDeviceOps_impl --> handle_write_functions
VgicInner_struct --> Vgicc_struct
Vgic_struct --> VgicInner_struct
Vgicc_struct --> link_registers
handle_read_functions --> register_offsets
handle_write_functions --> register_offsets
link_registers --> lr_config
pub_use_vgic --> Vgic_struct
Sources: src/lib.rs(L1 - L10)
Key Dependencies and Integration Points
The crate integrates with several ArceOS framework components and external libraries:
| Dependency | Purpose | Integration Point |
|---|---|---|
| axdevice_base | Device framework foundation | BaseDeviceOpstrait implementation |
| axaddrspace | Memory management abstractions | Address space operations |
| arm_gicv2 | Physical GIC hardware driver | Hardware interrupt management |
| memory_addr | Memory address type definitions | Address handling and validation |
| axerrno | Standardized error handling | Error propagation across components |
| spin | Synchronization primitives | Thread-safe state protection |
| log | Logging infrastructure | Debug and operational logging |
Component Communication Flow
flowchart TD
subgraph Hardware_Interface["Hardware Interface"]
arm_gicv2_driver["arm_gicv2::GicInterface"]
Physical_Operations["Physical GIC Operations"]
end
subgraph VGIC_Processing["arm_vgic Processing"]
BaseDeviceOps_impl["BaseDeviceOps impl"]
Vgic_handlers["Vgic read/write handlers"]
VgicInner_state["VgicInner state"]
Vgicc_interface["Vgicc CPU interface"]
end
subgraph VM_Access["VM Memory Access"]
VM_Read["VM Read Operation"]
VM_Write["VM Write Operation"]
end
BaseDeviceOps_impl --> Vgic_handlers
VM_Read --> BaseDeviceOps_impl
VM_Write --> BaseDeviceOps_impl
VgicInner_state --> Vgicc_interface
Vgic_handlers --> VgicInner_state
Vgic_handlers --> arm_gicv2_driver
arm_gicv2_driver --> Physical_Operations
Sources: Cargo.toml(L7 - L17) src/lib.rs(L3 - L10)
Functional Scope
The arm_vgic crate provides virtualization for ARM Generic Interrupt Controller functionality, specifically focusing on:
- Distributor interface emulation - Handles guest accesses to GIC distributor registers
- CPU interface abstraction - Manages per-CPU interrupt controller state
- Interrupt routing and prioritization - Implements ARM GIC interrupt handling semantics
- State persistence - Maintains interrupt controller state across VM operations
- Hardware integration - Coordinates with physical GIC hardware through
arm_gicv2
The crate operates within the memory address range 0x800_0000 to 0x800_FFFF, providing a standardized MMIO interface that guest VMs can interact with as if accessing a physical ARM GIC controller.
Sources: Cargo.toml(L1 - L18) src/lib.rs(L1 - L10)
System Architecture
Relevant source files
Purpose and Scope
This document provides a comprehensive overview of the arm_vgic crate's system architecture, detailing how it implements a Virtual Generic Interrupt Controller (VGIC) for ARM systems within the ArceOS hypervisor ecosystem. The architecture encompasses the virtualization of ARM GICv2 hardware, memory-mapped register emulation, and integration with the hypervisor's device framework.
For detailed information about individual components and their APIs, see Core Components. For dependency analysis and integration patterns, see Dependencies and Integration.
Virtualization Stack Positioning
The arm_vgic crate operates as a critical virtualization layer between guest operating systems and physical ARM GIC hardware. The architecture follows a clean separation of concerns across multiple abstraction levels.
Hypervisor Stack Architecture
flowchart TD
subgraph PhysicalHW["Physical Hardware"]
ARMCores["ARM CPU Cores"]
PhysicalGIC["Physical GIC Hardware"]
MemorySystem["Memory Subsystem"]
end
subgraph ArceOSHypervisor["ArceOS Hypervisor Layer"]
subgraph HardwareAbstraction["Hardware Abstraction"]
ArmGicV2["arm_gicv2::GicInterface"]
MemoryAddr["memory_addr"]
end
subgraph FrameworkLayer["Framework Layer"]
BaseDeviceOps["BaseDeviceOps"]
AxDeviceBase["axdevice_base"]
AxAddrSpace["axaddrspace"]
end
subgraph DeviceEmulation["Device Emulation Layer"]
VgicController["Vgic"]
VgicInner["VgicInner"]
VgiccState["Vgicc"]
end
end
subgraph GuestVM["Guest Virtual Machine"]
GuestOS["Guest OS Kernel"]
GuestDrivers["Device Drivers"]
GuestHandlers["Interrupt Handlers"]
end
ArmGicV2 --> PhysicalGIC
ArmGicV2 --> VgicController
BaseDeviceOps --> AxDeviceBase
GuestDrivers --> VgicController
GuestHandlers --> VgicController
MemoryAddr --> MemorySystem
PhysicalGIC --> ArmGicV2
VgicController --> AxAddrSpace
VgicController --> BaseDeviceOps
VgicController --> GuestHandlers
VgicController --> VgicInner
VgicInner --> VgiccState
Sources: src/vgic.rs(L32 - L34) src/devops_impl.rs(L10) src/vgicc.rs(L3)
The Vgic struct serves as the primary interface between guest VMs and the physical interrupt controller, implementing memory-mapped register emulation at the standardized ARM GIC address space of 0x800_0000 to 0x800_FFFF.
Core Architecture Components
The system architecture is built around four primary components that work together to provide complete interrupt controller virtualization.
Component Relationship Architecture
flowchart TD
subgraph ExternalDeps["External Dependencies"]
SpinMutex["spin::Mutex"]
ArmGicV2Interface["arm_gicv2::GicInterface"]
AxDeviceBase["axdevice_base"]
end
subgraph CoreImplementation["Core Implementation"]
subgraph DevOpsModule["devops_impl.rs"]
BaseDeviceOpsImpl["BaseDeviceOps impl"]
EmuType["emu_type()"]
AddressRange["address_range()"]
HandleRead["handle_read()"]
HandleWrite["handle_write()"]
end
subgraph ConstsModule["consts.rs"]
SGI_ID_MAX["SGI_ID_MAX"]
PPI_ID_MAX["PPI_ID_MAX"]
SPI_ID_MAX["SPI_ID_MAX"]
VGICD_CTLR["VGICD_CTLR"]
VGICD_ISENABLER_SGI_PPI["VGICD_ISENABLER_SGI_PPI"]
VGICD_ISENABLER_SPI["VGICD_ISENABLER_SPI"]
end
subgraph VgiccModule["vgicc.rs"]
VgiccStruct["Vgicc"]
PendingLR["pending_lr[SPI_ID_MAX]"]
SavedLR["saved_lr[GICD_LR_NUM]"]
SavedRegs["saved_elsr0, saved_apr, saved_hcr"]
end
subgraph VgicModule["vgic.rs"]
VgicStruct["Vgic"]
VgicInnerStruct["VgicInner"]
HandleRead8["handle_read8"]
HandleRead16["handle_read16"]
HandleRead32["handle_read32"]
HandleWrite8["handle_write8"]
HandleWrite16["handle_write16"]
HandleWrite32["handle_write32"]
end
end
subgraph PublicAPI["Public API Layer"]
LibRS["lib.rs"]
VgicExport["pub use vgic::Vgic"]
end
BaseDeviceOpsImpl --> AxDeviceBase
BaseDeviceOpsImpl --> HandleRead
BaseDeviceOpsImpl --> HandleWrite
HandleRead --> HandleRead16
HandleRead --> HandleRead32
HandleRead --> HandleRead8
HandleRead8 --> VGICD_CTLR
HandleWrite --> HandleWrite16
HandleWrite --> HandleWrite32
HandleWrite --> HandleWrite8
HandleWrite8 --> ArmGicV2Interface
HandleWrite8 --> SGI_ID_MAX
HandleWrite8 --> SPI_ID_MAX
HandleWrite8 --> VGICD_CTLR
HandleWrite8 --> VGICD_ISENABLER_SGI_PPI
LibRS --> VgicExport
VgicExport --> VgicStruct
VgicInnerStruct --> VgiccStruct
VgicStruct --> SpinMutex
VgicStruct --> VgicInnerStruct
VgiccStruct --> PendingLR
VgiccStruct --> SavedLR
VgiccStruct --> SavedRegs
Sources: src/vgic.rs(L15 - L30) src/vgicc.rs(L3 - L14) src/consts.rs(L1 - L19) src/devops_impl.rs(L10 - L99)
Memory Layout and Address Handling
The VGIC implements a memory-mapped interface that emulates the standard ARM GICv2 distributor registers. The address decoding and dispatch mechanism ensures proper isolation and functionality.
Address Space and Register Layout
| Address Range | Register Category | Handler Functions |
|---|---|---|
| 0x800_0000 + 0x000 | Control Register (VGICD_CTLR) | handle_write8/16/32 |
| 0x800_0000 + 0x100-0x104 | Interrupt Set Enable (VGICD_ISENABLER) | handle_write8/16/32 |
| 0x800_0000 + 0x180-0x184 | Interrupt Clear Enable (VGICD_ICENABLER) | handle_write8/16/32 |
| 0x800_0000 + 0x200 | Interrupt Set Pending (VGICD_ISPENDR) | handle_write8/16/32 |
Sources: src/consts.rs(L7 - L18) src/devops_impl.rs(L29 - L31) src/devops_impl.rs(L47)
The address range spans 64KB (0x10000 bytes) starting at 0x800_0000, with address masking applied using addr & 0xfff to ensure proper alignment within the 4KB register space.
Data Flow Architecture
The system processes guest memory accesses through a well-defined pipeline that maintains virtualization transparency while interfacing with physical hardware.
Memory Access Processing Pipeline
flowchart TD
subgraph PhysicalInterface["Physical Interface"]
GicInterface["arm_gicv2::GicInterface"]
SetEnable["set_enable(irq, bool)"]
SetPriority["set_priority(irq, u8)"]
end
subgraph StateManagement["State Management"]
UsedIRQ["used_irq[SPI_ID_MAX/32]"]
PTOV["ptov[SPI_ID_MAX]"]
VTOP["vtop[SPI_ID_MAX]"]
GICDRegs["gicd_* register arrays"]
end
subgraph RegisterHandling["Register Handling"]
CTLRHandler["VGICD_CTLR Handler"]
ENABLERHandler["ISENABLER/ICENABLER"]
PENDRHandler["ISPENDR/ICPENDR"]
MutexLock["VgicInner Mutex Lock"]
end
subgraph AddressProcessing["Address Processing"]
AddrDecode["Address DecodingGuestPhysAddr"]
AddrMask["Address Maskingaddr & 0xfff"]
WidthDispatch["Width Dispatch1/2/4 bytes"]
end
subgraph GuestAccess["Guest Memory Access"]
VMRead["VM Read Operation"]
VMWrite["VM Write Operation"]
end
AddrDecode --> AddrMask
AddrMask --> WidthDispatch
CTLRHandler --> GicInterface
CTLRHandler --> MutexLock
ENABLERHandler --> MutexLock
GicInterface --> SetEnable
GicInterface --> SetPriority
MutexLock --> GICDRegs
MutexLock --> PTOV
MutexLock --> UsedIRQ
MutexLock --> VTOP
VMRead --> AddrDecode
VMWrite --> AddrDecode
WidthDispatch --> CTLRHandler
WidthDispatch --> ENABLERHandler
WidthDispatch --> PENDRHandler
Sources: src/devops_impl.rs(L45 - L66) src/devops_impl.rs(L77 - L98) src/vgic.rs(L68 - L133) src/vgic.rs(L15 - L30)
The data flow ensures thread-safe access through mutex protection of the VgicInner state, while maintaining efficient dispatch based on access width and register offset. Physical GIC operations are performed only when necessary, such as during control register updates that enable or disable interrupt groups.
Thread Safety and Concurrency
The architecture employs a mutex-protected inner state design to ensure thread-safe operation across multiple CPU cores accessing the virtual interrupt controller simultaneously.
The VgicInner struct src/vgic.rs(L15 - L30) contains all mutable state protected by a spin::Mutex src/vgic.rs(L33) including interrupt tracking arrays, virtual-to-physical mapping tables, and register state. This design allows multiple guest CPUs to safely access the VGIC while maintaining consistency of the virtualized interrupt controller state.
Sources: src/vgic.rs(L32 - L53) src/vgic.rs(L76) src/devops_impl.rs(L1 - L8)
Core Components
Relevant source files
This document provides an overview of the four primary components that make up the arm_vgic virtual interrupt controller system. These components work together to provide complete ARM GIC (Generic Interrupt Controller) virtualization within the ArceOS hypervisor ecosystem.
For detailed information about system architecture and positioning within the broader virtualization stack, see System Architecture. For dependency analysis and integration details, see Dependencies and Integration.
Component Architecture Overview
The arm_vgic crate is structured around four core components that provide different aspects of interrupt controller virtualization:
Component Architecture
flowchart TD
subgraph subGraph1["External Framework"]
axdevice_base["axdevice_base::BaseDeviceOps"]
arm_gicv2["arm_gicv2::GicInterface"]
end
subgraph subGraph0["Core Components"]
Vgic["VgicMain Controller"]
VgicInner["VgicInnerProtected State"]
Vgicc["VgiccCPU Interface"]
BaseDeviceOps["BaseDeviceOps ImplementationDevice Framework Integration"]
Constants["Constants ModuleRegister Definitions"]
end
BaseDeviceOps --> Vgic
BaseDeviceOps --> axdevice_base
Constants --> Vgicc
Vgic --> Constants
Vgic --> VgicInner
Vgic --> arm_gicv2
VgicInner --> Vgicc
Sources: src/vgic.rs(L32 - L34) src/vgicc.rs(L3 - L14) src/devops_impl.rs(L10) src/consts.rs(L1 - L19)
Data Flow and Component Interaction
The components interact through a well-defined data flow that handles virtual machine memory accesses and translates them to physical interrupt controller operations:
Data Flow Between Components
flowchart TD
subgraph subGraph4["Hardware Interface"]
GicInterface_set_enable["GicInterface::set_enable"]
GicInterface_set_priority["GicInterface::set_priority"]
end
subgraph subGraph3["State Management"]
VgicInner_mutex["VgicInner (Mutex)"]
gicc_vec["Vec"]
register_arrays["Register State Arrays"]
end
subgraph subGraph2["VGIC Processing"]
handle_read8["Vgic::handle_read8"]
handle_read16["Vgic::handle_read16"]
handle_read32["Vgic::handle_read32"]
handle_write8["Vgic::handle_write8"]
handle_write16["Vgic::handle_write16"]
handle_write32["Vgic::handle_write32"]
end
subgraph subGraph1["Device Framework Layer"]
handle_read["BaseDeviceOps::handle_read"]
handle_write["BaseDeviceOps::handle_write"]
address_range["address_range()0x800_0000-0x800_FFFF"]
end
subgraph subGraph0["VM Access"]
VM_Read["VM Read Access"]
VM_Write["VM Write Access"]
end
VM_Read --> handle_read
VM_Write --> handle_write
VgicInner_mutex --> GicInterface_set_enable
VgicInner_mutex --> GicInterface_set_priority
VgicInner_mutex --> gicc_vec
VgicInner_mutex --> register_arrays
handle_read --> handle_read16
handle_read --> handle_read32
handle_read --> handle_read8
handle_write --> handle_write16
handle_write --> handle_write32
handle_write --> handle_write8
handle_write8 --> VgicInner_mutex
Sources: src/devops_impl.rs(L45 - L66) src/devops_impl.rs(L77 - L98) src/vgic.rs(L56 - L66) src/vgic.rs(L68 - L106) src/vgic.rs(L15 - L30)
Component Responsibilities
Virtual GIC Controller (Vgic)
The Vgic struct serves as the main controller that orchestrates interrupt virtualization. It maintains thread-safe access to the internal state through a Mutex<VgicInner> and provides the primary interface for handling virtual machine memory accesses to interrupt controller registers.
Key responsibilities:
- Memory-mapped register access handling via
handle_read8/16/32andhandle_write8/16/32methods - Thread-safe state management through
VgicInnermutex protection - Physical interrupt controller coordination via
arm_gicv2::GicInterface - Interrupt enable/disable control and priority management
For comprehensive details, see Virtual GIC Controller (Vgic).
Sources: src/vgic.rs(L32 - L34) src/vgic.rs(L36 - L54)
CPU Interface (Vgicc)
The Vgicc struct manages per-CPU interrupt controller state, including link registers for interrupt delivery and saved processor state for context switching operations.
Key responsibilities:
- Per-CPU interrupt state management with unique
idfield - Link register arrays for interrupt queuing (
pending_lr,saved_lr) - Processor state preservation (
saved_elsr0,saved_apr,saved_hcr) - Local interrupt enable/priority configuration (
isenabler,priorityr)
For detailed analysis, see CPU Interface (Vgicc).
Sources: src/vgicc.rs(L3 - L14)
Device Operations Interface
The BaseDeviceOps trait implementation bridges the Vgic controller with the ArceOS device framework, providing standardized device emulation capabilities.
Key responsibilities:
- Device type identification via
emu_type()returningEmuDeviceTGicdV2 - Address space definition through
address_range()covering0x800_0000to0x800_FFFF - Width-based memory access dispatch (8/16/32-bit operations)
- Address masking and alignment handling
For implementation details, see Device Operations Interface.
Sources: src/devops_impl.rs(L10) src/devops_impl.rs(L18 - L31)
Constants and Register Layout
The constants module defines critical system parameters, interrupt ID limits, and register offset mappings that govern the virtual interrupt controller behavior.
Key definitions:
- Interrupt ID boundaries:
SGI_ID_MAX,PPI_ID_MAX,SPI_ID_MAX - Link register configuration:
GICD_LR_NUM - VGICD register offsets:
VGICD_CTLR,VGICD_ISENABLER_*,VGICD_ICENABLER_* - Control and configuration register addresses
For complete reference, see Constants and Register Layout.
Sources: src/consts.rs(L1 - L19)
Integration Architecture
Component Integration with External Systems
flowchart TD
subgraph subGraph3["System Utilities"]
spin_mutex["spin::Mutex"]
log_macros["log::error!"]
alloc_vec["alloc::Vec"]
end
subgraph subGraph2["Hardware Abstraction"]
arm_gicv2_interface["arm_gicv2::GicInterface"]
memory_addr_range["memory_addr::AddrRange"]
end
subgraph subGraph1["ArceOS Framework"]
BaseDeviceOps_trait["BaseDeviceOps trait"]
axdevice_base_crate["axdevice_base crate"]
axaddrspace_addr["axaddrspace::GuestPhysAddr"]
axerrno_result["axerrno::AxResult"]
end
subgraph subGraph0["arm_vgic Components"]
Vgic_main["Vgic"]
VgicInner_state["VgicInner"]
Vgicc_cpu["Vgicc"]
consts_defs["Constants"]
end
BaseDeviceOps_trait --> axaddrspace_addr
BaseDeviceOps_trait --> axdevice_base_crate
BaseDeviceOps_trait --> axerrno_result
BaseDeviceOps_trait --> memory_addr_range
VgicInner_state --> Vgicc_cpu
VgicInner_state --> alloc_vec
VgicInner_state --> spin_mutex
Vgic_main --> BaseDeviceOps_trait
Vgic_main --> VgicInner_state
Vgic_main --> arm_gicv2_interface
Vgic_main --> consts_defs
Vgic_main --> log_macros
Sources: src/vgic.rs(L1 - L11) src/devops_impl.rs(L1 - L8) src/vgicc.rs(L2)
The four core components work together to provide a complete virtual interrupt controller implementation that integrates seamlessly with the ArceOS hypervisor framework while maintaining efficient virtualization of ARM GIC functionality.
Virtual GIC Controller (Vgic)
Relevant source files
This document covers the Vgic struct and its associated components, which form the core of the virtual interrupt controller implementation in the arm_vgic crate. The Vgic provides virtualized access to ARM Generic Interrupt Controller (GIC) functionality for guest virtual machines within the ArceOS hypervisor ecosystem.
For information about the CPU-specific interface components, see CPU Interface (Vgicc). For details about the device framework integration, see Device Operations Interface.
Core Structure and Components
The Virtual GIC Controller is implemented through two primary structures that work together to manage interrupt virtualization state and provide thread-safe access to interrupt controller resources.
Primary Data Structures
The Vgic implementation follows a layered approach with an outer wrapper providing thread-safe access to the internal state:
flowchart TD
subgraph subGraph4["Control State"]
ctrlr["ctrlr: u32"]
typer["typer: u32"]
iidr["iidr: u32"]
end
subgraph subGraph3["GIC Registers"]
gicd_igroupr["gicd_igroupr[SPI_ID_MAX/32]"]
gicd_isenabler["gicd_isenabler[SPI_ID_MAX/32]"]
gicd_ipriorityr["gicd_ipriorityr[SPI_ID_MAX]"]
gicd_itargetsr["gicd_itargetsr[SPI_ID_MAX]"]
gicd_icfgr["gicd_icfgr[SPI_ID_MAX/16]"]
end
subgraph subGraph2["State Arrays"]
used_irq["used_irq[SPI_ID_MAX/32]"]
ptov["ptov[SPI_ID_MAX]"]
vtop["vtop[SPI_ID_MAX]"]
gicc_vec["gicc: Vec"]
end
subgraph subGraph1["Protected State"]
VgicInner["VgicInner"]
Mutex["Mutex"]
end
subgraph subGraph0["Public Interface"]
Vgic["Vgic"]
end
Mutex --> VgicInner
Vgic --> Mutex
VgicInner --> ctrlr
VgicInner --> gicc_vec
VgicInner --> gicd_icfgr
VgicInner --> gicd_igroupr
VgicInner --> gicd_ipriorityr
VgicInner --> gicd_isenabler
VgicInner --> gicd_itargetsr
VgicInner --> iidr
VgicInner --> ptov
VgicInner --> typer
VgicInner --> used_irq
VgicInner --> vtop
Sources: src/vgic.rs(L15 - L34)
State Management Arrays
The VgicInner struct maintains several key data structures for interrupt management:
| Array | Type | Size | Purpose |
|---|---|---|---|
| used_irq | [u32; SPI_ID_MAX/32] | 16 words | Tracks which interrupt IDs are allocated |
| ptov | [u32; SPI_ID_MAX] | 512 entries | Physical-to-virtual interrupt ID mapping |
| vtop | [u32; SPI_ID_MAX] | 512 entries | Virtual-to-physical interrupt ID mapping |
| gicd_igroupr | [u32; SPI_ID_MAX/32] | 16 words | Interrupt group register state |
| gicd_isenabler | [u32; SPI_ID_MAX/32] | 16 words | Interrupt enable register state |
| gicd_ipriorityr | [u8; SPI_ID_MAX] | 512 bytes | Interrupt priority values |
| gicd_itargetsr | [u8; SPI_ID_MAX] | 512 bytes | Interrupt target CPU mapping |
| gicd_icfgr | [u32; SPI_ID_MAX/16] | 32 words | Interrupt configuration register state |
Sources: src/vgic.rs(L15 - L30) src/consts.rs(L1 - L4)
Memory Access Handling
The Vgic provides width-specific handlers for processing virtual machine memory accesses to the interrupt controller's memory-mapped registers. These handlers implement the virtualization layer between guest VM register accesses and the underlying interrupt controller state.
Handler Method Dispatch
flowchart TD
subgraph subGraph2["Register Processing"]
addr_decode["Address Decoding"]
register_logic["Register-Specific Logic"]
state_update["VgicInner State Update"]
end
subgraph subGraph1["Handler Methods"]
handle_read8["handle_read8()"]
handle_read16["handle_read16()"]
handle_read32["handle_read32()"]
handle_write8["handle_write8()"]
handle_write16["handle_write16()"]
handle_write32["handle_write32()"]
end
subgraph subGraph0["VM Memory Access"]
vm_read8["VM 8-bit Read"]
vm_read16["VM 16-bit Read"]
vm_read32["VM 32-bit Read"]
vm_write8["VM 8-bit Write"]
vm_write16["VM 16-bit Write"]
vm_write32["VM 32-bit Write"]
end
addr_decode --> register_logic
handle_read16 --> addr_decode
handle_read32 --> addr_decode
handle_read8 --> addr_decode
handle_write16 --> addr_decode
handle_write32 --> addr_decode
handle_write8 --> addr_decode
register_logic --> state_update
vm_read16 --> handle_read16
vm_read32 --> handle_read32
vm_read8 --> handle_read8
vm_write16 --> handle_write16
vm_write32 --> handle_write32
vm_write8 --> handle_write8
Sources: src/vgic.rs(L56 - L133)
Read Handler Implementation
The read handlers currently provide basic functionality with placeholder implementations:
handle_read8(): Returns 0 for all 8-bit register readshandle_read16(): Returns 0 for all 16-bit register readshandle_read32(): Returns 0 for all 32-bit register reads
These methods use the signature fn handle_readX(&self, addr: usize) -> AxResult<usize> and are marked as pub(crate) for internal crate access.
Sources: src/vgic.rs(L56 - L66)
Register Handling and Interrupt Control
The write handlers implement the core interrupt virtualization logic by processing writes to specific GIC distributor registers and coordinating with the physical interrupt controller.
Control Register Handling
The VGICD_CTLR register controls the overall enable/disable state of the interrupt distributor:
flowchart TD
subgraph subGraph2["Physical GIC Operations"]
scan_used["Scan used_irq array"]
set_enable["GicInterface::set_enable()"]
set_priority["GicInterface::set_priority()"]
end
subgraph subGraph1["Conditional Processing"]
check_enable["ctrlr > 0?"]
enable_path["Enable Path"]
disable_path["Disable Path"]
end
subgraph subGraph0["Control Register Write Flow"]
write_ctlr["Write to VGICD_CTLR"]
extract_bits["Extract bits [1:0]"]
lock_state["Lock VgicInner"]
update_ctrlr["Update ctrlr field"]
end
check_enable --> disable_path
check_enable --> enable_path
disable_path --> scan_used
enable_path --> scan_used
enable_path --> set_priority
extract_bits --> lock_state
lock_state --> update_ctrlr
scan_used --> set_enable
update_ctrlr --> check_enable
write_ctlr --> extract_bits
Sources: src/vgic.rs(L70 - L95)
Enable Register Handling
The interrupt enable registers (VGICD_ISENABLER_SGI_PPI and VGICD_ISENABLER_SPI) are handled through delegation to wider access methods:
- 8-bit writes to enable registers delegate to
handle_write32() - 16-bit writes to enable registers delegate to
handle_write32() - 32-bit writes to enable registers are handled directly but currently contain placeholder logic
Sources: src/vgic.rs(L96 - L132)
Physical Hardware Integration
The Vgic coordinates with the physical ARM GIC hardware through the arm_gicv2::GicInterface to ensure that virtual interrupt state changes are reflected in the underlying hardware configuration.
Hardware Interface Operations
| Operation | Method | Purpose |
|---|---|---|
| Enable/Disable | GicInterface::set_enable(irq_id, enabled) | Control interrupt enable state |
| Priority Setting | GicInterface::set_priority(irq_id, priority) | Set interrupt priority level |
The integration logic scans the used_irq bitmap to identify allocated interrupts and applies configuration changes only to interrupts that are actively managed by the virtual controller.
Sources: src/vgic.rs(L5) src/vgic.rs(L82 - L92)
Initialization and Construction
The Vgic::new() constructor initializes all internal state arrays to zero values and creates an empty vector for CPU interface objects:
flowchart TD
subgraph subGraph1["Default Values"]
zero_ctrlr["ctrlr = 0"]
zero_arrays["All arrays = [0; SIZE]"]
empty_gicc["gicc = Vec::new()"]
end
subgraph subGraph0["Constructor Process"]
new_call["Vgic::new()"]
create_mutex["Create Mutex"]
init_arrays["Initialize State Arrays"]
empty_vec["Empty gicc Vec"]
end
create_mutex --> init_arrays
empty_vec --> empty_gicc
init_arrays --> empty_vec
init_arrays --> zero_arrays
init_arrays --> zero_ctrlr
new_call --> create_mutex
The constructor ensures that the virtual interrupt controller starts in a clean, disabled state with no allocated interrupts or configured CPU interfaces.
Sources: src/vgic.rs(L37 - L54)
CPU Interface (Vgicc)
Relevant source files
This document covers the Vgicc struct, which implements the per-CPU interface portion of the virtual Generic Interrupt Controller (GIC). The Vgicc manages CPU-specific interrupt state, list registers for interrupt virtualization, and processor state saving/restoration.
For information about the main VGIC controller that coordinates multiple CPU interfaces, see Virtual GIC Controller (Vgic). For system-wide constants and register layouts, see Constants and Register Layout.
Structure Overview
The Vgicc struct represents a single CPU's interface to the virtual interrupt controller. Each CPU core in the virtualized system has its own Vgicc instance that maintains independent interrupt state and configuration.
Vgicc Struct Layout
flowchart TD
subgraph subGraph3["Vgicc Struct"]
ID["id: u32CPU Identifier"]
subgraph subGraph1["Processor State Registers"]
SAVED_ELSR["saved_elsr0: u32Empty List Status Register"]
SAVED_APR["saved_apr: u32Active Priority Register"]
SAVED_HCR["saved_hcr: u32Hypervisor Control Register"]
end
subgraph subGraph0["List Register Management"]
PENDING_LR["pending_lr: [u32; 512]SPI Pending List Registers"]
SAVED_LR["saved_lr: [u32; 4]Hardware List Register State"]
end
subgraph subGraph2["Interrupt Configuration"]
ISENABLER["isenabler: u32SGI/PPI Enable Bits 0-31"]
PRIORITYR["priorityr: [u8; 32]PPI Priority Configuration"]
end
end
ID --> PENDING_LR
ISENABLER --> PRIORITYR
SAVED_ELSR --> SAVED_LR
SAVED_LR --> PENDING_LR
Sources: src/vgicc.rs(L3 - L14)
Field Categories and Responsibilities
| Category | Fields | Purpose |
|---|---|---|
| CPU Identity | id | Uniquely identifies the CPU core this interface serves |
| List Register State | pending_lr,saved_lr | Manages virtual interrupt injection through hardware list registers |
| Processor Context | saved_elsr0,saved_apr,saved_hcr | Preserves CPU-specific GIC state during VM context switches |
| Interrupt Control | isenabler,priorityr | Configures interrupt enables and priorities for SGIs and PPIs |
Sources: src/vgicc.rs(L4 - L13) src/consts.rs(L1 - L4)
List Register Management
The Vgicc manages two types of list register arrays that are central to ARM GIC virtualization:
Hardware List Registers vs Pending Arrays
flowchart TD
subgraph subGraph2["Interrupt Sources"]
SPI["SPI Interrupts(32-543)"]
PPI_SGI["PPI/SGI Interrupts(0-31)"]
end
subgraph subGraph1["Vgicc State"]
SAVED_LR["saved_lr[4]Hardware LR Backup"]
PENDING_LR["pending_lr[512]SPI Virtual Queue"]
SAVED_ELSR["saved_elsr0Empty LR Status"]
end
subgraph subGraph0["Physical Hardware"]
HW_LR["Physical List Registers(4 registers)"]
end
HW_LR --> SAVED_LR
PENDING_LR --> SAVED_LR
PPI_SGI --> HW_LR
SAVED_LR --> HW_LR
SAVED_LR --> SAVED_ELSR
SPI --> PENDING_LR
Sources: src/vgicc.rs(L5 - L6) src/consts.rs(L3 - L4)
The saved_lr array holds the contents of the 4 physical hardware list registers, while pending_lr provides a much larger virtual queue for SPI interrupts that cannot fit in the limited hardware registers.
Processor State Management
The Vgicc maintains three critical processor state registers that must be preserved across VM context switches:
State Register Functions
flowchart TD
subgraph subGraph2["Hardware Registers"]
HW_HCR["Physical GICH_HCR"]
HW_APR["Physical GICH_APR"]
HW_ELSR["Physical GICH_ELSR"]
end
subgraph subGraph1["Vgicc State Preservation"]
HCR["saved_hcrHypervisor Control• Virtual IRQ/FIQ enables• List register usage"]
APR["saved_aprActive Priority• Currently active interrupt priority• Priority grouping config"]
ELSR["saved_elsr0Empty List Status• Which LRs are available• Hardware resource tracking"]
end
subgraph subGraph0["VM Context Switch"]
VM_EXIT["VM Exit"]
VM_ENTRY["VM Entry"]
end
APR --> HW_APR
APR --> VM_ENTRY
ELSR --> HW_ELSR
ELSR --> VM_ENTRY
HCR --> HW_HCR
HCR --> VM_ENTRY
VM_EXIT --> APR
VM_EXIT --> ELSR
VM_EXIT --> HCR
Sources: src/vgicc.rs(L8 - L10)
Interrupt Configuration State
The Vgicc maintains per-CPU interrupt configuration for Software Generated Interrupts (SGIs) and Private Peripheral Interrupts (PPIs):
SGI and PPI Management
flowchart TD
subgraph subGraph2["Per-bit Mapping"]
BIT0["Bit 0: SGI 0"]
BIT15["Bit 15: SGI 15"]
BIT16["Bit 16: PPI 16"]
BIT31["Bit 31: PPI 31"]
end
subgraph subGraph1["Vgicc Configuration"]
ISENABLER["isenabler: u3232-bit enable maskOne bit per interrupt 0-31"]
PRIORITYR["priorityr: [u8; 32]8-bit priority per PPIIndices 0-31 for PPI IDs 0-31"]
end
subgraph subGraph0["Interrupt ID Space"]
SGI["SGI IDs 0-15Software Generated"]
PPI["PPI IDs 16-31Private Peripheral"]
end
ISENABLER --> BIT0
ISENABLER --> BIT15
ISENABLER --> BIT16
ISENABLER --> BIT31
PPI --> ISENABLER
PPI --> PRIORITYR
SGI --> ISENABLER
Sources: src/vgicc.rs(L12 - L13) src/consts.rs(L1 - L2)
The isenabler field uses individual bits to track enable/disable state for each of the 32 SGI and PPI interrupts, while priorityr stores 8-bit priority values specifically for the 32 PPI interrupts.
Integration with VGIC System
The Vgicc operates as part of the larger VGIC virtualization system:
System Integration Flow
flowchart TD
subgraph subGraph2["Physical Hardware"]
PHYSICAL_GIC["arm_gicv2Physical GIC Driver"]
HW_CPU_IF["Hardware CPUInterface Registers"]
end
subgraph subGraph1["VGIC System"]
VGIC_MAIN["Vgic Controller(Main coordinator)"]
VGICC_INSTANCES["Multiple Vgicc(Per-CPU instances)"]
end
subgraph subGraph0["Guest VM"]
GUEST["Guest OSInterrupt Operations"]
end
GUEST --> VGIC_MAIN
PHYSICAL_GIC --> HW_CPU_IF
VGICC_INSTANCES --> HW_CPU_IF
VGICC_INSTANCES --> PHYSICAL_GIC
VGIC_MAIN --> VGICC_INSTANCES
Sources: src/vgicc.rs(L1 - L14)
Each Vgicc instance manages the virtualization state for one CPU core, coordinating with the main Vgic controller to provide a complete virtual interrupt controller implementation to guest operating systems.
Device Operations Interface
Relevant source files
Purpose and Scope
This document covers the Device Operations Interface implementation in the arm_vgic crate, which provides the integration layer between the Virtual Generic Interrupt Controller (VGIC) and the ArceOS device framework. The interface enables the hypervisor to expose the virtual GIC as a memory-mapped device to guest virtual machines.
For details about the core VGIC controller functionality, see Virtual GIC Controller (Vgic). For information about the CPU interface components, see CPU Interface (Vgicc).
BaseDeviceOps Trait Implementation
The VGIC integrates with the ArceOS device framework through the BaseDeviceOps trait implementation. This trait provides the standard interface for emulated devices within the hypervisor ecosystem.
Device Operations Structure
flowchart TD
subgraph subGraph3["VGIC Core Methods"]
Read8["handle_read8()"]
Read16["handle_read16()"]
Read32["handle_read32()"]
Write8["handle_write8()"]
Write16["handle_write16()"]
Write32["handle_write32()"]
end
subgraph subGraph2["VGIC Implementation"]
VgicStruct["VgicMain Controller"]
DevOpsImpl["impl BaseDeviceOps for Vgic"]
subgraph subGraph1["Trait Methods"]
EmuType["emu_type()→ EmuDeviceTGicdV2"]
AddrRange["address_range()→ 0x800_0000-0x800_FFFF"]
HandleRead["handle_read()→ Width Dispatch"]
HandleWrite["handle_write()→ Width Dispatch"]
end
end
subgraph subGraph0["ArceOS Device Framework"]
BaseDeviceOps["BaseDeviceOpsTrait"]
EmuDeviceType["EmuDeviceTypeEnum"]
GuestPhysAddr["GuestPhysAddrAddress Type"]
end
BaseDeviceOps --> DevOpsImpl
DevOpsImpl --> AddrRange
DevOpsImpl --> EmuType
DevOpsImpl --> HandleRead
DevOpsImpl --> HandleWrite
HandleRead --> Read16
HandleRead --> Read32
HandleRead --> Read8
HandleWrite --> Write16
HandleWrite --> Write32
HandleWrite --> Write8
VgicStruct --> DevOpsImpl
Sources: src/devops_impl.rs(L1 - L99) src/vgic.rs(L32 - L34)
Device Type and Address Mapping
The VGIC device is identified as EmuDeviceTGicdV2, indicating it emulates a Generic Interrupt Controller Distributor version 2. The device occupies a 64KB memory region in the guest physical address space.
| Property | Value | Description |
|---|---|---|
| Device Type | EmuDeviceTGicdV2 | GICv2 Distributor Emulation |
| Base Address | 0x800_0000 | Guest physical start address |
| Size | 0x10000(64KB) | Total address space |
| Address Mask | 0xfff | 4KB page alignment |
Address Range Implementation
flowchart TD
subgraph subGraph2["Register Space"]
RegisterOffset["Register OffsetWithin 4KB page"]
VGICDRegs["VGICD RegistersControl, Enable, Priority, etc."]
end
subgraph subGraph1["Address Processing"]
AddrRange["address_range()Returns: 0x800_0000-0x800_FFFF"]
AddrMask["Address Maskingaddr & 0xfff"]
end
subgraph subGraph0["Guest VM Memory Access"]
GuestAccess["Guest Memory Access0x800_0000 + offset"]
end
AddrMask --> RegisterOffset
AddrRange --> AddrMask
GuestAccess --> AddrRange
RegisterOffset --> VGICDRegs
Sources: src/devops_impl.rs(L29 - L31) src/devops_impl.rs(L47) src/devops_impl.rs(L79)
Read Operation Handling
The handle_read method provides width-based dispatching for memory read operations. The implementation supports 8-bit, 16-bit, and 32-bit read operations.
Read Operation Flow
flowchart TD
subgraph subGraph2["VGIC Read Handlers"]
HandleRead8["handle_read8(addr)Returns: AxResult"]
HandleRead16["handle_read16(addr)Returns: AxResult"]
HandleRead32["handle_read32(addr)Returns: AxResult"]
end
subgraph subGraph1["Width Dispatch"]
WidthMatch["match width"]
Width1["width == 18-bit read"]
Width2["width == 216-bit read"]
Width4["width == 432-bit read"]
WidthOther["_ => Ok(0)Unsupported width"]
end
subgraph subGraph0["Read Entry Point"]
HandleRead["handle_read()Parameters: addr, width"]
AddrMask["addr = addr.as_usize() & 0xfff"]
end
AddrMask --> WidthMatch
HandleRead --> AddrMask
Width1 --> HandleRead8
Width2 --> HandleRead16
Width4 --> HandleRead32
WidthMatch --> Width1
WidthMatch --> Width2
WidthMatch --> Width4
WidthMatch --> WidthOther
Sources: src/devops_impl.rs(L45 - L66) src/vgic.rs(L56 - L66)
Write Operation Handling
The handle_write method manages memory write operations with similar width-based dispatching. Write operations modify the virtual GIC state and may trigger hardware GIC operations.
Write Operation Dispatch and Register Handling
flowchart TD
subgraph subGraph3["State Management"]
MutexLock["vgic_inner.lock()"]
CtrlrUpdate["vgic_inner.ctrlr = val & 0b11"]
IRQLoop["Loop: SGI_ID_MAX..SPI_ID_MAX"]
GicInterface["GicInterface::set_enable()GicInterface::set_priority()"]
end
subgraph subGraph2["Register Address Matching"]
AddrMatch["match addr"]
VGICD_CTLR["VGICD_CTLRControl Register"]
VGICD_ISENABLER["VGICD_ISENABLER_*Enable Registers"]
UnknownAddr["_ => error!()"]
end
subgraph subGraph1["Width Dispatch"]
WriteMatch["match width"]
WriteWidth1["width == 1handle_write8()"]
WriteWidth2["width == 2handle_write16()"]
WriteWidth4["width == 4handle_write32()"]
end
subgraph subGraph0["Write Entry Point"]
HandleWrite["handle_write()Parameters: addr, width, val"]
WriteMask["addr = addr.as_usize() & 0xfff"]
end
AddrMatch --> UnknownAddr
AddrMatch --> VGICD_CTLR
AddrMatch --> VGICD_ISENABLER
CtrlrUpdate --> IRQLoop
HandleWrite --> WriteMask
IRQLoop --> GicInterface
MutexLock --> CtrlrUpdate
VGICD_CTLR --> MutexLock
WriteMask --> WriteMatch
WriteMatch --> WriteWidth1
WriteMatch --> WriteWidth2
WriteMatch --> WriteWidth4
WriteWidth1 --> AddrMatch
WriteWidth2 --> AddrMatch
WriteWidth4 --> AddrMatch
Sources: src/devops_impl.rs(L77 - L98) src/vgic.rs(L68 - L133)
Integration with VGIC Core
The device operations interface serves as the bridge between guest VM memory accesses and the internal VGIC state management. Key integration points include:
Core Integration Components
| Component | Purpose | Implementation |
|---|---|---|
| VgicInner | Protected state storage | Mutex-wrapped internal state |
| Register handlers | Address-specific logic | Match-based dispatch in write methods |
| Hardware interface | Physical GIC operations | arm_gicv2::GicInterfacecalls |
| Error handling | Operation validation | AxResult |
State Synchronization
The interface ensures thread-safe access to the VGIC state through mutex protection. Write operations that modify interrupt enable states trigger corresponding operations on the physical GIC hardware.
flowchart TD
subgraph subGraph2["Hardware Interface"]
GicSetEnable["GicInterface::set_enable()"]
GicSetPriority["GicInterface::set_priority()"]
PhysicalGIC["Physical GIC Hardware"]
end
subgraph subGraph1["VGIC Processing"]
DevOpsWrite["handle_write()"]
StateUpdate["VgicInner Updateused_irq[] modification"]
MutexProtection["Mutex"]
end
subgraph subGraph0["Guest Operation"]
GuestWrite["Guest WriteEnable Interrupt"]
end
DevOpsWrite --> MutexProtection
GicSetEnable --> PhysicalGIC
GicSetPriority --> PhysicalGIC
GuestWrite --> DevOpsWrite
MutexProtection --> StateUpdate
StateUpdate --> GicSetEnable
StateUpdate --> GicSetPriority
Sources: src/vgic.rs(L15 - L30) src/vgic.rs(L76 - L93)
Constants and Register Layout
Relevant source files
This page documents the system constants, register offsets, and memory layout configuration used by the Virtual Generic Interrupt Controller (VGIC) implementation. These constants define the interrupt ID ranges, register mappings, and hardware configuration limits that govern how the virtual interrupt controller emulates ARM GIC functionality for guest virtual machines.
For information about how these constants are used in the main VGIC implementation, see Virtual GIC Controller (Vgic). For details on the CPU interface configuration, see CPU Interface (Vgicc).
Interrupt ID Ranges and Classifications
The VGIC implementation supports three categories of interrupts as defined by the ARM GIC specification, each with specific ID ranges and usage patterns.
Interrupt Type Limits
| Interrupt Type | Constant | Value | ID Range | Description |
|---|---|---|---|---|
| Software Generated Interrupts (SGI) | SGI_ID_MAX | 16 | 0-15 | Inter-processor interrupts for VM communication |
| Private Peripheral Interrupts (PPI) | PPI_ID_MAX | 32 | 16-31 | Per-CPU private interrupts |
| Shared Peripheral Interrupts (SPI) | SPI_ID_MAX | 512 | 32-543 | Shared interrupts across multiple CPUs |
List Register Configuration
The CPU interface supports a fixed number of list registers for interrupt virtualization:
| Configuration | Constant | Value | Purpose |
|---|---|---|---|
| List Registers | GICD_LR_NUM | 4 | Maximum number of pending interrupts per CPU |
Interrupt ID Space Layout
flowchart TD
subgraph subGraph0["Interrupt ID Space (0-543)"]
SGI["SGI0-15SGI_ID_MAX=16"]
PPI["PPI16-31PPI_ID_MAX=32"]
SPI["SPI32-543SPI_ID_MAX=512"]
end
PPI --> SPI
SGI --> PPI
Sources: src/consts.rs(L1 - L4)
VGIC Distributor Register Layout
The Virtual GIC Distributor (VGICD) registers are mapped to specific offsets within the device memory space. These offsets correspond to standard ARM GIC register locations that guest VMs expect to access.
Control and Status Registers
| Register | Constant | Offset | Access | Purpose |
|---|---|---|---|---|
| Control Register | VGICD_CTLR | 0x0000 | RW | Global interrupt controller enable/disable |
Interrupt Enable Registers
The interrupt enable registers are split by interrupt type to optimize access patterns:
| Register | Constant | Offset | Access | Purpose |
|---|---|---|---|---|
| Set Enable SGI/PPI | VGICD_ISENABLER_SGI_PPI | 0x0100 | RW | Enable SGI and PPI interrupts (ID 0-31) |
| Set Enable SPI | VGICD_ISENABLER_SPI | 0x0104 | RW | Enable SPI interrupts (ID 32+) |
| Clear Enable SGI/PPI | VGICD_ICENABLER_SGI_PPI | 0x0180 | RW | Disable SGI and PPI interrupts (ID 0-31) |
| Clear Enable SPI | VGICD_ICENABLER_SPI | 0x0184 | RW | Disable SPI interrupts (ID 32+) |
Interrupt State Registers
| Register | Constant | Offset | Access | Purpose |
|---|---|---|---|---|
| Set Pending | VGICD_ISPENDR | 0x0200 | RW | Set interrupt pending state |
| Clear Pending | VGICD_ICPENDR | 0x5 | RW | Clear interrupt pending state |
| Set Active | VGICD_ISACTIVER | 0x6 | RW | Set interrupt active state |
| Clear Active | VGICD_ICACTIVER | 0x7 | RW | Clear interrupt active state |
Configuration and Control Registers
| Register | Constant | Offset | Access | Purpose |
|---|---|---|---|---|
| Configuration | VGICD_ICFGR | 0x18 | RW | Interrupt trigger type configuration |
| Software Generated Interrupt | VGICD_SGIR | 0x1e | WO | Generate software interrupts |
VGIC Register Memory Map
flowchart TD
subgraph subGraph4["VGIC Distributor Register Space"]
ISACTIVER["VGICD_ISACTIVERSet Active"]
ICACTIVER["VGICD_ICACTIVERClear Active"]
ICENABLER_SGIPPI["VGICD_ICENABLER_SGI_PPI0x0180Clear Enable SGI/PPI"]
subgraph subGraph1["Enable Registers (0x0100-0x01FF)"]
ICFGR["VGICD_ICFGRConfiguration"]
SGIR["VGICD_SGIRSoftware Generated IRQ"]
ISPENDR["VGICD_ISPENDR0x0200Set Pending"]
ICPENDR["VGICD_ICPENDRClear Pending"]
ISENABLER_SGIPPI["VGICD_ISENABLER_SGI_PPI0x0100Set Enable SGI/PPI"]
ISENABLER_SPI["VGICD_ISENABLER_SPI0x0104Set Enable SPI"]
ICENABLER_SPI["VGICD_ICENABLER_SPI0x0184Clear Enable SPI"]
CTLR["VGICD_CTLR0x0000Control Register"]
subgraph subGraph2["Pending/Active Registers (0x0200+)"]
ISACTIVER["VGICD_ISACTIVERSet Active"]
ICACTIVER["VGICD_ICACTIVERClear Active"]
ICENABLER_SGIPPI["VGICD_ICENABLER_SGI_PPI0x0180Clear Enable SGI/PPI"]
subgraph subGraph3["Configuration (Various Offsets)"]
ICFGR["VGICD_ICFGRConfiguration"]
SGIR["VGICD_SGIRSoftware Generated IRQ"]
ISPENDR["VGICD_ISPENDR0x0200Set Pending"]
ICPENDR["VGICD_ICPENDRClear Pending"]
ISENABLER_SGIPPI["VGICD_ISENABLER_SGI_PPI0x0100Set Enable SGI/PPI"]
ISENABLER_SPI["VGICD_ISENABLER_SPI0x0104Set Enable SPI"]
CTLR["VGICD_CTLR0x0000Control Register"]
end
subgraph subGraph0["Control Block (0x0000-0x00FF)"]
ICFGR["VGICD_ICFGRConfiguration"]
ISPENDR["VGICD_ISPENDR0x0200Set Pending"]
ISENABLER_SGIPPI["VGICD_ISENABLER_SGI_PPI0x0100Set Enable SGI/PPI"]
CTLR["VGICD_CTLR0x0000Control Register"]
end
end
end
end
Sources: src/consts.rs(L6 - L19)
Register Access Patterns
The register layout follows ARM GIC specification patterns where related registers are grouped by function and interrupt type. The separation of SGI/PPI and SPI enable registers reflects the different handling requirements for private versus shared interrupts.
Enable Register Organization
Enable Register Bit Mapping
flowchart TD
subgraph subGraph1["VGICD_ISENABLER_SPI (0x0104)"]
SPI_BITS["Bits 0-31SPI Enable(ID 32-63)"]
SPI_NEXT["Additional SPIregisters forID 64+"]
SGI_BITS["Bits 0-15SGI Enable(ID 0-15)"]
PPI_BITS["Bits 16-31PPI Enable(ID 16-31)"]
end
subgraph subGraph0["VGICD_ISENABLER_SGI_PPI (0x0100)"]
SPI_BITS["Bits 0-31SPI Enable(ID 32-63)"]
SPI_NEXT["Additional SPIregisters forID 64+"]
SGI_BITS["Bits 0-15SGI Enable(ID 0-15)"]
PPI_BITS["Bits 16-31PPI Enable(ID 16-31)"]
end
SGI_BITS --> PPI_BITS
SPI_BITS --> SPI_NEXT
Register Offset Patterns
The register offsets follow standard ARM GIC conventions:
- 0x0000-0x00FF: Control and identification registers
- 0x0100-0x017F: Interrupt Set Enable registers
- 0x0180-0x01FF: Interrupt Clear Enable registers
- 0x0200-0x027F: Interrupt Set Pending registers
- 0x0280-0x02FF: Interrupt Clear Pending registers
This layout ensures compatibility with guest operating systems expecting standard GIC register locations while allowing the hypervisor to intercept and virtualize all interrupt controller access.
Sources: src/consts.rs(L7 - L19)
Dependencies and Integration
Relevant source files
This document analyzes the external dependencies of the arm_vgic crate and explains how it integrates with the broader ArceOS hypervisor ecosystem. It covers dependency categorization, version management, integration patterns, and build system configuration.
For detailed information about the internal system architecture, see System Architecture. For documentation of specific component implementations, see Core Components.
Dependency Overview
The arm_vgic crate integrates with the ArceOS hypervisor ecosystem through a carefully structured set of dependencies that provide device framework support, memory management, hardware abstraction, and system utilities.
Dependency Categories
The dependencies can be categorized into four main groups based on their role in the system:
| Category | Dependencies | Purpose |
|---|---|---|
| ArceOS Framework | axdevice_base,axaddrspace,axerrno | Core hypervisor framework integration |
| Hardware Abstraction | arm_gicv2,memory_addr | Physical hardware interface and memory primitives |
| System Utilities | spin,log | Thread synchronization and logging |
| Development Tools | (Cargo build system) | Build configuration and dependency resolution |
Dependency Resolution Architecture
flowchart TD
subgraph subGraph4["Transitive Dependencies"]
TOCK["tock-registers"]
CRATE_IF["crate_interface"]
BITFLAGS["bitflags"]
MEMORY_SET["memory_set"]
end
subgraph subGraph3["System Utilities"]
SPIN["spin::Mutex"]
LOG["log macros"]
end
subgraph subGraph2["Hardware Abstraction"]
ARM_GIC["arm_gicv2"]
MEM_ADDR["memory_addr"]
end
subgraph subGraph1["ArceOS Framework Dependencies"]
AXDEVICE["axdevice_base"]
AXADDR["axaddrspace"]
AXERRNO["axerrno"]
end
subgraph subGraph0["arm_vgic Crate"]
VGIC["Vgic"]
DEVOPS["BaseDeviceOps"]
VGICC["Vgicc"]
end
ARM_GIC --> CRATE_IF
ARM_GIC --> TOCK
AXADDR --> BITFLAGS
AXADDR --> MEMORY_SET
AXDEVICE --> AXADDR
DEVOPS --> AXDEVICE
VGIC --> ARM_GIC
VGIC --> AXADDR
VGIC --> AXERRNO
VGIC --> LOG
VGIC --> MEM_ADDR
VGIC --> SPIN
Sources: Cargo.toml(L7 - L17) Cargo.lock(L24 - L34)
ArceOS Framework Integration
Device Framework Integration
The arm_vgic crate integrates with the ArceOS device framework through the axdevice_base dependency, which provides the foundational device abstraction layer.
flowchart TD
subgraph subGraph1["axdevice_base Components"]
BASE_DEV["BaseDevice"]
ADDR_RANGE["AddressRange"]
DEV_ERROR["DeviceError"]
end
subgraph subGraph0["Device Framework Flow"]
VM["VM Memory Access"]
TRAIT["BaseDeviceOps trait"]
IMPL["DevOps Implementation"]
VGIC_CORE["Vgic Core Logic"]
end
AXADDR_DEP["axaddrspace"]
AXERRNO_DEP["axerrno"]
BASE_DEV --> AXADDR_DEP
DEV_ERROR --> AXERRNO_DEP
IMPL --> ADDR_RANGE
IMPL --> BASE_DEV
IMPL --> DEV_ERROR
IMPL --> VGIC_CORE
TRAIT --> IMPL
VM --> TRAIT
Sources: Cargo.toml(L8) Cargo.lock(L60 - L69)
Memory Management Integration
The axaddrspace dependency provides memory management capabilities including address space abstraction and page table management.
Key Integration Points:
- Address Translation: Converting guest virtual addresses to host physical addresses
- Memory Protection: Enforcing access permissions for virtualized GIC registers
- Address Range Validation: Ensuring memory accesses fall within valid GIC register ranges
Error Handling Integration
The axerrno dependency provides standardized error handling across the ArceOS ecosystem.
Error Integration Pattern:
- Device operation errors propagate through
axerrno::AxResult<T> - Error codes maintain consistency with the broader hypervisor framework
- Logging integration provides structured error reporting
Sources: Cargo.toml(L9 - L11) Cargo.lock(L43 - L57) Cargo.lock(L72 - L78)
Hardware Abstraction Layer
Physical GIC Driver Integration
The arm_gicv2 dependency provides the interface to physical ARM Generic Interrupt Controller hardware.
flowchart TD
subgraph subGraph2["Hardware Registers"]
GICD["GICD_* Registers"]
GICC["GICC_* Registers"]
GICH["GICH_* Registers"]
end
subgraph subGraph1["arm_gicv2 Physical Driver"]
GIC_INTERFACE["GicInterface"]
GIC_DISTRIBUTOR["GicDistributor"]
GIC_CPU["GicCpu"]
PHYS_OPS["Physical Operations"]
end
subgraph subGraph0["VGIC Virtualization Layer"]
VGIC_CTRL["Vgic Controller"]
REG_HANDLERS["Register Handlers"]
STATE_MGT["State Management"]
end
GIC_INTERFACE --> PHYS_OPS
PHYS_OPS --> GICC
PHYS_OPS --> GICD
PHYS_OPS --> GICH
REG_HANDLERS --> GIC_INTERFACE
STATE_MGT --> GIC_CPU
STATE_MGT --> GIC_DISTRIBUTOR
Integration Characteristics:
- Version Pinning: Uses specific git revision
2289063for stability - Hardware Abstraction: Provides register-level access through
tock-registers - Interface Consistency: Maintains consistent API with virtual GIC operations
Sources: Cargo.toml(L12) Cargo.lock(L15 - L21)
Memory Address Primitives
The memory_addr dependency provides type-safe memory address handling.
Memory Address Types Used:
PhysAddr: Physical memory addresses for hardware register accessVirtAddr: Virtual addresses for guest memory mappingHostVirtAddr: Host virtual addresses for hypervisor operations
Sources: Cargo.toml(L10) Cargo.lock(L150 - L159)
System Utilities and Threading
Synchronization Primitives
The spin dependency provides lock-free synchronization primitives essential for hypervisor operation.
flowchart TD
subgraph subGraph1["spin Crate Primitives"]
SPIN_MUTEX["spin::Mutex"]
LOCK_API["lock_api"]
MUTEX_GUARD_TYPE["MutexGuard"]
end
subgraph subGraph0["VGIC Synchronization Model"]
VGIC_STRUCT["Vgic"]
MUTEX_GUARD["Mutex"]
INNER_STATE["VgicInner"]
CRITICAL_SECTION["Critical Sections"]
end
INNER_STATE --> CRITICAL_SECTION
MUTEX_GUARD --> INNER_STATE
MUTEX_GUARD --> SPIN_MUTEX
SPIN_MUTEX --> LOCK_API
SPIN_MUTEX --> MUTEX_GUARD_TYPE
VGIC_STRUCT --> MUTEX_GUARD
Synchronization Strategy:
- Lock-Free Design: Uses spinlocks suitable for hypervisor context
- Fine-Grained Locking: Protects internal VGIC state without blocking interrupt handling
- Deadlock Prevention: Consistent lock ordering across operations
Sources: Cargo.toml(L17) Cargo.lock(L271 - L277)
Logging Infrastructure
The log dependency provides structured logging capabilities integrated with the ArceOS logging framework.
Logging Integration:
- Debug Information: Register access patterns and state transitions
- Error Reporting: Failed operations and validation errors
- Performance Monitoring: Operation timing and frequency analysis
Sources: Cargo.toml(L13) Cargo.lock(L144 - L147)
Build System Integration
Version Management Strategy
The build system uses a mixed approach to dependency version management:
| Dependency Type | Version Strategy | Rationale |
|---|---|---|
| ArceOS Framework | Git repositories with latest | Active development coordination |
| Hardware Drivers | Git with pinned revision | Stability and compatibility |
| System Utilities | Semantic versioning | Mature, stable APIs |
Dependency Resolution Chain
flowchart TD
subgraph subGraph1["Transitive Dependencies"]
TRANS1["tock-registers"]
TRANS2["crate_interface"]
TRANS3["bitflags"]
TRANS4["lock_api"]
TRANS5["memory_set"]
TRANS6["page_table_entry"]
end
subgraph subGraph0["Direct Dependencies"]
ARM_VGIC["arm_vgic"]
DEP1["axdevice_base"]
DEP2["axaddrspace"]
DEP3["arm_gicv2"]
DEP4["memory_addr"]
DEP5["axerrno"]
DEP6["spin"]
DEP7["log"]
end
ARM_VGIC --> DEP1
ARM_VGIC --> DEP2
ARM_VGIC --> DEP3
ARM_VGIC --> DEP4
ARM_VGIC --> DEP5
ARM_VGIC --> DEP6
ARM_VGIC --> DEP7
DEP2 --> TRANS3
DEP2 --> TRANS5
DEP2 --> TRANS6
DEP3 --> TRANS1
DEP3 --> TRANS2
DEP6 --> TRANS4
Build Configuration Characteristics:
- Edition 2021: Uses latest Rust edition features
- No Default Features: Minimal dependency surface
- Git Source Priority: Framework dependencies use git sources for latest features
Sources: Cargo.toml(L1 - L18) Cargo.lock(L1 - L330)
Integration Patterns and Architectural Considerations
Layered Integration Model
The arm_vgic crate follows a layered integration pattern that maintains clear separation of concerns:
- Framework Layer: Integrates with ArceOS device and memory management
- Abstraction Layer: Uses hardware abstraction for physical GIC access
- Utility Layer: Leverages system utilities for synchronization and logging
- Application Layer: Exposes virtualization capabilities to hypervisor
Dependency Injection and Inversion of Control
The crate uses dependency inversion principles where high-level virtualization logic depends on abstractions rather than concrete implementations, enabling:
- Testability: Mock implementations for unit testing
- Portability: Different hardware backends through common interfaces
- Maintainability: Clear separation between virtualization logic and platform specifics
Sources: Cargo.toml(L7 - L17) Cargo.lock(L24 - L34)
Dependency Analysis
Relevant source files
This document provides a comprehensive analysis of the external dependencies used by the arm_vgic crate and how they integrate to enable virtual interrupt controller functionality within the ArceOS hypervisor ecosystem. The analysis covers direct dependencies, their roles in the system architecture, version constraints, and integration patterns.
For information about the overall system architecture and component interactions, see System Architecture. For details about how dependencies are configured in the build system, see Build Configuration.
Direct Dependencies Overview
The arm_vgic crate relies on seven direct dependencies that provide foundational capabilities for device emulation, memory management, hardware abstraction, and system utilities. Each dependency serves a specific architectural role in implementing the virtual GIC functionality.
ArceOS Framework Dependencies
The core ArceOS framework dependencies provide the foundational abstractions for device emulation and memory management within the hypervisor environment.
| Dependency | Version/Source | Primary Role |
|---|---|---|
| axdevice_base | Git repository | Device framework integration and trait definitions |
| axaddrspace | Git repository | Virtual memory management and address space operations |
| axerrno | 0.1.0 | Standardized error handling across ArceOS components |
Device Framework Integration
The axdevice_base dependency provides the BaseDeviceOps trait that enables the VGIC to integrate with the ArceOS device management framework. This integration is implemented in devops_impl.rs(L1 - L87) where the Vgic struct implements device operations for memory-mapped I/O handling.
Memory Management Abstraction
The axaddrspace dependency supplies memory management primitives used throughout the VGIC implementation for address translation, memory region management, and virtual memory operations. The integration occurs primarily in the device operations where guest physical addresses are translated and validated.
Sources: Cargo.toml(L7 - L11) Cargo.lock(L43 - L78)
Hardware Abstraction Dependencies
These dependencies provide low-level hardware abstractions and type definitions for ARM GIC hardware and memory address handling.
| Dependency | Version/Source | Hardware Component |
|---|---|---|
| arm_gicv2 | Git commit 2289063 | Physical ARM GICv2 hardware interface |
| memory_addr | 0.3 | Memory address types and operations |
Physical GIC Interface
The arm_gicv2 dependency provides the hardware abstraction layer for interfacing with physical ARM Generic Interrupt Controller version 2 hardware. The VGIC uses this interface to forward virtual interrupt operations to the underlying physical hardware when necessary.
flowchart TD
subgraph subGraph1["Memory Abstraction"]
MEMORY_ADDR["memory_addr types"]
ADDR_OPS["Address Operations"]
end
subgraph subGraph0["Hardware Abstraction Layer"]
VGIC["Vgic struct"]
ARM_GIC["arm_gicv2::GicInterface"]
PHYSICAL["Physical GIC Hardware"]
end
ARM_GIC --> PHYSICAL
MEMORY_ADDR --> ADDR_OPS
VGIC --> ARM_GIC
VGIC --> MEMORY_ADDR
Sources: Cargo.toml(L10 - L12) Cargo.lock(L15 - L159)
System Utility Dependencies
These dependencies provide essential system-level utilities for logging, synchronization, and concurrent access control.
| Dependency | Version | Functionality |
|---|---|---|
| spin | 0.9 | Spinlock-based synchronization primitives |
| log | 0.4.21 | Structured logging and debug output |
Synchronization Infrastructure
The spin crate provides the Mutex type used to protect the VgicInner state from concurrent access. This is critical for thread-safe operation of the virtual interrupt controller across multiple CPU cores.
Logging Framework
The log crate enables structured logging throughout the VGIC implementation for debugging, monitoring, and system analysis. Log statements are used extensively in interrupt handling paths for troubleshooting virtualization behavior.
Sources: Cargo.toml(L13 - L17) Cargo.lock(L144 - L277)
Dependency Relationship Analysis
The following diagram illustrates how dependencies interact and form the foundation for VGIC functionality:
flowchart TD
subgraph subGraph2["Transitive Dependencies"]
TOCK["tock-registers"]
BITFLAGS["bitflags"]
LOCK_API["lock_api"]
SERDE["serde"]
end
subgraph subGraph1["Direct Dependencies"]
AXDEVICE["axdevice_base"]
AXADDR["axaddrspace"]
AXERRNO["axerrno"]
ARM_GIC["arm_gicv2"]
MEMORY_ADDR["memory_addr"]
SPIN["spin"]
LOG["log"]
end
subgraph subGraph0["arm_vgic Crate"]
VGIC_IMPL["Vgic Implementation"]
DEVOPS_IMPL["BaseDeviceOps Implementation"]
VGIC_INNER["VgicInner Protected State"]
end
ARM_GIC --> TOCK
AXADDR --> BITFLAGS
AXDEVICE --> SERDE
DEVOPS_IMPL --> AXDEVICE
SPIN --> LOCK_API
VGIC_IMPL --> ARM_GIC
VGIC_IMPL --> AXADDR
VGIC_IMPL --> AXERRNO
VGIC_IMPL --> LOG
VGIC_IMPL --> MEMORY_ADDR
VGIC_INNER --> SPIN
Sources: Cargo.toml(L7 - L17) Cargo.lock(L24 - L34)
Version Constraints and Compatibility
The dependency version strategy reflects the development status and integration requirements within the ArceOS ecosystem.
Git-based Dependencies
Four dependencies are sourced directly from Git repositories, indicating active development and tight integration with the ArceOS ecosystem:
| Repository | Commit/Branch | Integration Level |
|---|---|---|
| axdevice_crates.git | Latest | Core device framework |
| axaddrspace.git | Latest | Memory management |
| arm_gicv2.git | Commit 2289063 | Hardware abstraction |
The use of specific commit hashes for arm_gicv2 ensures reproducible builds while allowing controlled updates to the hardware abstraction layer.
Crates.io Dependencies
Standard dependencies from crates.io follow semantic versioning with conservative version constraints:
flowchart TD
subgraph subGraph1["Compatibility Strategy"]
CONSERVATIVE["Conservative Updates"]
STABILITY["API Stability"]
ECOSYSTEM["Ecosystem Integration"]
end
subgraph subGraph0["Version Constraints"]
MEMORY["memory_addr = 0.3"]
AXERRNO["axerrno = 0.1.0"]
LOG["log = 0.4.21"]
SPIN["spin = 0.9"]
end
AXERRNO --> STABILITY
LOG --> ECOSYSTEM
MEMORY --> CONSERVATIVE
SPIN --> STABILITY
Sources: Cargo.toml(L10 - L17)
Integration Patterns
The dependencies are integrated into the VGIC implementation through several distinct patterns that reflect their architectural roles.
Device Framework Integration Pattern
The axdevice_base integration follows the trait-based device abstraction pattern:
flowchart TD
subgraph subGraph0["Device Framework Pattern"]
TRAIT["BaseDeviceOps trait"]
IMPL["Vgic BaseDeviceOps impl"]
METHODS["read/write methods"]
DISPATCH["Width-based dispatch"]
end
IMPL --> METHODS
METHODS --> DISPATCH
TRAIT --> IMPL
This pattern enables the VGIC to participate in the broader ArceOS device management ecosystem while maintaining type safety and performance.
Hardware Abstraction Pattern
The arm_gicv2 dependency is used through a delegation pattern where virtual operations are selectively forwarded to physical hardware:
flowchart TD
subgraph subGraph0["Hardware Delegation Pattern"]
VIRTUAL_OP["Virtual GIC Operation"]
VALIDATION["Operation Validation"]
DECISION["Forward Decision"]
PHYSICAL_OP["Physical GIC Operation"]
STATE_UPDATE["Virtual State Update"]
end
DECISION --> PHYSICAL_OP
DECISION --> STATE_UPDATE
VALIDATION --> DECISION
VIRTUAL_OP --> VALIDATION
Synchronization Pattern
The spin dependency provides thread-safe access to shared VGIC state through a mutex-protected inner structure:
flowchart TD
subgraph subGraph0["Synchronization Pattern"]
EXTERNAL_ACCESS["External Access Request"]
MUTEX_ACQUIRE["spin::Mutex::lock()"]
PROTECTED_ACCESS["VgicInner Access"]
MUTEX_RELEASE["Automatic Drop"]
end
EXTERNAL_ACCESS --> MUTEX_ACQUIRE
MUTEX_ACQUIRE --> PROTECTED_ACCESS
PROTECTED_ACCESS --> MUTEX_RELEASE
Sources: devops_impl.rs(L1 - L87) vgic.rs(L1 - L200)
Transitive Dependency Analysis
The transitive dependencies reveal the underlying infrastructure that supports the direct dependencies:
| Transitive Dependency | Used By | Purpose |
|---|---|---|
| tock-registers | arm_gicv2 | Register-level hardware access |
| bitflags | axaddrspace | Bit field manipulation |
| lock_api | spin | Lock trait abstractions |
| serde | axdevice_base | Serialization support |
| memory_addr(0.2.1) | axaddrspace | Address type compatibility |
The presence of two versions of memory_addr (0.2.1 and 0.3.0) indicates a transitional period in the ecosystem where different components are migrating to newer versions at different rates.
Sources: Cargo.lock(L150 - L168)
Dependency Security and Maintenance
All dependencies follow established Rust ecosystem patterns for security and maintenance:
- Crates.io dependencies benefit from the Rust Security Advisory database monitoring
- Git dependencies require manual monitoring but provide direct control over updates
- Version pinning for
arm_gicv2ensures consistent hardware behavior across builds - Semantic versioning for utility crates allows safe automatic updates within major version boundaries
The dependency structure supports both stability (through version constraints) and flexibility (through Git-sourced framework components) required for hypervisor development.
Sources: Cargo.toml(L1 - L18) Cargo.lock(L1 - L330)
Build Configuration
Relevant source files
This document covers the build system configuration for the arm_vgic crate, including package metadata, dependency management, and development workflow. It explains how the crate is compiled and integrated within the ArceOS hypervisor ecosystem.
For information about runtime dependencies and system integration, see Dependency Analysis. For architectural details about the components themselves, see Core Components.
Package Configuration
The arm_vgic crate is configured as a standard Rust library package using Cargo. The package metadata defines the basic build parameters and compilation target.
| Configuration | Value | Purpose |
|---|---|---|
| Package Name | arm_vgic | Crate identifier for dependency resolution |
| Rust Edition | 2021 | Language feature set and compatibility |
| Package Type | Library | Compiled as a library crate for integration |
Build Targets and Artifacts
flowchart TD
subgraph subGraph3["Integration Points"]
HYPERVISOR["ArceOS Hypervisor"]
DEVICE_FRAMEWORK["axdevice_base"]
end
subgraph subGraph2["Build Artifacts"]
RLIB["libarm_vgic.rlib"]
METADATA["Crate Metadata"]
TARGET_DIR["target/ directory"]
end
subgraph subGraph1["Cargo Build Process"]
CARGO_BUILD["cargo build"]
RUSTC["rustc compiler"]
DEP_RESOLUTION["Dependency Resolution"]
end
subgraph subGraph0["Source Files"]
SRC_LIB["src/lib.rs"]
SRC_VGIC["src/vgic.rs"]
SRC_VGICC["src/vgicc.rs"]
SRC_CONSTS["src/consts.rs"]
SRC_DEVOPS["src/devops_impl.rs"]
end
CARGO_BUILD --> DEP_RESOLUTION
CARGO_BUILD --> RUSTC
DEP_RESOLUTION --> RUSTC
DEVICE_FRAMEWORK --> DEP_RESOLUTION
METADATA --> TARGET_DIR
RLIB --> HYPERVISOR
RLIB --> TARGET_DIR
RUSTC --> METADATA
RUSTC --> RLIB
SRC_CONSTS --> CARGO_BUILD
SRC_DEVOPS --> CARGO_BUILD
SRC_LIB --> CARGO_BUILD
SRC_VGIC --> CARGO_BUILD
SRC_VGICC --> CARGO_BUILD
Sources: Cargo.toml(L1 - L18)
Dependency Configuration
The crate's dependencies are configured to integrate with the ArceOS ecosystem and provide ARM virtualization capabilities. Dependencies are sourced from both Git repositories and crates.io.
Git Repository Dependencies
| Dependency | Source Repository | Purpose |
|---|---|---|
| axdevice_base | github.com/arceos-hypervisor/axdevice_crates.git | Device framework integration |
| axaddrspace | github.com/arceos-hypervisor/axaddrspace.git | Memory management interface |
| arm_gicv2 | github.com/luodeb/arm_gicv2.git (rev: 2289063) | Physical GIC hardware driver |
Crates.io Dependencies
| Dependency | Version | Purpose |
|---|---|---|
| memory_addr | 0.3 | Memory address abstractions |
| axerrno | 0.1.0 | Error handling types |
| log | 0.4.21 | Logging infrastructure |
| spin | 0.9 | Synchronization primitives |
flowchart TD
subgraph subGraph4["arm_vgic Build Dependencies"]
VGIC["arm_vgic crate"]
subgraph subGraph3["Source Locations"]
GIT_REPOS["Git Repositories"]
CRATES_IO["crates.io"]
end
subgraph subGraph2["System Utilities"]
LOG["log"]
SPIN["spin"]
end
subgraph subGraph1["Hardware Abstraction"]
ARM_GIC["arm_gicv2"]
MEMORY_ADDR["memory_addr"]
end
subgraph subGraph0["ArceOS Framework"]
AXDEVICE["axdevice_base"]
AXADDR["axaddrspace"]
AXERRNO["axerrno"]
end
end
ARM_GIC --> GIT_REPOS
AXADDR --> GIT_REPOS
AXDEVICE --> GIT_REPOS
AXERRNO --> CRATES_IO
LOG --> CRATES_IO
MEMORY_ADDR --> CRATES_IO
SPIN --> CRATES_IO
VGIC --> ARM_GIC
VGIC --> AXADDR
VGIC --> AXDEVICE
VGIC --> AXERRNO
VGIC --> LOG
VGIC --> MEMORY_ADDR
VGIC --> SPIN
Sources: Cargo.toml(L7 - L17)
Development Dependencies and Optional Features
The configuration includes commented-out dependencies that indicate potential future integrations or alternative development paths.
Commented Dependencies
// vcpu_if = { path = "../vcpu_if" }
// crate_interface = "0.1.3"
These suggest:
- Local development with a
vcpu_ifcrate for CPU virtualization interfaces - Potential use of
crate_interfacefor dynamic plugin systems
The current build configuration prioritizes direct integration with ArceOS framework components rather than abstract interface layers.
Sources: Cargo.toml(L14 - L15)
Build Workflow
The standard development workflow follows Cargo conventions with specific considerations for the hypervisor development environment.
Development Commands
flowchart TD
subgraph subGraph3["Development Workflow"]
DEV_START["Development Start"]
subgraph Artifacts["Artifacts"]
DEBUG_BUILD["Debug Build"]
RELEASE_BUILD["Release Build"]
DOCUMENTATION["Generated Docs"]
end
subgraph subGraph1["Integration Steps"]
CLONE_DEPS["Clone Git Dependencies"]
RESOLVE_VERSIONS["Resolve Version Constraints"]
COMPILE_DEPS["Compile Dependencies"]
LINK_ARTIFACTS["Link Final Artifacts"]
end
subgraph subGraph0["Build Commands"]
CARGO_CHECK["cargo check"]
CARGO_BUILD["cargo build"]
CARGO_TEST["cargo test"]
CARGO_DOC["cargo doc"]
end
end
CARGO_BUILD --> CARGO_TEST
CARGO_BUILD --> CLONE_DEPS
CARGO_CHECK --> CARGO_BUILD
CARGO_DOC --> DOCUMENTATION
CARGO_TEST --> CARGO_DOC
CLONE_DEPS --> RESOLVE_VERSIONS
COMPILE_DEPS --> LINK_ARTIFACTS
DEV_START --> CARGO_CHECK
LINK_ARTIFACTS --> DEBUG_BUILD
LINK_ARTIFACTS --> RELEASE_BUILD
RESOLVE_VERSIONS --> COMPILE_DEPS
Build Environment Requirements
| Requirement | Purpose |
|---|---|
| Rust toolchain | Compilation and dependency management |
| Git access | Fetching Git-based dependencies |
| Network connectivity | Downloading crates.io dependencies |
| ARM target support | Cross-compilation for ARM platforms |
Sources: Cargo.toml(L1 - L18) .gitignore(L1)
Git Configuration
The project includes basic Git configuration to exclude build artifacts from version control.
Ignored Paths
/target- Cargo build output directory containing compiled artifacts
This ensures that only source code and configuration files are tracked, while build outputs are regenerated locally.
Sources: .gitignore(L1)
Overview
Relevant source files
Purpose and Scope
The x86_vlapic crate provides a software implementation of a virtual Local Advanced Programmable Interrupt Controller (LAPIC) for x86 hypervisor systems. This crate enables hypervisors to emulate LAPIC functionality for guest virtual machines without requiring direct hardware access to the physical LAPIC.
The crate implements both legacy xAPIC (MMIO-based) and modern x2APIC (MSR-based) access methods, allowing guest VMs to interact with a fully functional virtual interrupt controller. For detailed information about the architectural design and component interactions, see Core Architecture. For comprehensive register documentation and functionality, see Register System.
Virtual LAPIC Fundamentals
A Local APIC is a critical component in x86 multiprocessor systems that handles interrupt delivery, inter-processor communication, and timer functionality for each CPU core. In virtualized environments, guest operating systems expect to interact with LAPIC hardware, but direct hardware access would compromise isolation between virtual machines.
The x86_vlapic crate solves this by providing a software-based LAPIC implementation that:
- Virtualizes all standard LAPIC registers and functionality
- Supports both xAPIC MMIO accesses (address range
0xFEE00000-0xFEE00FFF) and x2APIC MSR accesses (MSR range0x800-0x8FF) - Manages interrupt state through virtualized register arrays (ISR, TMR, IRR)
- Implements Local Vector Table (LVT) registers for various interrupt sources
- Provides timer functionality with support for one-shot, periodic, and TSC-deadline modes
High-Level System Architecture
flowchart TD
subgraph subGraph4["ArceOS Infrastructure"]
AddrSpace["axaddrspaceMemory Management"]
DeviceBase["axdevice_baseDevice Framework"]
ErrNo["axerrnoError Handling"]
end
subgraph subGraph3["Physical Memory"]
PhysPage["4KB PhysFrameAllocated via axaddrspace"]
end
subgraph subGraph2["Virtual Register Layer"]
VirtRegs["VirtualApicRegs<H: AxMmHal>4KB Memory Page Management"]
RegStructure["LocalAPICRegstock-registers Structure"]
end
subgraph subGraph1["EmulatedLocalApic Device"]
EmuDev["EmulatedLocalApic<H: AxMmHal>BaseDeviceOps Implementation"]
AddrXlate["Address Translation Layerxapic_mmio_access_reg_offset()x2apic_msr_access_reg()"]
end
subgraph subGraph0["Guest VM Access Layer"]
GuestMMIO["Guest xAPIC MMIO Access0xFEE00000-0xFEE00FFF"]
GuestMSR["Guest x2APIC MSR Access0x800-0x8FF"]
end
AddrXlate --> VirtRegs
EmuDev --> AddrXlate
EmuDev --> DeviceBase
EmuDev --> ErrNo
GuestMMIO --> EmuDev
GuestMSR --> EmuDev
RegStructure --> PhysPage
VirtRegs --> AddrSpace
VirtRegs --> RegStructure
Sources: src/lib.rs(L32 - L44) Cargo.toml(L14 - L16)
Dual Access Method Support
The virtual LAPIC supports both traditional and modern CPU access methods through a unified interface:
xAPIC Mode (MMIO-Based)
- Address Range:
0xFEE00000to0xFEE00FFF(4KB) - Access Method: Memory-mapped I/O through guest physical addresses
- Implementation:
BaseDeviceOps<AddrRange<GuestPhysAddr>>trait - Translation Function:
xapic_mmio_access_reg_offset()
x2APIC Mode (MSR-Based)
- Address Range: MSR
0x800to0x8FF - Access Method: Model-Specific Register instructions (RDMSR/WRMSR)
- Implementation:
BaseDeviceOps<SysRegAddrRange>trait - Translation Function:
x2apic_msr_access_reg()
flowchart TD
subgraph subGraph2["Unified Register Interface"]
RegOffset["ApicRegOffset enumCommon register addressing"]
VirtRegsRead["VirtualApicRegs.handle_read()"]
VirtRegsWrite["VirtualApicRegs.handle_write()"]
end
subgraph subGraph1["Address Translation"]
XAPICXlate["xapic_mmio_access_reg_offsetGuestPhysAddr → ApicRegOffset"]
X2APICXlate["x2apic_msr_access_regSysRegAddr → ApicRegOffset"]
end
subgraph subGraph0["Access Method Implementations"]
XAPICImpl["BaseDeviceOps<AddrRange<GuestPhysAddr>>handle_read/handle_write"]
X2APICImpl["BaseDeviceOps<SysRegAddrRange>handle_read/handle_write"]
end
RegOffset --> VirtRegsRead
RegOffset --> VirtRegsWrite
X2APICImpl --> X2APICXlate
X2APICXlate --> RegOffset
XAPICImpl --> XAPICXlate
XAPICXlate --> RegOffset
Sources: src/lib.rs(L67 - L112) src/lib.rs(L114 - L159) src/lib.rs(L23 - L24)
Core Components
EmulatedLocalApic Device
The EmulatedLocalApic<H: AxMmHal> struct serves as the primary device interface, implementing the ArceOS device framework through BaseDeviceOps. It coordinates between guest access requests and the underlying virtual register management system.
VirtualApicRegs Management
The VirtualApicRegs<H: AxMmHal> component manages a 4KB memory page that contains all virtualized LAPIC registers. This page is structured according to the standard LAPIC register layout and uses the tock-registers crate for type-safe register access.
Static APIC Access Page
A statically allocated 4KB page (VIRTUAL_APIC_ACCESS_PAGE) provides the foundation for hardware virtualization features. The address of this page can be configured in hardware virtualization controls to enable optimized guest access handling.
Register System
The crate implements a comprehensive register system including:
- Control Registers: ID, Version, Task Priority Register (TPR), Spurious Interrupt Vector Register (SVR)
- Interrupt State Arrays: In-Service Register (ISR), Trigger Mode Register (TMR), Interrupt Request Register (IRR)
- Local Vector Table: Timer, Thermal, Performance Counter, LINT0/1, Error, CMCI registers
- Timer Subsystem: Initial Count, Current Count, Divide Configuration registers
Sources: src/lib.rs(L27 - L30) src/lib.rs(L33 - L43) src/lib.rs(L47 - L64) Cargo.toml(L7 - L16)
Dependencies and Integration
The crate integrates with the ArceOS hypervisor ecosystem through several key dependencies:
| Component | Purpose | Usage |
|---|---|---|
| axaddrspace | Memory management and address space operations | Physical frame allocation, address translation |
| axdevice_base | Device emulation framework | BaseDeviceOpstrait implementation, interrupt injection |
| axerrno | Error handling | Standardized error types (AxResult) |
| tock-registers | Type-safe register access | Register field definitions and access patterns |
| memory_addr | Address abstractions | GuestPhysAddr,HostPhysAddr,HostVirtAddrtypes |
The crate is designed to work within hypervisor systems that provide hardware-assisted virtualization features, supporting both Intel VT-x and AMD-V virtualization extensions through the abstracted AxMmHal trait.
Sources: Cargo.toml(L7 - L16) src/lib.rs(L16 - L21)
Core Architecture
Relevant source files
This document explains the fundamental architectural design of the x86_vlapic virtual Local APIC implementation, covering the main components and their interactions. The architecture provides virtualization of x86 Local APIC functionality for hypervisor environments, supporting both legacy xAPIC (MMIO-based) and modern x2APIC (MSR-based) access patterns.
For detailed information about individual register implementations, see Register System. For specific LVT register functionality, see Local Vector Table (LVT).
Main Architectural Components
The x86_vlapic crate implements a three-layer architecture that provides complete virtualization of Local APIC functionality:
flowchart TD
subgraph subGraph3["Physical Memory"]
PHYS_PAGE["PhysFrame<H>4KB Allocated Page"]
end
subgraph subGraph2["Register Virtualization Layer"]
VAR["VirtualApicRegs<H>4KB Page Management"]
LAPIC_REGS["LocalAPICRegsMemory-Mapped Structure"]
LVT_CACHE["LocalVectorTableCached LVT State"]
SVR_CACHE["SpuriousInterruptVectorRegisterLocalCached SVR State"]
end
subgraph subGraph1["Device Interface Layer"]
ELA["EmulatedLocalApic<H>BaseDeviceOps Implementation"]
XAPIC_TRANS["xapic_mmio_access_reg_offset()GuestPhysAddr → ApicRegOffset"]
X2APIC_TRANS["x2apic_msr_access_reg()SysRegAddr → ApicRegOffset"]
end
subgraph subGraph0["Guest VM Access Layer"]
GUEST_MMIO["Guest MMIO Access0xFEE0_0000-0xFEE0_0FFF"]
GUEST_MSR["Guest MSR Access0x800-0x8FF"]
end
ELA --> X2APIC_TRANS
ELA --> XAPIC_TRANS
GUEST_MMIO --> ELA
GUEST_MSR --> ELA
LAPIC_REGS --> PHYS_PAGE
VAR --> LAPIC_REGS
VAR --> LVT_CACHE
VAR --> SVR_CACHE
X2APIC_TRANS --> VAR
XAPIC_TRANS --> VAR
Sources: src/lib.rs(L33 - L44) src/vlapic.rs(L15 - L28) src/consts.rs(L200 - L202) src/consts.rs(L213 - L215)
The EmulatedLocalApic<H> struct serves as the primary device interface, implementing BaseDeviceOps for both AddrRange<GuestPhysAddr> (xAPIC) and SysRegAddrRange (x2APIC) access patterns. It contains a single VirtualApicRegs<H> instance that manages the actual register virtualization.
Address Translation Architecture
The system implements two distinct address translation paths to convert guest accesses into internal register offsets:
flowchart TD
subgraph subGraph2["Common Register Space"]
REG_ENUM["ApicRegOffset enumID, Version, TPR, etc."]
end
subgraph subGraph1["x2APIC Translation Path"]
MSR_ADDR["SysRegAddr0x800 + offset"]
MSR_SUB["addr - 0x800"]
X2APIC_OFFSET["ApicRegOffset"]
end
subgraph subGraph0["xAPIC Translation Path"]
MMIO_ADDR["GuestPhysAddr0xFEE0_0000 + offset"]
MMIO_MASK["addr & 0xFFF"]
MMIO_SHIFT["result >> 4"]
MMIO_OFFSET["ApicRegOffset"]
end
MMIO_ADDR --> MMIO_MASK
MMIO_MASK --> MMIO_SHIFT
MMIO_OFFSET --> REG_ENUM
MMIO_SHIFT --> MMIO_OFFSET
MSR_ADDR --> MSR_SUB
MSR_SUB --> X2APIC_OFFSET
X2APIC_OFFSET --> REG_ENUM
Sources: src/consts.rs(L200 - L202) src/consts.rs(L213 - L215) src/consts.rs(L117 - L148)
The xapic_mmio_access_reg_offset() function extracts the register offset from xAPIC MMIO addresses by masking the lower 12 bits and shifting right by 4, effectively converting byte offsets to 16-byte aligned register indices. The x2apic_msr_access_reg() function performs simple arithmetic subtraction from the MSR base address. Both paths converge on the same ApicRegOffset enum, enabling unified register handling.
Virtual Register Memory Management
The VirtualApicRegs<H> struct implements a sophisticated memory management system that provides both hardware-compatible register layout and software-accessible caching:
flowchart TD
subgraph subGraph2["Access Patterns"]
DIRECT_ACCESS["Direct Hardware Accessvia virtual_lapic pointer"]
CACHED_ACCESS["Cached Accessvia _last structures"]
end
subgraph subGraph1["4KB Physical Page"]
LAPIC_LAYOUT["LocalAPICRegs Memory LayoutHardware-compatible structure"]
REG_ARRAYS["Register ArraysISR[8], TMR[8], IRR[8]"]
LVT_REGS["LVT RegistersTimer, Thermal, PMC, etc."]
CTRL_REGS["Control RegistersID, Version, TPR, SVR, etc."]
end
subgraph subGraph0["VirtualApicRegs Structure"]
VLAPIC_PTR["virtual_lapic: NonNull<LocalAPICRegs>Direct memory access"]
APIC_PAGE["apic_page: PhysFrame<H>4KB page ownership"]
SVR_LAST["svr_last: SpuriousInterruptVectorRegisterLocalChange detection cache"]
LVT_LAST["lvt_last: LocalVectorTableCoherent snapshot cache"]
end
APIC_PAGE --> LAPIC_LAYOUT
LAPIC_LAYOUT --> CTRL_REGS
LAPIC_LAYOUT --> LVT_REGS
LAPIC_LAYOUT --> REG_ARRAYS
LVT_LAST --> CACHED_ACCESS
SVR_LAST --> CACHED_ACCESS
VLAPIC_PTR --> DIRECT_ACCESS
VLAPIC_PTR --> LAPIC_LAYOUT
Sources: src/vlapic.rs(L15 - L28) src/vlapic.rs(L32 - L40) src/vlapic.rs(L55 - L59)
The system allocates a zero-initialized 4KB page using PhysFrame::alloc_zero() and maps the LocalAPICRegs structure directly onto this memory. The NonNull<LocalAPICRegs> pointer provides direct access to hardware-compatible register layout, while cached copies in svr_last and lvt_last enable change detection and maintain coherent snapshots of critical registers.
Device Interface Implementation
The EmulatedLocalApic<H> implements dual BaseDeviceOps traits to handle both xAPIC and x2APIC access patterns:
Sources: src/lib.rs(L67 - L112) src/lib.rs(L114 - L159) src/vlapic.rs(L62 - L176)
Both access patterns converge on the same VirtualApicRegs::handle_read() and VirtualApicRegs::handle_write() methods, ensuring consistent behavior regardless of the guest's chosen access mode. The EmulatedLocalApic provides address range information through address_range() methods and implements device type identification via emu_type() returning EmuDeviceTInterruptController.
Static Memory Layout
The architecture includes a static 4KB page for APIC access virtualization:
| Component | Purpose | Memory Management |
|---|---|---|
| VIRTUAL_APIC_ACCESS_PAGE | Static 4KB page for VMX APIC-access page | Statically allocated, aligned to 4KB |
| VirtualApicRegs.apic_page | Dynamic 4KB page for register storage | Dynamically allocated viaPhysFrame::alloc_zero() |
| virtual_lapicpointer | Direct access to register structure | Points to dynamically allocated page |
Sources: src/lib.rs(L27 - L30) src/lib.rs(L52 - L56) src/vlapic.rs(L32 - L40)
The static VIRTUAL_APIC_ACCESS_PAGE supports VMX virtualization features, while the dynamic page in VirtualApicRegs provides the actual register storage and implements proper memory lifecycle management through the Drop trait.
EmulatedLocalApic Device Interface
Relevant source files
Purpose and Scope
This document covers the EmulatedLocalApic struct, which serves as the primary device interface for virtual Local APIC emulation in the x86_vlapic crate. The EmulatedLocalApic implements the BaseDeviceOps trait twice to provide unified handling for both xAPIC MMIO accesses and x2APIC MSR accesses, while delegating actual register operations to the underlying virtual register management system.
For details on the virtual register management system that EmulatedLocalApic delegates to, see Virtual Register Management. For information about address translation mechanisms, see Register Address Translation.
Core EmulatedLocalApic Structure
The EmulatedLocalApic struct is a thin wrapper around the virtual register management system that provides the device interface layer required by the ArceOS hypervisor framework.
flowchart TD
subgraph subGraph3["Address Translation"]
XAPIC_XLATE["xapic_mmio_access_reg_offset()"]
X2APIC_XLATE["x2apic_msr_access_reg()"]
end
subgraph subGraph2["Device Framework Integration"]
BDO_MMIO["BaseDeviceOps<AddrRange<GuestPhysAddr>>"]
BDO_MSR["BaseDeviceOps<SysRegAddrRange>"]
end
subgraph subGraph0["EmulatedLocalApic Device Interface"]
ELA["EmulatedLocalApic<H: AxMmHal>"]
VAR["vlapic_regs: VirtualApicRegs<H>"]
end
subgraph subGraph1["Memory Management"]
AAP["VIRTUAL_APIC_ACCESS_PAGE: APICAccessPage"]
STATIC["Static 4KB aligned page"]
end
AAP --> STATIC
BDO_MMIO --> XAPIC_XLATE
BDO_MSR --> X2APIC_XLATE
ELA --> BDO_MMIO
ELA --> BDO_MSR
ELA --> VAR
EmulatedLocalApic Structure Components
| Component | Type | Purpose |
|---|---|---|
| vlapic_regs | VirtualApicRegs | Manages actual APIC register virtualization and storage |
The struct uses a generic type parameter H: AxMmHal to integrate with the ArceOS memory management abstraction layer.
Sources: src/lib.rs(L33 - L35) src/lib.rs(L39 - L43)
Device Interface Implementation
The EmulatedLocalApic implements BaseDeviceOps twice to handle the two distinct access methods for Local APIC registers: xAPIC memory-mapped I/O and x2APIC model-specific registers.
Dual Interface Architecture
flowchart TD
subgraph subGraph4["Register Operations"]
VLAPIC_REGS["VirtualApicRegs<H>"]
READ_OP["handle_read()"]
WRITE_OP["handle_write()"]
end
subgraph subGraph3["Address Translation Layer"]
XAPIC_FUNC["xapic_mmio_access_reg_offset()"]
X2APIC_FUNC["x2apic_msr_access_reg()"]
end
subgraph subGraph2["EmulatedLocalApic Interface Layer"]
ELA["EmulatedLocalApic<H>"]
subgraph subGraph1["BaseDeviceOps Implementations"]
MMIO_IMPL["BaseDeviceOps<AddrRange<GuestPhysAddr>>"]
MSR_IMPL["BaseDeviceOps<SysRegAddrRange>"]
end
end
subgraph subGraph0["Guest Access Methods"]
GUEST_MMIO["Guest MMIO Access0xFEE00000-0xFEE00FFF"]
GUEST_MSR["Guest MSR Access0x800-0x8FF"]
end
ELA --> MMIO_IMPL
ELA --> MSR_IMPL
GUEST_MMIO --> MMIO_IMPL
GUEST_MSR --> MSR_IMPL
MMIO_IMPL --> XAPIC_FUNC
MSR_IMPL --> X2APIC_FUNC
READ_OP --> VLAPIC_REGS
WRITE_OP --> VLAPIC_REGS
X2APIC_FUNC --> READ_OP
X2APIC_FUNC --> WRITE_OP
XAPIC_FUNC --> READ_OP
XAPIC_FUNC --> WRITE_OP
Sources: src/lib.rs(L67 - L112) src/lib.rs(L114 - L159)
xAPIC MMIO Interface
The xAPIC interface handles memory-mapped I/O accesses in the traditional Local APIC address range.
Interface Configuration
| Property | Value | Description |
|---|---|---|
| Address Range | 0xFEE00000 - 0xFEE00FFF | 4KB MMIO region for xAPIC registers |
| Device Type | EmuDeviceTInterruptController | Identifies as interrupt controller device |
| Access Translation | xapic_mmio_access_reg_offset() | ConvertsGuestPhysAddrtoApicRegOffset |
Access Flow
- Guest performs MMIO read/write to xAPIC address range
handle_read()orhandle_write()called withGuestPhysAddr- Address translated via
xapic_mmio_access_reg_offset() - Operation delegated to
VirtualApicRegs::handle_read()/handle_write()
Sources: src/lib.rs(L67 - L112) src/lib.rs(L90 - L91) src/lib.rs(L105 - L106)
x2APIC MSR Interface
The x2APIC interface handles model-specific register accesses for the extended Local APIC mode.
Interface Configuration
| Property | Value | Description |
|---|---|---|
| Address Range | 0x800 - 0x8FF | MSR range for x2APIC registers |
| Device Type | EmuDeviceTInterruptController | Identifies as interrupt controller device |
| Access Translation | x2apic_msr_access_reg() | ConvertsSysRegAddrtoApicRegOffset |
Access Flow
- Guest performs MSR read/write to x2APIC address range
handle_read()orhandle_write()called withSysRegAddr- Address translated via
x2apic_msr_access_reg() - Operation delegated to
VirtualApicRegs::handle_read()/handle_write()
Sources: src/lib.rs(L114 - L159) src/lib.rs(L137 - L138) src/lib.rs(L152 - L153)
Memory Management
The EmulatedLocalApic manages two distinct 4KB memory pages for virtual Local APIC functionality, each serving different purposes in the virtualization architecture.
APIC Access Page
flowchart TD
subgraph subGraph2["Access Methods"]
ACCESSOR["virtual_apic_access_addr()"]
HAL_CONVERT["H::virt_to_phys()"]
end
subgraph subGraph1["Hypervisor Integration"]
VMCS["VM-execution control'virtualize APIC accesses'"]
VM_EXIT["VM exits or processor virtualization"]
end
subgraph subGraph0["APIC Access Page Management"]
STATIC_PAGE["VIRTUAL_APIC_ACCESS_PAGEAPICAccessPage"]
ALIGNED_ARRAY["[u8; PAGE_SIZE_4K]4KB aligned static array"]
VIRT_ADDR["HostVirtAddr"]
PHYS_ADDR["HostPhysAddr"]
end
ACCESSOR --> HAL_CONVERT
ALIGNED_ARRAY --> VIRT_ADDR
HAL_CONVERT --> PHYS_ADDR
PHYS_ADDR --> VMCS
STATIC_PAGE --> ALIGNED_ARRAY
VIRT_ADDR --> PHYS_ADDR
VMCS --> VM_EXIT
The APIC access page is a static 4KB page used by hardware virtualization features to control guest access to the APIC MMIO region.
APIC Access Page Characteristics
| Property | Value | Purpose |
|---|---|---|
| Type | APICAccessPage | 4KB aligned wrapper around byte array |
| Alignment | 4096 bytes | Required by hardware virtualization |
| Initialization | Zero-filled | Safe default state |
| Scope | Static global | Shared across all EmulatedLocalApic instances |
Sources: src/lib.rs(L27 - L30) src/lib.rs(L47 - L56)
Virtual APIC Page
The virtual APIC page contains the actual register storage and is managed by the VirtualApicRegs component.
flowchart TD
subgraph subGraph0["Virtual APIC Page Access"]
REG_STORAGE["VirtualApicRegs register storage"]
subgraph subGraph1["Register Layout"]
PHYS_PAGE["4KB Physical Page"]
LAPIC_REGS["LocalAPICRegs structure"]
REG_FIELDS["Individual APIC registers"]
ELA_METHOD["virtual_apic_page_addr()"]
DELEGATE["self.vlapic_regs.virtual_apic_page_addr()"]
end
end
DELEGATE --> REG_STORAGE
ELA_METHOD --> DELEGATE
LAPIC_REGS --> REG_FIELDS
PHYS_PAGE --> LAPIC_REGS
The virtual APIC page provides the actual register storage that guests read from and write to during APIC operations.
Sources: src/lib.rs(L58 - L64)
Address Translation and Delegation
The EmulatedLocalApic acts as an address translation layer, converting guest addresses to register offsets before delegating operations to the virtual register system.
Translation Architecture
sequenceDiagram
participant GuestVM as "Guest VM"
participant EmulatedLocalApic as "EmulatedLocalApic"
participant AddressTranslator as "Address Translator"
participant VirtualApicRegs as "VirtualApicRegs"
Note over GuestVM,VirtualApicRegs: xAPIC MMIO Access Example
GuestVM ->> EmulatedLocalApic: "handle_read(GuestPhysAddr, width, context)"
EmulatedLocalApic ->> AddressTranslator: "xapic_mmio_access_reg_offset(addr)"
AddressTranslator -->> EmulatedLocalApic: "ApicRegOffset"
EmulatedLocalApic ->> VirtualApicRegs: "handle_read(reg_off, width, context)"
VirtualApicRegs -->> EmulatedLocalApic: "AxResult<usize>"
EmulatedLocalApic -->> GuestVM: "Register value"
Note over GuestVM,VirtualApicRegs: x2APIC MSR Access Example
GuestVM ->> EmulatedLocalApic: "handle_write(SysRegAddr, width, val, context)"
EmulatedLocalApic ->> AddressTranslator: "x2apic_msr_access_reg(addr)"
AddressTranslator -->> EmulatedLocalApic: "ApicRegOffset"
EmulatedLocalApic ->> VirtualApicRegs: "handle_write(reg_off, width, context)"
VirtualApicRegs -->> EmulatedLocalApic: "AxResult"
EmulatedLocalApic -->> GuestVM: "Write complete"
Translation Functions
| Function | Input Type | Output Type | Purpose |
|---|---|---|---|
| xapic_mmio_access_reg_offset() | GuestPhysAddr | ApicRegOffset | Convert xAPIC MMIO address to register offset |
| x2apic_msr_access_reg() | SysRegAddr | ApicRegOffset | Convert x2APIC MSR address to register offset |
Both translation functions convert different address formats to the unified ApicRegOffset enum used by the virtual register system.
Sources: src/lib.rs(L23 - L24) src/lib.rs(L90 - L91) src/lib.rs(L105 - L106) src/lib.rs(L137 - L138) src/lib.rs(L152 - L153)
Virtual Register Management
Relevant source files
The Virtual Register Management system implements the core virtualization layer for APIC registers through the VirtualApicRegs struct. This system allocates and manages a 4KB memory page that contains a complete set of virtualized Local APIC registers, providing the foundation for both xAPIC MMIO and x2APIC MSR access patterns. For information about the device interface that uses this register management, see EmulatedLocalApic Device Interface. For details about address translation that routes to these registers, see Register Address Translation.
Memory Management Architecture
The VirtualApicRegs<H: AxMmHal> struct serves as the primary memory manager for virtualized APIC registers. It allocates a single 4KB physical memory page and maps the LocalAPICRegs structure directly onto this page, providing a complete virtualized APIC register set.
flowchart TD
subgraph subGraph2["Register Layout in Memory"]
ID["ID (0x20)"]
VER["VERSION (0x30)"]
TPR["TPR (0x80)"]
SVR["SVR (0xF0)"]
ISR["ISR Arrays (0x100-0x170)"]
TMR["TMR Arrays (0x180-0x1F0)"]
IRR["IRR Arrays (0x200-0x270)"]
LVT["LVT Registers (0x2F0-0x370)"]
TIMER["Timer Registers (0x380-0x3E0)"]
end
subgraph subGraph1["Physical Memory"]
PM["4KB PhysFrame"]
LAR["LocalAPICRegs Structure"]
end
subgraph subGraph0["VirtualApicRegs Structure"]
VR["VirtualApicRegs<H>"]
VLP["virtual_lapic: NonNull<LocalAPICRegs>"]
AP["apic_page: PhysFrame<H>"]
SL["svr_last: SpuriousInterruptVectorRegisterLocal"]
LL["lvt_last: LocalVectorTable"]
end
AP --> PM
LAR --> ID
LAR --> IRR
LAR --> ISR
LAR --> LVT
LAR --> SVR
LAR --> TIMER
LAR --> TMR
LAR --> TPR
LAR --> VER
PM --> LAR
VLP --> LAR
VR --> AP
VR --> LL
VR --> SL
VR --> VLP
The allocation process uses PhysFrame::alloc_zero() to obtain a zeroed 4KB page, ensuring clean initial register state. The NonNull<LocalAPICRegs> pointer provides direct access to the register structure while maintaining memory safety.
Sources: src/vlapic.rs(L15 - L40)
Register Structure and Layout
The LocalAPICRegs structure defines the exact memory layout of the 4KB virtual APIC page, mapping all register offsets according to the Intel APIC specification. This structure uses the tock_registers framework to provide type-safe register access with proper read/write permissions.
flowchart TD
subgraph subGraph4["LocalAPICRegs Memory Map (4KB Page)"]
subgraph subGraph2["Local Vector Table"]
ICR_TIMER["ICR_TIMER: ReadWrite<u32> (0x380)"]
CCR_TIMER["CCR_TIMER: ReadOnly<u32> (0x390)"]
DCR_TIMER["DCR_TIMER: ReadWrite<u32> (0x3E0)"]
LVT_CMCI["LVT_CMCI: LvtCmciRegisterMmio (0x2F0)"]
LVT_TIMER["LVT_TIMER: LvtTimerRegisterMmio (0x320)"]
LVT_THERMAL["LVT_THERMAL: LvtThermalMonitorRegisterMmio (0x330)"]
LVT_PMI["LVT_PMI: LvtPerformanceCounterRegisterMmio (0x340)"]
LVT_LINT0["LVT_LINT0: LvtLint0RegisterMmio (0x350)"]
LVT_LINT1["LVT_LINT1: LvtLint1RegisterMmio (0x360)"]
LVT_ERROR["LVT_ERROR: LvtErrorRegisterMmio (0x370)"]
ISR_REG["ISR: [ReadOnly<u128>; 8] (0x100-0x170)"]
TMR_REG["TMR: [ReadOnly<u128>; 8] (0x180-0x1F0)"]
IRR_REG["IRR: [ReadOnly<u128>; 8] (0x200-0x270)"]
ID_REG["ID: ReadWrite<u32> (0x20)"]
VERSION_REG["VERSION: ReadOnly<u32> (0x30)"]
TPR_REG["TPR: ReadWrite<u32> (0x80)"]
SVR_REG["SVR: SpuriousInterruptVectorRegisterMmio (0xF0)"]
end
subgraph subGraph0["Control Registers"]
ICR_TIMER["ICR_TIMER: ReadWrite<u32> (0x380)"]
CCR_TIMER["CCR_TIMER: ReadOnly<u32> (0x390)"]
DCR_TIMER["DCR_TIMER: ReadWrite<u32> (0x3E0)"]
LVT_CMCI["LVT_CMCI: LvtCmciRegisterMmio (0x2F0)"]
LVT_TIMER["LVT_TIMER: LvtTimerRegisterMmio (0x320)"]
LVT_THERMAL["LVT_THERMAL: LvtThermalMonitorRegisterMmio (0x330)"]
LVT_PMI["LVT_PMI: LvtPerformanceCounterRegisterMmio (0x340)"]
ISR_REG["ISR: [ReadOnly<u128>; 8] (0x100-0x170)"]
TMR_REG["TMR: [ReadOnly<u128>; 8] (0x180-0x1F0)"]
IRR_REG["IRR: [ReadOnly<u128>; 8] (0x200-0x270)"]
ID_REG["ID: ReadWrite<u32> (0x20)"]
VERSION_REG["VERSION: ReadOnly<u32> (0x30)"]
TPR_REG["TPR: ReadWrite<u32> (0x80)"]
SVR_REG["SVR: SpuriousInterruptVectorRegisterMmio (0xF0)"]
end
end
The register arrays (ISR, TMR, IRR) each consist of 8 x 128-bit fields that collectively represent 256-bit interrupt state vectors, with each bit corresponding to a specific interrupt vector number.
Sources: src/regs/mod.rs(L13 - L104)
Register Access Patterns
The VirtualApicRegs implements register access through handle_read() and handle_write() methods that translate ApicRegOffset enum values to direct memory operations on the LocalAPICRegs structure. This provides a unified interface for both xAPIC MMIO and x2APIC MSR access patterns.
sequenceDiagram
participant AccessClient as "Access Client"
participant VirtualApicRegs as "VirtualApicRegs"
participant LocalAPICRegs as "LocalAPICRegs"
participant LocalCopies as "Local Copies"
Note over AccessClient,LocalCopies: Register Read Operation
AccessClient ->> VirtualApicRegs: "handle_read(offset, width, context)"
VirtualApicRegs ->> VirtualApicRegs: "match offset"
alt Standard Register
VirtualApicRegs ->> LocalAPICRegs: "self.regs().REGISTER.get()"
LocalAPICRegs -->> VirtualApicRegs: "register_value"
else LVT Register
VirtualApicRegs ->> LocalCopies: "self.lvt_last.register.get()"
LocalCopies -->> VirtualApicRegs: "cached_value"
else Array Register (ISR/TMR/IRR)
VirtualApicRegs ->> LocalAPICRegs: "self.regs().ARRAY[index].get()"
LocalAPICRegs -->> VirtualApicRegs: "array_element_value"
end
VirtualApicRegs -->> AccessClient: "Ok(value)"
Note over AccessClient,LocalCopies: Register Write Operation
AccessClient ->> VirtualApicRegs: "handle_write(offset, width, context)"
VirtualApicRegs ->> VirtualApicRegs: "match offset and validate"
VirtualApicRegs -->> AccessClient: "Ok(())"
The read operations access different register types through specific patterns:
- Standard registers: Direct access via
self.regs().REGISTER.get() - LVT registers: Cached access via
self.lvt_last.register.get() - Array registers: Indexed access via
self.regs().ARRAY[index].get()
Sources: src/vlapic.rs(L62 - L186)
Caching Strategy
The VirtualApicRegs maintains local copies of certain registers to support change detection and coherent snapshots. This caching strategy ensures consistent behavior during register modifications and enables tracking of register state changes.
| Cached Register Type | Field Name | Purpose |
|---|---|---|
| Spurious Interrupt Vector | svr_last: SpuriousInterruptVectorRegisterLocal | Change detection for SVR modifications |
| Local Vector Table | lvt_last: LocalVectorTable | Coherent snapshot of all LVT registers |
flowchart TD
subgraph subGraph3["VirtualApicRegs Caching Architecture"]
subgraph subGraph2["Access Patterns"]
READ_SVR["SVR Read Operations"]
READ_LVT["LVT Read Operations"]
end
subgraph subGraph1["Local Copies"]
CACHE_SVR["svr_last: SpuriousInterruptVectorRegisterLocal"]
CACHE_LVT["lvt_last: LocalVectorTable"]
end
subgraph subGraph0["Hardware Registers"]
HW_SVR["SVR in LocalAPICRegs"]
HW_LVT["LVT Registers in LocalAPICRegs"]
end
end
HW_LVT --> CACHE_LVT
HW_SVR --> CACHE_SVR
READ_LVT --> CACHE_LVT
READ_SVR --> HW_SVR
The cached LVT registers are accessed during read operations instead of the hardware registers, providing stable values during register modifications. The SVR cache enables detection of configuration changes that affect APIC behavior.
Sources: src/vlapic.rs(L21 - L26) src/vlapic.rs(L37 - L38) src/vlapic.rs(L131 - L157)
Memory Management Lifecycle
The VirtualApicRegs implements proper resource management through RAII patterns, automatically deallocating the 4KB page when the instance is dropped.
flowchart TD
subgraph subGraph0["Lifecycle Management"]
CREATE["VirtualApicRegs::new()"]
ALLOC["PhysFrame::alloc_zero()"]
INIT["Initialize cached registers"]
USE["Runtime register access"]
DROP["Drop trait implementation"]
DEALLOC["H::dealloc_frame()"]
end
ALLOC --> INIT
CREATE --> ALLOC
DROP --> DEALLOC
INIT --> USE
USE --> DROP
The initialization process sets default values for cached registers using RESET_SPURIOUS_INTERRUPT_VECTOR for the SVR and LocalVectorTable::default() for the LVT state, ensuring proper initial configuration.
Sources: src/vlapic.rs(L30 - L40) src/vlapic.rs(L55 - L59)
Register Address Translation
Relevant source files
This document describes the address translation mechanism that converts guest memory accesses into internal register identifiers within the x86_vlapic crate. The translation layer serves as a bridge between two different APIC access methods (xAPIC MMIO and x2APIC MSR) and the unified internal register representation using the ApicRegOffset enum.
For information about the overall virtual register management system, see Virtual Register Management. For details about specific register layouts and functionality, see Register System.
Translation Architecture Overview
The address translation system provides a unified interface for accessing APIC registers regardless of whether the guest uses xAPIC MMIO-based access or x2APIC MSR-based access. Both access methods are translated into the same internal ApicRegOffset enum representation.
flowchart TD
subgraph subGraph4["Register Access Handlers"]
VLAPIC_REGS["VirtualApicRegshandle_read/write methods"]
end
subgraph subGraph3["Unified Register Space"]
APIC_REG_OFFSET["ApicRegOffset enumsrc/consts.rs:61-114"]
subgraph subGraph2["Register Categories"]
CONTROL_REGS["Control RegistersID, Version, TPR, etc."]
ARRAY_REGS["Array RegistersISR, TMR, IRR"]
LVT_REGS["LVT RegistersTimer, Thermal, PMC, etc."]
TIMER_REGS["Timer RegistersInitCount, CurCount, DivConf"]
end
end
subgraph subGraph1["Translation Functions"]
XAPIC_FUNC["xapic_mmio_access_reg_offset()src/consts.rs:200-202"]
X2APIC_FUNC["x2apic_msr_access_reg()src/consts.rs:213-215"]
end
subgraph subGraph0["Guest Access Methods"]
GUEST_MMIO["Guest xAPIC MMIO AccessGuestPhysAddr0xFEE0_0000-0xFEE0_0FFF"]
GUEST_MSR["Guest x2APIC MSR AccessSysRegAddr0x800-0x8FF"]
end
APIC_REG_OFFSET --> ARRAY_REGS
APIC_REG_OFFSET --> CONTROL_REGS
APIC_REG_OFFSET --> LVT_REGS
APIC_REG_OFFSET --> TIMER_REGS
ARRAY_REGS --> VLAPIC_REGS
CONTROL_REGS --> VLAPIC_REGS
GUEST_MMIO --> XAPIC_FUNC
GUEST_MSR --> X2APIC_FUNC
LVT_REGS --> VLAPIC_REGS
TIMER_REGS --> VLAPIC_REGS
X2APIC_FUNC --> APIC_REG_OFFSET
XAPIC_FUNC --> APIC_REG_OFFSET
Sources: src/consts.rs(L200 - L202) src/consts.rs(L213 - L215) src/consts.rs(L61 - L114) src/lib.rs(L90) src/lib.rs(L105) src/lib.rs(L137) src/lib.rs(L152)
xAPIC MMIO Address Translation
The xAPIC mode uses Memory-Mapped I/O (MMIO) for register access. Guest physical addresses in the range 0xFEE0_0000 to 0xFEE0_0FFF are translated to register offsets using the xapic_mmio_access_reg_offset() function.
Translation Process
flowchart TD
subgraph Constants["Constants"]
APIC_BASE["DEFAULT_APIC_BASE0xFEE0_0000"]
APIC_SIZE["APIC_MMIO_SIZE0x1000 (4KB)"]
end
START["Guest Physical AddressGuestPhysAddr"]
MASK["Apply MMIO Size Maskaddr & (APIC_MMIO_SIZE - 1)Extract lower 12 bits"]
SHIFT["Right Shift by 4result >> 4Convert to 16-byte aligned offset"]
CONVERT["ApicRegOffset::from()Map offset to enum variant"]
END["ApicRegOffset enum"]
CONVERT --> END
MASK --> SHIFT
SHIFT --> CONVERT
START --> MASK
The translation algorithm:
- Mask Address:
addr.as_usize() & (APIC_MMIO_SIZE - 1)extracts the offset within the 4KB APIC region - Normalize to 16-byte boundaries:
result >> 4converts byte offset to register offset (APIC registers are 16-byte aligned) - Convert to enum:
ApicRegOffset::from(offset)maps the numeric offset to the appropriate enum variant
Address Mapping Examples
| Guest Physical Address | Masked Offset | Register Offset | ApicRegOffset Enum |
|---|---|---|---|
| 0xFEE0_0020 | 0x020 | 0x2 | ApicRegOffset::ID |
| 0xFEE0_0030 | 0x030 | 0x3 | ApicRegOffset::Version |
| 0xFEE0_0100 | 0x100 | 0x10 | ApicRegOffset::ISR(ISRIndex0) |
| 0xFEE0_0320 | 0x320 | 0x32 | ApicRegOffset::LvtTimer |
Sources: src/consts.rs(L192 - L203) src/consts.rs(L197 - L198) src/lib.rs(L90) src/lib.rs(L105)
x2APIC MSR Address Translation
The x2APIC mode uses Model-Specific Registers (MSRs) for register access. MSR addresses in the range 0x800 to 0x8FF are translated using the x2apic_msr_access_reg() function.
Translation Process
flowchart TD
subgraph Constants["Constants"]
MSR_BASE["X2APIC_MSE_REG_BASE0x800"]
MSR_SIZE["X2APIC_MSE_REG_SIZE0x100 (256 registers)"]
end
START["System Register AddressSysRegAddr"]
SUBTRACT["Subtract Base Addressaddr.addr() - X2APIC_MSE_REG_BASEConvert to relative offset"]
CONVERT["ApicRegOffset::from()Map offset to enum variant"]
END["ApicRegOffset enum"]
CONVERT --> END
START --> SUBTRACT
SUBTRACT --> CONVERT
The translation is simpler than xAPIC because x2APIC MSRs directly correspond to register offsets:
- Calculate relative offset:
addr.addr() - X2APIC_MSE_REG_BASEgives the register offset - Convert to enum:
ApicRegOffset::from(offset)maps the offset to the enum variant
MSR Address Mapping Examples
| MSR Address | Relative Offset | ApicRegOffset Enum |
|---|---|---|
| 0x802 | 0x2 | ApicRegOffset::ID |
| 0x803 | 0x3 | ApicRegOffset::Version |
| 0x810 | 0x10 | ApicRegOffset::ISR(ISRIndex0) |
| 0x832 | 0x32 | ApicRegOffset::LvtTimer |
Sources: src/consts.rs(L205 - L216) src/consts.rs(L210 - L211) src/lib.rs(L137) src/lib.rs(L152)
ApicRegOffset Enum Structure
The ApicRegOffset enum serves as the unified internal representation for all APIC registers. It categorizes registers into distinct types and handles special cases like register arrays.
flowchart TD
subgraph subGraph5["Index Enums"]
ISR_INDEX["ISRIndexISRIndex0-ISRIndex7"]
TMR_INDEX["TMRIndexTMRIndex0-TMRIndex7"]
IRR_INDEX["IRRIndexIRRIndex0-IRRIndex7"]
end
subgraph subGraph4["ApicRegOffset Enum Variants"]
LVT_LINT0["LvtLint0 (0x35)"]
SIVR_REG["SIVR (0xF)"]
subgraph subGraph1["Interrupt State Arrays"]
ISR_ARRAY["ISR(ISRIndex)0x10-0x178 registers"]
TMR_ARRAY["TMR(TMRIndex)0x18-0x1F8 registers"]
IRR_ARRAY["IRR(IRRIndex)0x20-0x278 registers"]
end
subgraph subGraph3["Timer Control"]
TIMER_INIT["TimerInitCount (0x38)"]
TIMER_CUR["TimerCurCount (0x39)"]
TIMER_DIV["TimerDivConf (0x3E)"]
LVT_TIMER["LvtTimer (0x32)"]
LVT_THERMAL["LvtThermal (0x33)"]
LVT_PMC["LvtPmc (0x34)"]
ID_REG["ID (0x2)"]
VER_REG["Version (0x3)"]
TPR_REG["TPR (0x8)"]
end
subgraph subGraph2["Local Vector Table"]
LVT_LINT1["LvtLint1 (0x36)"]
LVT_ERR["LvtErr (0x37)"]
LVT_CMCI["LvtCMCI (0x2F)"]
subgraph subGraph0["Basic Control Registers"]
TIMER_INIT["TimerInitCount (0x38)"]
TIMER_CUR["TimerCurCount (0x39)"]
TIMER_DIV["TimerDivConf (0x3E)"]
LVT_TIMER["LvtTimer (0x32)"]
LVT_THERMAL["LvtThermal (0x33)"]
LVT_PMC["LvtPmc (0x34)"]
LVT_LINT0["LvtLint0 (0x35)"]
ID_REG["ID (0x2)"]
VER_REG["Version (0x3)"]
TPR_REG["TPR (0x8)"]
SIVR_REG["SIVR (0xF)"]
end
end
end
IRR_ARRAY --> IRR_INDEX
ISR_ARRAY --> ISR_INDEX
TMR_ARRAY --> TMR_INDEX
Register Array Handling
The enum includes special handling for register arrays (ISR, TMR, IRR) that span multiple consecutive offsets. Each array uses a dedicated index enum generated by the define_index_enum! macro:
- ISR (In-Service Register): 8 registers at offsets
0x10-0x17 - TMR (Trigger Mode Register): 8 registers at offsets
0x18-0x1F - IRR (Interrupt Request Register): 8 registers at offsets
0x20-0x27
The index calculation uses: IndexType::from(offset - base_offset) where base_offset is the starting offset for each array.
Sources: src/consts.rs(L61 - L114) src/consts.rs(L129 - L131) src/consts.rs(L3 - L54) src/consts.rs(L56 - L58)
Translation Implementation Details
The core translation logic is implemented in the ApicRegOffset::from() method, which uses pattern matching to map numeric offsets to enum variants.
Pattern Matching Strategy
flowchart TD START["Numeric Offset (usize)"] CAST["Cast to u32"] MATCH["Pattern Match"] SINGLE["Single Register0x2, 0x3, 0x8, etc."] RANGE["Range Patterns0x10..=0x170x18..=0x1F0x20..=0x27"] INVALID["Invalid Offsetpanic!"] SINGLE_VARIANT["Direct Enum VariantApicRegOffset::ID"] ARRAY_VARIANT["Array Enum VariantApicRegOffset::ISR(index)"] CAST --> MATCH MATCH --> INVALID MATCH --> RANGE MATCH --> SINGLE RANGE --> ARRAY_VARIANT SINGLE --> SINGLE_VARIANT START --> CAST
The implementation handles three categories of offsets:
- Direct mappings: Single offsets like
0x2 => ApicRegOffset::ID - Range mappings: Consecutive ranges like
0x10..=0x17 => ApicRegOffset::ISR(index) - Invalid offsets: Any unmapped offset triggers a panic
Index Calculation for Arrays
For register arrays, the index is calculated by subtracting the base offset:
// Example for ISR array (offset 0x10-0x17)
0x10..=0x17 => ApicRegOffset::ISR(ISRIndex::from(value - 0x10))
This ensures that offset 0x10 maps to ISRIndex0, offset 0x11 maps to ISRIndex1, and so on.
Sources: src/consts.rs(L116 - L148) src/consts.rs(L129 - L131) src/consts.rs(L19 - L31)
Register System
Relevant source files
This document provides comprehensive documentation of the virtual Local APIC register system implemented in the x86_vlapic crate. It covers all APIC registers, their memory layouts, address translation mechanisms, and access patterns for both xAPIC and x2APIC operating modes. For details about specific Local Vector Table register implementations, see Local Vector Table (LVT). For information about the higher-level device interface and virtualization architecture, see Core Architecture.
Register Organization Overview
The virtual LAPIC register system virtualizes the complete set of x86 Local APIC registers within a 4KB memory page. The system supports dual access modes: legacy xAPIC MMIO access and modern x2APIC MSR access, both mapping to the same underlying register state.
Register Address Translation
The system translates guest access addresses to internal register offsets using the ApicRegOffset enum, which serves as the canonical register identifier throughout the codebase.
flowchart TD
subgraph subGraph3["Physical Memory Layout"]
LocalAPICRegs["LocalAPICRegs struct4KB tock-registers layout"]
end
subgraph subGraph2["Canonical Register Space"]
ApicRegOffsetEnum["ApicRegOffset enumUnified register identifier"]
end
subgraph subGraph1["Address Translation Layer"]
XAPICTranslate["xapic_mmio_access_reg_offset()GuestPhysAddr → ApicRegOffset"]
X2APICTranslate["x2apic_msr_access_reg()SysRegAddr → ApicRegOffset"]
end
subgraph subGraph0["Guest Access Methods"]
GuestMMIO["Guest xAPIC MMIO0xFEE00000-0xFEE00FFF"]
GuestMSR["Guest x2APIC MSR0x800-0x8FF"]
end
ApicRegOffsetEnum --> LocalAPICRegs
GuestMMIO --> XAPICTranslate
GuestMSR --> X2APICTranslate
X2APICTranslate --> ApicRegOffsetEnum
XAPICTranslate --> ApicRegOffsetEnum
Sources: src/consts.rs(L200 - L202) src/consts.rs(L213 - L215) src/consts.rs(L61 - L114)
Register Categories
The APIC register space is organized into several functional categories, each serving distinct purposes in interrupt handling and APIC management.
flowchart TD
subgraph subGraph5["APIC Register Categories"]
subgraph subGraph2["Local Vector Table"]
ICR_LOW["ICR_LO (0x300)Command Register Low"]
ICR_HIGH["ICR_HI (0x310)Command Register High"]
TIMER_INIT["ICR_TIMER (0x380)Initial Count"]
TIMER_CUR["CCR_TIMER (0x390)Current Count"]
TIMER_DIV["DCR_TIMER (0x3E0)Divide Configuration"]
LVT_TIMER["LVT_TIMER (0x320)Timer Interrupt"]
LVT_THERMAL["LVT_THERMAL (0x330)Thermal Monitor"]
LVT_PMC["LVT_PMC (0x340)Performance Counter"]
LVT_LINT1["LVT_LINT1 (0x360)External Pin 1"]
LVT_ERROR["LVT_ERROR (0x370)Error Interrupt"]
LVT_CMCI["LVT_CMCI (0x2F0)Corrected Machine Check"]
ISR["ISR Array (0x100-0x170)In-Service Registers8 x 128-bit"]
TMR["TMR Array (0x180-0x1F0)Trigger Mode Registers8 x 128-bit"]
IRR["IRR Array (0x200-0x270)Interrupt Request Registers8 x 128-bit"]
ID["ID (0x20)Local APIC Identifier"]
VERSION["VERSION (0x30)APIC Version Info"]
TPR["TPR (0x80)Task Priority"]
subgraph subGraph0["Control Registers"]
LVT_LINT0["LVT_LINT0 (0x350)External Pin 0"]
SVR["SVR (0xF0)Spurious Interrupt Vector"]
subgraph subGraph4["Interrupt Command"]
ICR_LOW["ICR_LO (0x300)Command Register Low"]
ICR_HIGH["ICR_HI (0x310)Command Register High"]
TIMER_INIT["ICR_TIMER (0x380)Initial Count"]
TIMER_CUR["CCR_TIMER (0x390)Current Count"]
LVT_TIMER["LVT_TIMER (0x320)Timer Interrupt"]
LVT_THERMAL["LVT_THERMAL (0x330)Thermal Monitor"]
ISR["ISR Array (0x100-0x170)In-Service Registers8 x 128-bit"]
TMR["TMR Array (0x180-0x1F0)Trigger Mode Registers8 x 128-bit"]
ID["ID (0x20)Local APIC Identifier"]
VERSION["VERSION (0x30)APIC Version Info"]
end
subgraph subGraph3["Timer Subsystem"]
ICR_LOW["ICR_LO (0x300)Command Register Low"]
ICR_HIGH["ICR_HI (0x310)Command Register High"]
TIMER_INIT["ICR_TIMER (0x380)Initial Count"]
TIMER_CUR["CCR_TIMER (0x390)Current Count"]
TIMER_DIV["DCR_TIMER (0x3E0)Divide Configuration"]
LVT_TIMER["LVT_TIMER (0x320)Timer Interrupt"]
LVT_THERMAL["LVT_THERMAL (0x330)Thermal Monitor"]
LVT_PMC["LVT_PMC (0x340)Performance Counter"]
ISR["ISR Array (0x100-0x170)In-Service Registers8 x 128-bit"]
TMR["TMR Array (0x180-0x1F0)Trigger Mode Registers8 x 128-bit"]
IRR["IRR Array (0x200-0x270)Interrupt Request Registers8 x 128-bit"]
ID["ID (0x20)Local APIC Identifier"]
VERSION["VERSION (0x30)APIC Version Info"]
TPR["TPR (0x80)Task Priority"]
end
end
end
subgraph subGraph1["Interrupt State Arrays"]
LVT_LINT0["LVT_LINT0 (0x350)External Pin 0"]
SVR["SVR (0xF0)Spurious Interrupt Vector"]
subgraph subGraph4["Interrupt Command"]
ICR_LOW["ICR_LO (0x300)Command Register Low"]
ICR_HIGH["ICR_HI (0x310)Command Register High"]
TIMER_INIT["ICR_TIMER (0x380)Initial Count"]
TIMER_CUR["CCR_TIMER (0x390)Current Count"]
LVT_TIMER["LVT_TIMER (0x320)Timer Interrupt"]
LVT_THERMAL["LVT_THERMAL (0x330)Thermal Monitor"]
ISR["ISR Array (0x100-0x170)In-Service Registers8 x 128-bit"]
TMR["TMR Array (0x180-0x1F0)Trigger Mode Registers8 x 128-bit"]
ID["ID (0x20)Local APIC Identifier"]
VERSION["VERSION (0x30)APIC Version Info"]
end
subgraph subGraph3["Timer Subsystem"]
ICR_LOW["ICR_LO (0x300)Command Register Low"]
ICR_HIGH["ICR_HI (0x310)Command Register High"]
TIMER_INIT["ICR_TIMER (0x380)Initial Count"]
TIMER_CUR["CCR_TIMER (0x390)Current Count"]
TIMER_DIV["DCR_TIMER (0x3E0)Divide Configuration"]
LVT_TIMER["LVT_TIMER (0x320)Timer Interrupt"]
LVT_THERMAL["LVT_THERMAL (0x330)Thermal Monitor"]
LVT_PMC["LVT_PMC (0x340)Performance Counter"]
ISR["ISR Array (0x100-0x170)In-Service Registers8 x 128-bit"]
TMR["TMR Array (0x180-0x1F0)Trigger Mode Registers8 x 128-bit"]
IRR["IRR Array (0x200-0x270)Interrupt Request Registers8 x 128-bit"]
ID["ID (0x20)Local APIC Identifier"]
VERSION["VERSION (0x30)APIC Version Info"]
TPR["TPR (0x80)Task Priority"]
end
end
end
Sources: src/consts.rs(L61 - L114) src/regs/mod.rs(L15 - L104)
Address Translation Implementation
The ApicRegOffset enum provides a unified addressing scheme that abstracts the differences between xAPIC and x2APIC access patterns.
ApicRegOffset Enum Structure
| Register Category | Offset Range | ApicRegOffset Variants |
|---|---|---|
| Control Registers | 0x2-0xF | ID,Version,TPR,APR,PPR,EOI,RRR,LDR,DFR,SIVR |
| Interrupt Arrays | 0x10-0x27 | ISR(ISRIndex),TMR(TMRIndex),IRR(IRRIndex) |
| Error Status | 0x28 | ESR |
| LVT Registers | 0x2F, 0x32-0x37 | LvtCMCI,LvtTimer,LvtThermal,LvtPmc,LvtLint0,LvtLint1,LvtErr |
| Interrupt Command | 0x30-0x31 | ICRLow,ICRHi |
| Timer Registers | 0x38-0x39, 0x3E | TimerInitCount,TimerCurCount,TimerDivConf |
Sources: src/consts.rs(L61 - L114) src/consts.rs(L116 - L148)
Index Enums for Register Arrays
The system uses generated index enums for the three 256-bit interrupt state arrays, each comprising 8 128-bit registers.
flowchart TD
subgraph subGraph2["Usage in ApicRegOffset"]
ISRVariant["ApicRegOffset::ISR(ISRIndex)"]
TMRVariant["ApicRegOffset::TMR(TMRIndex)"]
IRRVariant["ApicRegOffset::IRR(IRRIndex)"]
end
subgraph subGraph1["Array Index Generation"]
MacroGenerate["define_index_enum! macroGenerates 0-7 indices"]
subgraph subGraph0["Generated Enums"]
ISRIndex["ISRIndexISRIndex0-ISRIndex7"]
TMRIndex["TMRIndexTMRIndex0-TMRIndex7"]
IRRIndex["IRRIndexIRRIndex0-IRRIndex7"]
end
end
IRRIndex --> IRRVariant
ISRIndex --> ISRVariant
MacroGenerate --> IRRIndex
MacroGenerate --> ISRIndex
MacroGenerate --> TMRIndex
TMRIndex --> TMRVariant
Sources: src/consts.rs(L3 - L54) src/consts.rs(L56 - L58)
Memory Layout Implementation
The LocalAPICRegs struct defines the physical memory layout using the tock-registers crate, providing type-safe register access with appropriate read/write permissions.
Register Access Patterns
| Access Type | Usage | Examples |
|---|---|---|
| ReadWrite | Configurable control registers | ID,TPR,LDR,DFR,ESR |
| ReadOnly | Status and version registers | VERSION,APR,PPR,RRD,CCR_TIMER |
| WriteOnly | Command registers | EOI,SELF_IPI |
| ReadOnly | Interrupt state arrays | ISR,TMR,IRRarrays |
| LVT Register Types | Specialized LVT registers | LvtTimerRegisterMmio,LvtThermalMonitorRegisterMmio, etc. |
Sources: src/regs/mod.rs(L15 - L104)
Reset Values and Constants
The system defines standardized reset values for APIC registers according to the x86 specification:
| Constant | Value | Purpose |
|---|---|---|
| RESET_LVT_REG | 0x0001_0000 | Default LVT register state with interrupt masked |
| RESET_SPURIOUS_INTERRUPT_VECTOR | 0x0000_00FF | Default spurious interrupt vector configuration |
Sources: src/consts.rs(L183 - L190)
Access Mode Implementation
The register system supports both xAPIC and x2APIC access modes through dedicated translation functions.
xAPIC MMIO Access
flowchart TD
subgraph subGraph0["xAPIC MMIO Translation"]
MMIOAddr["Guest Physical Address0xFEE00000 + offset"]
MMIOTranslate["xapic_mmio_access_reg_offset()(addr & 0xFFF) >> 4"]
MMIOConstants["DEFAULT_APIC_BASE: 0xFEE00000APIC_MMIO_SIZE: 0x1000"]
MMIOResult["ApicRegOffset enum value"]
end
MMIOAddr --> MMIOTranslate
MMIOConstants --> MMIOTranslate
MMIOTranslate --> MMIOResult
Sources: src/consts.rs(L192 - L202)
x2APIC MSR Access
flowchart TD
subgraph subGraph0["x2APIC MSR Translation"]
MSRAddr["System Register Address0x800-0x8FF range"]
MSRTranslate["x2apic_msr_access_reg()addr - 0x800"]
MSRConstants["X2APIC_MSE_REG_BASE: 0x800X2APIC_MSE_REG_SIZE: 0x100"]
MSRResult["ApicRegOffset enum value"]
end
MSRAddr --> MSRTranslate
MSRConstants --> MSRTranslate
MSRTranslate --> MSRResult
Sources: src/consts.rs(L205 - L216)
Register Structure Organization
The complete register system provides a comprehensive virtualization of Local APIC functionality, supporting interrupt handling, inter-processor communication, and timer operations through a unified, type-safe interface that abstracts the complexities of dual-mode APIC access.
Sources: src/consts.rs(L1 - L217) src/regs/mod.rs(L1 - L105)
Register Constants and Offsets
Relevant source files
This document covers the register constant definitions and address offset mappings used by the virtual Local APIC implementation. It documents the ApicRegOffset enum that standardizes access to all APIC registers, the index enums for register arrays, and the address translation functions that convert between xAPIC MMIO addresses and x2APIC MSR addresses to unified register offsets.
For information about the actual register layouts and bit field definitions, see the Local Vector Table documentation (3.2) and Control and Status Registers (3.3).
ApicRegOffset Enum
The ApicRegOffset enum serves as the central abstraction for all APIC register types, providing a unified interface for both xAPIC and x2APIC access modes. Each variant corresponds to a specific APIC register defined in the Intel x86 architecture specification.
flowchart TD
subgraph subGraph5["ApicRegOffset Register Categories"]
subgraph LVT_Registers["Local Vector Table"]
TimerInitCount["TimerInitCount (0x38)"]
TimerCurCount["TimerCurCount (0x39)"]
TimerDivConf["TimerDivConf (0x3E)"]
LvtTimer["LvtTimer (0x32)"]
LvtThermal["LvtThermal (0x33)"]
LvtPmc["LvtPmc (0x34)"]
LvtLint0["LvtLint0 (0x35)"]
LvtLint1["LvtLint1 (0x36)"]
LvtErr["LvtErr (0x37)"]
LvtCMCI["LvtCMCI (0x2F)"]
ISR["ISR[0-7] (0x10-0x17)"]
TMR["TMR[0-7] (0x18-0x1F)"]
IRR["IRR[0-7] (0x20-0x27)"]
EOI["EOI (0xB)"]
SIVR["SIVR (0xF)"]
ESR["ESR (0x28)"]
ICRLow["ICRLow (0x30)"]
ICRHi["ICRHi (0x31)"]
ID["ID (0x2)"]
Version["Version (0x3)"]
TPR["TPR (0x8)"]
APR["APR (0x9)"]
PPR["PPR (0xA)"]
subgraph Interrupt_Management["Interrupt Management"]
subgraph Control_Registers["Control Registers"]
TimerInitCount["TimerInitCount (0x38)"]
TimerCurCount["TimerCurCount (0x39)"]
TimerDivConf["TimerDivConf (0x3E)"]
LvtTimer["LvtTimer (0x32)"]
LvtThermal["LvtThermal (0x33)"]
LvtPmc["LvtPmc (0x34)"]
LvtLint0["LvtLint0 (0x35)"]
ISR["ISR[0-7] (0x10-0x17)"]
TMR["TMR[0-7] (0x18-0x1F)"]
IRR["IRR[0-7] (0x20-0x27)"]
EOI["EOI (0xB)"]
SIVR["SIVR (0xF)"]
ESR["ESR (0x28)"]
ICRLow["ICRLow (0x30)"]
ICRHi["ICRHi (0x31)"]
ID["ID (0x2)"]
Version["Version (0x3)"]
TPR["TPR (0x8)"]
APR["APR (0x9)"]
PPR["PPR (0xA)"]
subgraph Timer_Subsystem["Timer Subsystem"]
subgraph Register_Arrays["Register Arrays"]
TimerInitCount["TimerInitCount (0x38)"]
TimerCurCount["TimerCurCount (0x39)"]
TimerDivConf["TimerDivConf (0x3E)"]
LvtTimer["LvtTimer (0x32)"]
LvtThermal["LvtThermal (0x33)"]
LvtPmc["LvtPmc (0x34)"]
LvtLint0["LvtLint0 (0x35)"]
ISR["ISR[0-7] (0x10-0x17)"]
TMR["TMR[0-7] (0x18-0x1F)"]
IRR["IRR[0-7] (0x20-0x27)"]
EOI["EOI (0xB)"]
SIVR["SIVR (0xF)"]
ESR["ESR (0x28)"]
ICRLow["ICRLow (0x30)"]
ID["ID (0x2)"]
Version["Version (0x3)"]
TPR["TPR (0x8)"]
APR["APR (0x9)"]
end
end
end
end
end
end
The enum implements a from method that performs offset-to-register mapping based on the numeric offset values defined in the Intel specification. Register arrays like ISR, TMR, and IRR use index-based variants to represent their 8-register sequences.
Sources: src/consts.rs(L61 - L114) src/consts.rs(L116 - L148)
Register Array Index Enums
Three specialized index enums handle the APIC's 256-bit register arrays, each consisting of 8 consecutive 32-bit registers:
| Array Type | Index Enum | Register Range | Purpose |
|---|---|---|---|
| In-Service Register | ISRIndex | 0x10-0x17 | Tracks interrupts currently being serviced |
| Trigger Mode Register | TMRIndex | 0x18-0x1F | Stores trigger mode for each interrupt vector |
| Interrupt Request Register | IRRIndex | 0x20-0x27 | Queues pending interrupt requests |
flowchart TD
subgraph Concrete_Types["Concrete Index Types"]
ISRIndex["ISRIndex (ISR0-ISR7)"]
TMRIndex["TMRIndex (TMR0-TMR7)"]
IRRIndex["IRRIndex (IRR0-IRR7)"]
end
subgraph Index_Enum_Pattern["Index Enum Pattern"]
IndexEnum["Index0 through Index7"]
subgraph Generated_Methods["Generated Methods"]
from_method["from(usize) -> Self"]
as_usize_method["as_usize() -> usize"]
display_method["Display trait"]
end
end
IndexEnum --> as_usize_method
IndexEnum --> display_method
IndexEnum --> from_method
The define_index_enum! macro generates identical implementations for all three index types, providing type-safe indexing into the 8-register arrays while maintaining zero runtime cost through const evaluation.
Sources: src/consts.rs(L3 - L54) src/consts.rs(L56 - L58)
Address Translation Functions
The virtual APIC supports both xAPIC (MMIO-based) and x2APIC (MSR-based) access modes through dedicated address translation functions that convert guest addresses to ApicRegOffset values.
flowchart TD
subgraph Unified_Output["Unified Register Access"]
ApicRegOffset_Output["ApicRegOffset enumNormalized register identifier"]
end
subgraph Translation_Functions["Translation Functions"]
xapic_mmio_access_reg_offset["xapic_mmio_access_reg_offset()(addr & 0xFFF) >> 4"]
x2apic_msr_access_reg["x2apic_msr_access_reg()addr - 0x800"]
end
subgraph Guest_Access_Methods["Guest Access Methods"]
xAPIC_MMIO["xAPIC MMIO AccessGuestPhysAddr0xFEE0_0000-0xFEE0_0FFF"]
x2APIC_MSR["x2APIC MSR AccessSysRegAddr0x800-0x8FF"]
end
x2APIC_MSR --> x2apic_msr_access_reg
x2apic_msr_access_reg --> ApicRegOffset_Output
xAPIC_MMIO --> xapic_mmio_access_reg_offset
xapic_mmio_access_reg_offset --> ApicRegOffset_Output
xAPIC MMIO Translation
The xapic_mmio_access_reg_offset function extracts the 12-bit offset from the guest physical address and shifts right by 4 bits to obtain the register index. This follows the xAPIC specification where registers are aligned on 16-byte boundaries within the 4KB APIC page.
| Address Range | Calculation | Register Type |
|---|---|---|
| 0xFEE0_0020 | (0x20 & 0xFFF) >> 4 = 0x2 | ID Register |
| 0xFEE0_0100 | (0x100 & 0xFFF) >> 4 = 0x10 | ISR[0] |
| 0xFEE0_0320 | (0x320 & 0xFFF) >> 4 = 0x32 | LVT Timer |
x2APIC MSR Translation
The x2apic_msr_access_reg function performs simple offset subtraction from the MSR base address 0x800. x2APIC mode provides a flat MSR address space for APIC registers.
| MSR Address | Calculation | Register Type |
|---|---|---|
| 0x802 | 0x802 - 0x800 = 0x2 | ID Register |
| 0x810 | 0x810 - 0x800 = 0x10 | ISR[0] |
| 0x832 | 0x832 - 0x800 = 0x32 | LVT Timer |
Sources: src/consts.rs(L192 - L203) src/consts.rs(L205 - L216)
Reset Values and Default Constants
The system defines standard reset values for APIC registers according to Intel specifications:
flowchart TD
subgraph Address_Constants["Address Space Constants"]
X2APIC_MSE_REG_SIZE["X2APIC_MSE_REG_SIZE0x100256 MSR range"]
subgraph Reset_Constants["Reset Value Constants"]
DEFAULT_APIC_BASE["DEFAULT_APIC_BASE0xFEE0_0000xAPIC MMIO base"]
APIC_MMIO_SIZE["APIC_MMIO_SIZE0x10004KB page size"]
X2APIC_MSE_REG_BASE["X2APIC_MSE_REG_BASE0x800x2APIC MSR base"]
RESET_LVT_REG["RESET_LVT_REG0x0001_0000LVT registers after reset"]
RESET_SPURIOUS_INTERRUPT_VECTOR["RESET_SPURIOUS_INTERRUPT_VECTOR0x0000_00FFSIVR after reset"]
end
end
| Constant | Value | Purpose | Specification Reference |
|---|---|---|---|
| RESET_LVT_REG | 0x0001_0000 | LVT register reset state | Intel Manual 11.5.1 |
| RESET_SPURIOUS_INTERRUPT_VECTOR | 0x0000_00FF | SIVR reset state | Intel Manual 11.9 |
| DEFAULT_APIC_BASE | 0xFEE0_0000 | Standard xAPIC MMIO base | Intel Manual 11.4.1 |
| APIC_MMIO_SIZE | 0x1000 | xAPIC page size | Intel Manual 11.4.1 |
The reset values ensure that LVT registers start in a masked state (bit 16 set) and the spurious interrupt vector defaults to vector 0xFF, providing safe initialization conditions for the virtual APIC.
Sources: src/consts.rs(L183 - L191) src/consts.rs(L197 - L198) src/consts.rs(L210 - L211)
Local Vector Table (LVT)
Relevant source files
Purpose and Scope
This document covers the Local Vector Table (LVT) subsystem within the x86_vlapic crate, which manages interrupt vector configuration for various local interrupt sources in the virtual Local APIC implementation. The LVT provides a standardized interface for configuring how different types of interrupts (timer, thermal, performance monitoring, external pins, errors) are delivered to the processor.
For detailed information about individual LVT register implementations, see Timer LVT Register, External Interrupt Pin Registers, System Monitoring LVT Registers, and Error Handling LVT Registers. For broader APIC register system context, see Register System.
LVT Architecture Overview
The Local Vector Table consists of seven specialized registers that control different interrupt sources within the Local APIC. Each register follows a common structural pattern while providing source-specific functionality.
LVT Register Layout
flowchart TD
subgraph subGraph4["LocalVectorTable Struct"]
LVT["LocalVectorTable"]
subgraph subGraph3["Error Handling"]
ERROR["lvt_errLvtErrorRegisterLocal0x370"]
end
subgraph subGraph2["External Pin Sources"]
LINT0["lvt_lint0LvtLint0RegisterLocalFEE0_0350H"]
LINT1["lvt_lint1LvtLint1RegisterLocalFEE0_0360H"]
end
subgraph subGraph1["Timer Source"]
TIMER["lvt_timerLvtTimerRegisterLocalFEE0_0320H"]
end
subgraph subGraph0["System Monitoring Sources"]
CMCI["lvt_cmciLvtCmciRegisterLocalFEE0_02F0H"]
THERMAL["lvt_thermalLvtThermalMonitorRegisterLocalFEE0_0330H"]
PERFCNT["lvt_perf_countLvtPerformanceCounterRegisterLocalFEE0_0340H"]
end
end
LVT --> CMCI
LVT --> ERROR
LVT --> LINT0
LVT --> LINT1
LVT --> PERFCNT
LVT --> THERMAL
LVT --> TIMER
Sources: src/lvt.rs(L9 - L24)
Register Address Mapping
The LVT registers are mapped to specific offsets within the APIC register space:
| Register | Type | Address Offset | Purpose |
|---|---|---|---|
| lvt_cmci | LvtCmciRegisterLocal | FEE0_02F0H | Corrected Machine Check Interrupts |
| lvt_timer | LvtTimerRegisterLocal | FEE0_0320H | Local APIC Timer |
| lvt_thermal | LvtThermalMonitorRegisterLocal | FEE0_0330H | Thermal Monitoring |
| lvt_perf_count | LvtPerformanceCounterRegisterLocal | FEE0_0340H | Performance Counter Overflow |
| lvt_lint0 | LvtLint0RegisterLocal | FEE0_0350H | External Interrupt Pin 0 |
| lvt_lint1 | LvtLint1RegisterLocal | FEE0_0360H | External Interrupt Pin 1 |
| lvt_err | LvtErrorRegisterLocal | 0x370 | APIC Error Conditions |
Sources: src/lvt.rs(L10 - L23)
Common LVT Structure and Functionality
All LVT registers share a common architectural pattern using the *RegisterLocal type structure, which provides local cached copies of register values. This design enables efficient access while maintaining consistency with the underlying APIC register model.
Initialization Pattern
flowchart TD DEFAULT["Default::default()"] RESET_VALUE["RESET_LVT_REG"] CMCI_INIT["LvtCmciRegisterLocal::new()"] TIMER_INIT["LvtTimerRegisterLocal::new()"] THERMAL_INIT["LvtThermalMonitorRegisterLocal::new()"] PERF_INIT["LvtPerformanceCounterRegisterLocal::new()"] LINT0_INIT["LvtLint0RegisterLocal::new()"] LINT1_INIT["LvtLint1RegisterLocal::new()"] ERROR_INIT["LvtErrorRegisterLocal::new()"] DEFAULT --> RESET_VALUE RESET_VALUE --> CMCI_INIT RESET_VALUE --> ERROR_INIT RESET_VALUE --> LINT0_INIT RESET_VALUE --> LINT1_INIT RESET_VALUE --> PERF_INIT RESET_VALUE --> THERMAL_INIT RESET_VALUE --> TIMER_INIT
Sources: src/lvt.rs(L26 - L38)
All LVT registers are initialized with the same reset value RESET_LVT_REG, providing a consistent starting state across all interrupt sources. This ensures predictable behavior during system initialization and reset conditions.
Register Organization
The LVT implementation follows a modular architecture where each register type is implemented in its own module, promoting code organization and maintainability.
Module Structure
flowchart TD
subgraph src/regs/lvt/["src/regs/lvt/"]
MOD["mod.rsPublic Interface"]
subgraph subGraph0["Individual Register Modules"]
CMCI_MOD["cmci.rsLvtCmciRegisterLocal"]
ERROR_MOD["error.rsLvtErrorRegisterLocal"]
LINT0_MOD["lint0.rsLvtLint0RegisterLocal"]
LINT1_MOD["lint1.rsLvtLint1RegisterLocal"]
PERFMON_MOD["perfmon.rsLvtPerformanceCounterRegisterLocal"]
THERMAL_MOD["thermal.rsLvtThermalMonitorRegisterLocal"]
TIMER_MOD["timer.rsLvtTimerRegisterLocal"]
end
end
MOD --> CMCI_MOD
MOD --> ERROR_MOD
MOD --> LINT0_MOD
MOD --> LINT1_MOD
MOD --> PERFMON_MOD
MOD --> THERMAL_MOD
MOD --> TIMER_MOD
Sources: src/regs/lvt/mod.rs(L1 - L16)
Each module implements a specific LVT register type with its unique functionality while maintaining the common interface pattern. The mod.rs file serves as the public interface, re-exporting all register types for use by the LocalVectorTable struct.
Integration with Virtual APIC System
The LocalVectorTable is integrated into the broader virtual APIC register system through the VirtualApicRegs structure, where it manages all interrupt vector table functionality as a cohesive unit. This design separates interrupt configuration concerns from other APIC functionality while maintaining the standard APIC register interface.
Sources: src/lvt.rs(L1 - L39) src/regs/lvt/mod.rs(L1 - L16)
Timer LVT Register
Relevant source files
This document covers the Timer LVT (Local Vector Table) Register implementation in the x86_vlapic crate. The Timer LVT register controls the configuration and behavior of timer-based interrupts in the virtual Local APIC, including timer modes, interrupt masking, and delivery status monitoring.
For information about other LVT registers, see External Interrupt Pin Registers, System Monitoring LVT Registers, and Error Handling LVT Registers. For the broader LVT system architecture, see Local Vector Table (LVT).
Register Overview
The Timer LVT register is located at offset 0x320 in the APIC register space and provides control over timer interrupt generation and delivery. It supports three distinct timer modes and includes standard LVT fields for interrupt configuration.
Timer LVT Register Bit Layout
flowchart TD
subgraph subGraph0["Timer LVT Register (32-bit) - Offset 0x320"]
Reserved2["Reserved2[31:19]13 bits"]
TimerMode["TimerMode[18:17]2 bits"]
Mask["Mask[16]1 bit"]
Reserved1["Reserved1[15:13]3 bits"]
DeliveryStatus["DeliveryStatus[12]1 bit (RO)"]
Reserved0["Reserved0[11:8]4 bits"]
Vector["Vector[7:0]8 bits"]
end
Sources: src/regs/lvt/timer.rs(L5 - L49)
Register Fields
Timer Mode Field
The TimerMode field at bits [18:17] controls the operational mode of the timer:
| Mode | Value | Description |
|---|---|---|
| OneShot | 0b00 | One-shot mode using a count-down value |
| Periodic | 0b01 | Periodic mode reloading a count-down value |
| TSCDeadline | 0b10 | TSC-Deadline mode using absolute target value in IA32_TSC_DEADLINE MSR |
| Reserved | 0b11 | Reserved value (not used) |
Sources: src/regs/lvt/timer.rs(L10 - L20)
Mask Field
The Mask bit at position [16] controls interrupt reception:
NotMasked(0): Enables reception of the interruptMasked(1): Inhibits reception of the interrupt
The mask flag is automatically set to 1 on reset and can only be cleared by software. When the local APIC handles a timer interrupt, it may automatically set this mask flag depending on the timer configuration.
Sources: src/regs/lvt/timer.rs(L21 - L32)
Delivery Status Field
The DeliveryStatus bit at position [12] is read-only and indicates interrupt delivery status:
Idle(0): No current activity for this interrupt source, or the previous interrupt was delivered and acceptedSendPending(1): An interrupt has been delivered to the processor core but not yet accepted
Sources: src/regs/lvt/timer.rs(L34 - L44)
Vector Field
The Vector field at bits [7:0] specifies the interrupt vector number that will be used when the timer interrupt is delivered to the processor.
Sources: src/regs/lvt/timer.rs(L46 - L47)
Timer Mode Details
Timer Mode Operation Flow
stateDiagram-v2 [*] --> Disabled : "Reset/Mask=1" Disabled --> OneShot : "Configure OneShot modeSet initial count" Disabled --> Periodic : "Configure Periodic modeSet initial count" Disabled --> TSCDeadline : "Configure TSC-DeadlineSet deadline MSR" OneShot --> CountingDown : "Timer starts" CountingDown --> InterruptGenerated : "Count reaches 0" InterruptGenerated --> Disabled : "One-shot complete" Periodic --> PeriodicCounting : "Timer starts" PeriodicCounting --> PeriodicInterrupt : "Count reaches 0" PeriodicInterrupt --> PeriodicCounting : "Reload initial count" PeriodicCounting --> Disabled : "Mask=1 or mode change" TSCDeadline --> TSCMonitoring : "Monitor TSC value" TSCMonitoring --> TSCInterrupt : "TSC >= deadline" TSCInterrupt --> Disabled : "Deadline reached" TSCMonitoring --> Disabled : "Deadline cleared or mode change" Disabled --> [*] : "System reset"
Sources: src/regs/lvt/timer.rs(L11 - L19)
Access Types
The Timer LVT register supports two access patterns for different use cases:
MMIO Access Type
flowchart TD
subgraph subGraph0["Direct MMIO Access Pattern"]
Guest["Guest VM Timer Configuration"]
MMIO["LvtTimerRegisterMmioReadWrite"]
Hardware["Physical APIC Registerat 0xFEE00320"]
end
Guest --> MMIO
MMIO --> Hardware
The LvtTimerRegisterMmio type provides direct memory-mapped I/O access to the timer LVT register for real-time configuration changes.
Sources: src/regs/lvt/timer.rs(L52)
Local Copy Access Type
flowchart TD
subgraph subGraph0["Local Copy Access Pattern"]
VirtualAPIC["Virtual APIC Implementation"]
Local["LvtTimerRegisterLocalLocalRegisterCopy"]
Memory["Local Memory CopyCached Register State"]
end
Local --> Memory
VirtualAPIC --> Local
The LvtTimerRegisterLocal type maintains a local cached copy of the register contents in memory, avoiding volatile MMIO reads for each access while providing the same interface.
Sources: src/regs/lvt/timer.rs(L54 - L59)
Integration with Virtual APIC System
Timer LVT in LVT System Context
flowchart TD
subgraph subGraph1["Timer Subsystem Integration"]
TimerInit["TIMER_INIT_COUNTOffset 0x380Initial count value"]
TimerCurrent["TIMER_CURRENT_COUNTOffset 0x390Current count value"]
TimerDivide["TIMER_DIVIDE_CONFIGOffset 0x3E0Divide configuration"]
end
subgraph subGraph0["LVT Register System"]
LVTTimer["LVT_TIMEROffset 0x320Timer interrupts"]
LVTThermal["LVT_THERMALOffset 0x330"]
LVTPMC["LVT_PMCOffset 0x340"]
LVTLINT0["LVT_LINT0Offset 0x350"]
LVTLINT1["LVT_LINT1Offset 0x360"]
LVTError["LVT_ERROROffset 0x370"]
LVTCMCI["LVT_CMCIOffset 0x2F0"]
end
LVTTimer --> TimerCurrent
LVTTimer --> TimerDivide
LVTTimer --> TimerInit
The Timer LVT register works in conjunction with the timer count and divide configuration registers to provide complete timer interrupt functionality within the virtual APIC system.
Sources: src/regs/lvt/timer.rs(L1 - L60)
External Interrupt Pin Registers
Relevant source files
This document covers the LINT0 and LINT1 Local Vector Table (LVT) registers, which handle external interrupt pins in the virtual LAPIC implementation. These registers configure how the APIC responds to interrupts signaled through external hardware interrupt pins.
For information about other LVT registers like timer and thermal monitoring, see Timer LVT Register and System Monitoring LVT Registers.
Overview
The LINT0 and LINT1 registers are part of the Local Vector Table that configure external interrupt pin behavior. LINT0 is typically used for external interrupt controllers (like the legacy 8259A PIC), while LINT1 is commonly configured for NMI delivery from external sources.
Both registers share identical field layouts but serve different interrupt pins and have some operational constraints:
- LINT0 supports all delivery modes and trigger configurations
- LINT1 should always use edge-sensitive triggering and does not support level-sensitive interrupts
flowchart TD
subgraph subGraph3["Processor Core"]
INT_HANDLER["Interrupt Handler"]
SMI_HANDLER["SMI Handler"]
NMI_HANDLER["NMI Handler"]
end
subgraph subGraph2["Virtual LAPIC LVT System"]
LINT0_REG["LvtLint0RegisterMmio/LocalOFFSET 0x350FEE0_0350H"]
LINT1_REG["LvtLint1RegisterLocal/MmioOFFSET 0x360FEE0_0360H"]
subgraph subGraph1["Register Fields"]
MASK["Mask Bit"]
DELMODE["DeliveryModeFixed/SMI/NMI/INIT/ExtINT"]
TRIGMODE["TriggerModeEdge/Level"]
POLARITY["InterruptInputPinPolarityActiveHigh/ActiveLow"]
VECTOR["Vector8-bit interrupt number"]
end
end
subgraph subGraph0["External Hardware"]
EXT_CTRL["External Interrupt Controller (8259A)"]
NMI_SRC["NMI Source Hardware"]
OTHER_EXT["Other External Devices"]
end
DELMODE --> INT_HANDLER
DELMODE --> NMI_HANDLER
DELMODE --> SMI_HANDLER
EXT_CTRL --> LINT0_REG
LINT0_REG --> DELMODE
LINT0_REG --> MASK
LINT1_REG --> DELMODE
LINT1_REG --> MASK
NMI_SRC --> LINT1_REG
OTHER_EXT --> LINT0_REG
Sources: src/regs/lvt/lint0.rs(L88 - L90) src/regs/lvt/lint1.rs(L88 - L90)
Register Structure and Implementation
Both LINT0 and LINT1 registers implement identical 32-bit layouts using the tock_registers framework. The implementation provides both MMIO and local cached register types.
flowchart TD
subgraph subGraph2["Access Patterns"]
DIRECT_MMIO["Direct MMIO AccessVolatile reads/writes"]
CACHED_LOCAL["Cached Local CopyMemory-resident copy"]
end
subgraph subGraph1["Bitfield Definition"]
LVT_LINT0_BITFIELD["LVT_LINT0 register_bitfields!"]
LVT_LINT1_BITFIELD["LVT_LINT1 register_bitfields!"]
end
subgraph subGraph0["Register Types"]
MMIO_TYPE["LvtLint0RegisterMmioReadWrite"]
LOCAL_TYPE["LvtLint0RegisterLocalLocalRegisterCopy"]
end
LOCAL_TYPE --> CACHED_LOCAL
LVT_LINT0_BITFIELD --> LOCAL_TYPE
LVT_LINT0_BITFIELD --> MMIO_TYPE
LVT_LINT1_BITFIELD --> LOCAL_TYPE
LVT_LINT1_BITFIELD --> MMIO_TYPE
MMIO_TYPE --> DIRECT_MMIO
Sources: src/regs/lvt/lint0.rs(L1 - L4) src/regs/lvt/lint0.rs(L90 - L97) src/regs/lvt/lint1.rs(L90 - L97)
Register Field Layout
| Bits | Field | Description | Values |
|---|---|---|---|
| 31-17 | Reserved1 | Reserved bits | Must be 0 |
| 16 | Mask | Enable/disable interrupt | 0=NotMasked, 1=Masked |
| 15 | TriggerMode | Edge or level sensitive | 0=EdgeSensitive, 1=LevelSensitive |
| 14 | RemoteIRR | Remote IRR flag (RO) | Set when interrupt accepted |
| 13 | InterruptInputPinPolarity | Pin polarity | 0=ActiveHigh, 1=ActiveLow |
| 12 | DeliveryStatus | Delivery status (RO) | 0=Idle, 1=SendPending |
| 11 | Reserved0 | Reserved | Must be 0 |
| 10-8 | DeliveryMode | Interrupt delivery type | See delivery modes table |
| 7-0 | Vector | Interrupt vector number | 0-255 |
Sources: src/regs/lvt/lint0.rs(L7 - L86) src/regs/lvt/lint1.rs(L7 - L86)
Delivery Modes
The DeliveryMode field determines how the interrupt is delivered to the processor core. Different modes serve different purposes in the interrupt handling system.
| Mode | Value | Description | Vector Usage |
|---|---|---|---|
| Fixed | 000b | Standard interrupt delivery | Uses Vector field |
| SMI | 010b | System Management Interrupt | Vector should be 00H |
| NMI | 100b | Non-Maskable Interrupt | Vector ignored |
| INIT | 101b | Processor initialization | Vector should be 00H |
| ExtINT | 111b | External interrupt controller | Vector from external controller |
Delivery Mode Constraints
- Fixed Mode: Most common mode, uses the vector field to specify interrupt number
- SMI Mode: Triggers System Management Mode entry, vector field ignored
- NMI Mode: Generates non-maskable interrupt, bypasses interrupt masking
- INIT Mode: Causes processor reset/initialization, not supported for thermal/PMC/CMCI registers
- ExtINT Mode: Compatibility with 8259A-style external controllers, only one system-wide ExtINT source allowed
Sources: src/regs/lvt/lint0.rs(L56 - L82) src/regs/lvt/lint1.rs(L56 - L82)
Trigger Mode and Polarity Configuration
The trigger mode and polarity settings determine how the APIC interprets signals on the LINT pins.
Trigger Mode Behavior
- Edge Sensitive (0): Interrupt triggered on signal transition
- Level Sensitive (1): Interrupt triggered while signal is active
Important constraints:
- LINT1 should always use edge-sensitive triggering
- Level-sensitive interrupts are not supported for LINT1
- Fixed delivery mode can use either trigger mode
- NMI, SMI, and INIT modes always use edge-sensitive triggering
- ExtINT mode always uses level-sensitive triggering
Polarity Configuration
The InterruptInputPinPolarity field configures the active state of the interrupt pin:
- ActiveHigh (0): Interrupt active when pin is high
- ActiveLow (1): Interrupt active when pin is low
Sources: src/regs/lvt/lint0.rs(L17 - L30) src/regs/lvt/lint0.rs(L36 - L43) src/regs/lvt/lint1.rs(L17 - L30) src/regs/lvt/lint1.rs(L36 - L43)
Status and Control Fields
Delivery Status (Read-Only)
The DeliveryStatus field indicates the current state of interrupt delivery:
- Idle (0): No pending activity or previous interrupt accepted
- SendPending (1): Interrupt delivered to core but not yet accepted
Remote IRR Flag (Read-Only)
For fixed mode, level-triggered interrupts, the RemoteIRR flag tracks interrupt servicing:
- Set when the local APIC accepts the interrupt
- Reset when an EOI command is received
- Undefined for edge-triggered interrupts and other delivery modes
Mask Bit
The Mask field controls interrupt enabling:
- NotMasked (0): Interrupt enabled
- Masked (1): Interrupt disabled
Sources: src/regs/lvt/lint0.rs(L10 - L16) src/regs/lvt/lint0.rs(L31 - L35) src/regs/lvt/lint0.rs(L44 - L54)
System Monitoring LVT Registers
Relevant source files
This document covers the system monitoring Local Vector Table (LVT) registers in the x86_vlapic implementation. These registers handle interrupts generated by thermal monitoring hardware and performance counter overflow events. System monitoring LVT registers are distinct from timer-based interrupts (see Timer LVT Register), external pin interrupts (see External Interrupt Pin Registers), and error condition interrupts (see Error Handling LVT Registers).
Overview
The x86_vlapic crate implements two system monitoring LVT registers that provide interrupt delivery for hardware monitoring subsystems:
- Thermal Monitor Register: Handles thermal sensor threshold interrupts
- Performance Counter Register: Handles performance monitoring unit (PMU) overflow and Intel Processor Trace (PT) interrupts
Both registers are implementation-specific extensions to the APIC architecture and share a common bit field structure for interrupt configuration.
System Monitoring LVT Architecture
System Monitoring LVT Register Structure
flowchart TD
subgraph INTERRUPT_SOURCES["Hardware Interrupt Sources"]
MASK["Mask (16)"]
THERMAL_SENSOR["Thermal Sensor Threshold Events"]
PMU_OVERFLOW["Performance Counter Overflow"]
INTEL_PT["Intel PT ToPA PMI"]
end
subgraph PERFMON["Performance Counter Register (0x340)"]
PERFMON_BITFIELDS["LVT_PERFORMANCE_COUNTER Bitfields"]
PERFMON_MMIO["LvtPerformanceCounterRegisterMmio"]
PERFMON_LOCAL["LvtPerformanceCounterRegisterLocal"]
end
subgraph THERMAL["Thermal Monitor Register (0x330)"]
VECTOR["Vector (0-7)"]
DELIVERY_MODE["DeliveryMode (8-10)"]
DELIVERY_STATUS["DeliveryStatus (12)"]
THERMAL_BITFIELDS["LVT_THERMAL_MONITOR Bitfields"]
THERMAL_MMIO["LvtThermalMonitorRegisterMmio"]
THERMAL_LOCAL["LvtThermalMonitorRegisterLocal"]
end
subgraph COMMON["Common Bit Structure"]
VECTOR["Vector (0-7)"]
DELIVERY_MODE["DeliveryMode (8-10)"]
DELIVERY_STATUS["DeliveryStatus (12)"]
MASK["Mask (16)"]
THERMAL_MMIO["LvtThermalMonitorRegisterMmio"]
THERMAL_LOCAL["LvtThermalMonitorRegisterLocal"]
THERMAL_SENSOR["Thermal Sensor Threshold Events"]
end
PERFMON_LOCAL --> PERFMON_BITFIELDS
PERFMON_MMIO --> PERFMON_BITFIELDS
THERMAL_LOCAL --> THERMAL_BITFIELDS
THERMAL_MMIO --> THERMAL_BITFIELDS
Sources: src/regs/lvt/thermal.rs(L1 - L74) src/regs/lvt/perfmon.rs(L1 - L75)
Thermal Monitor Register
The LVT Thermal Monitor Register at offset 0x330 delivers interrupts when thermal monitoring hardware detects temperature threshold violations. This register is implementation-specific and always located at base address FEE0 0330H when implemented.
Register Definition
The thermal monitor register is defined using the LVT_THERMAL_MONITOR bitfield structure:
| Field | Bits | Description |
|---|---|---|
| Vector | 0-7 | Interrupt vector number |
| DeliveryMode | 8-10 | Interrupt delivery type |
| DeliveryStatus | 12 | Read-only delivery status |
| Mask | 16 | Interrupt enable/disable |
Thermal Interrupt Conditions
The thermal monitor generates interrupts when:
- Processor temperature exceeds configured thresholds
- Thermal control circuit (TCC) activation occurs
- Temperature monitoring hardware detects critical conditions
Register Types
The crate provides two access patterns for the thermal monitor register:
LvtThermalMonitorRegisterMmio: Direct MMIO access usingReadWrite<u32, LVT_THERMAL_MONITOR::Register>LvtThermalMonitorRegisterLocal: Cached local copy usingLocalRegisterCopy<u32, LVT_THERMAL_MONITOR::Register>
Sources: src/regs/lvt/thermal.rs(L5 - L66) src/regs/lvt/thermal.rs(L68 - L73)
Performance Counter Register
The LVT Performance Counter Register at offset 0x340 handles interrupts from performance monitoring units and Intel Processor Trace events. This register is implementation-specific and not guaranteed to be at the documented base address.
Register Definition
The performance counter register uses the LVT_PERFORMANCE_COUNTER bitfield structure with identical bit layout to the thermal monitor register:
| Field | Bits | Description |
|---|---|---|
| Vector | 0-7 | Interrupt vector number |
| DeliveryMode | 8-10 | Interrupt delivery type |
| DeliveryStatus | 12 | Read-only delivery status |
| Mask | 16 | Interrupt enable/disable |
Performance Monitoring Interrupt Sources
The performance counter register delivers interrupts for:
- Performance Counter Overflow: When hardware performance counters exceed their configured limits
- Intel PT ToPA PMI: Performance Monitoring Interrupt (PMI) signals from Intel Processor Trace Table of Physical Addresses (ToPA) mechanism
Register Types
Similar to the thermal register, two access patterns are provided:
LvtPerformanceCounterRegisterMmio: Direct MMIO access usingReadWrite<u32, LVT_PERFORMANCE_COUNTER::Register>LvtPerformanceCounterRegisterLocal: Cached local copy usingLocalRegisterCopy<u32, LVT_PERFORMANCE_COUNTER::Register>
Sources: src/regs/lvt/perfmon.rs(L5 - L66) src/regs/lvt/perfmon.rs(L68 - L74)
Common Bit Field Structure
Both system monitoring registers share identical bit field definitions, providing consistent configuration interfaces across different monitoring subsystems.
System Monitoring Register Bit Fields
flowchart TD
subgraph REG32["32-bit Register Layout"]
FIXED["Fixed = 000"]
IDLE["Idle = 0"]
NOT_MASKED["NotMasked = 0"]
RESERVED2["Reserved2(17-31)"]
MASK_BIT["Mask(16)"]
RESERVED1["Reserved1(13-15)"]
DELIVERY_STATUS_BIT["DeliveryStatus(12)"]
RESERVED0["Reserved0(11)"]
DELIVERY_MODE_BITS["DeliveryMode(8-10)"]
VECTOR_BITS["Vector(0-7)"]
end
subgraph MODE_VALUES["Delivery Mode Values"]
FIXED["Fixed = 000"]
SMI["SMI = 010"]
NMI["NMI = 100"]
RESERVED_MODE["Reserved = 110"]
IDLE["Idle = 0"]
SEND_PENDING["SendPending = 1"]
NOT_MASKED["NotMasked = 0"]
MASKED["Masked = 1"]
RESERVED2["Reserved2(17-31)"]
subgraph MASK_VALUES["Mask Field Values"]
subgraph STATUS_VALUES["Delivery Status Values"]
FIXED["Fixed = 000"]
SMI["SMI = 010"]
IDLE["Idle = 0"]
SEND_PENDING["SendPending = 1"]
NOT_MASKED["NotMasked = 0"]
MASKED["Masked = 1"]
RESERVED2["Reserved2(17-31)"]
end
end
end
Sources: src/regs/lvt/thermal.rs(L10 - L16) src/regs/lvt/perfmon.rs(L10 - L16) src/regs/lvt/thermal.rs(L30 - L56) src/regs/lvt/perfmon.rs(L30 - L56)
Delivery Mode Restrictions
System monitoring LVT registers have specific delivery mode restrictions compared to other LVT registers:
| Mode | Binary | Support Status |
|---|---|---|
| Fixed | 000 | ✅ Supported |
| SMI | 010 | ✅ Supported |
| NMI | 100 | ✅ Supported |
| INIT | 101 | ❌ Not supported |
| Reserved | 110 | ❌ Not supported |
| ExtINT | 111 | ❌ Not supported |
The INIT and ExtINT delivery modes are explicitly not supported for thermal monitor and performance counter registers.
Sources: src/regs/lvt/thermal.rs(L44 - L45) src/regs/lvt/perfmon.rs(L44 - L45) src/regs/lvt/thermal.rs(L54 - L55) src/regs/lvt/perfmon.rs(L54 - L55)
Register Access Patterns
System monitoring LVT registers support both direct MMIO access and cached local copy patterns using the tock-registers framework.
LVT System Monitoring Register Access Architecture
sequenceDiagram
participant GuestOS as "Guest OS"
participant EmulatedLocalApic as "EmulatedLocalApic"
participant VirtualApicRegs as "VirtualApicRegs"
participant MMIORegister as "MMIO Register"
participant LocalCopy as "Local Copy"
Note over GuestOS,LocalCopy: Thermal Monitor Access (0x330)
GuestOS ->> EmulatedLocalApic: "MMIO Read/Write FEE0_0330H"
EmulatedLocalApic ->> VirtualApicRegs: "handle_read/write(ApicRegOffset::LvtThermal)"
alt Direct MMIO Access
VirtualApicRegs ->> MMIORegister: "LvtThermalMonitorRegisterMmio::read/write"
MMIORegister -->> VirtualApicRegs: "LVT_THERMAL_MONITOR register value"
else Cached Local Access
VirtualApicRegs ->> LocalCopy: "LvtThermalMonitorRegisterLocal::read/write"
LocalCopy -->> VirtualApicRegs: "Cached LVT_THERMAL_MONITOR value"
end
Note over GuestOS,LocalCopy: Performance Counter Access (0x340)
GuestOS ->> EmulatedLocalApic: "MMIO Read/Write FEE0_0340H"
EmulatedLocalApic ->> VirtualApicRegs: "handle_read/write(ApicRegOffset::LvtPerformanceCounter)"
alt Direct MMIO Access
VirtualApicRegs ->> MMIORegister: "LvtPerformanceCounterRegisterMmio::read/write"
MMIORegister -->> VirtualApicRegs: "LVT_PERFORMANCE_COUNTER register value"
else Cached Local Access
VirtualApicRegs ->> LocalCopy: "LvtPerformanceCounterRegisterLocal::read/write"
LocalCopy -->> VirtualApicRegs: "Cached LVT_PERFORMANCE_COUNTER value"
end
Sources: src/regs/lvt/thermal.rs(L66 - L73) src/regs/lvt/perfmon.rs(L66 - L74)
Error Handling LVT Registers
Relevant source files
This document covers the two Local Vector Table (LVT) registers responsible for error handling in the virtual LAPIC implementation: the LVT Error Register and the LVT CMCI (Corrected Machine Check Interrupt) Register. These registers configure interrupt delivery for APIC internal errors and corrected machine check errors respectively.
For information about other LVT registers such as timer and external interrupt pins, see Timer LVT Register and External Interrupt Pin Registers. For system monitoring LVT registers, see System Monitoring LVT Registers.
Error Handling LVT Architecture
The error handling LVT registers are part of the broader LVT subsystem and provide interrupt configuration for different types of error conditions detected by the APIC.
Error Handling LVT Position in Register Space
flowchart TD
subgraph subGraph2["Virtual Register Space (4KB Page)"]
subgraph subGraph1["Error_Sources[Error Detection Sources]"]
APIC_INTERNAL["APIC InternalError Conditions"]
MCE_CORRECTED["Machine CheckCorrected Errors"]
end
subgraph subGraph0["LVT_Registers[Local Vector Table Registers]"]
LVT_ERROR["LVT_ERROR0x370APIC Internal Errors"]
LVT_CMCI["LVT_CMCI0x2F0Corrected Machine Check"]
LVT_TIMER["LVT_TIMER0x320Timer Interrupts"]
LVT_THERMAL["LVT_THERMAL0x330Thermal Monitor"]
LVT_PMC["LVT_PMC0x340Performance Counter"]
LVT_LINT0["LVT_LINT00x350External Pin 0"]
LVT_LINT1["LVT_LINT10x360External Pin 1"]
end
end
APIC_INTERNAL --> LVT_ERROR
MCE_CORRECTED --> LVT_CMCI
Sources: src/regs/lvt/error.rs(L35 - L36) src/regs/lvt/cmci.rs(L62 - L65)
Code Entity Mapping
flowchart TD
subgraph subGraph1["Register_Access_Patterns[Register Access Patterns]"]
DIRECT_MMIO["Direct MMIO AccessVolatile Hardware Read/Write"]
CACHED_LOCAL["Cached Local CopyNon-volatile Memory Access"]
end
subgraph subGraph0["Rust_Types[Rust Type Definitions]"]
LVT_ERROR_BITFIELD["LVT_ERRORregister_bitfields!"]
LVT_CMCI_BITFIELD["LVT_CMCIregister_bitfields!"]
ERROR_MMIO["LvtErrorRegisterMmioReadWrite"]
ERROR_LOCAL["LvtErrorRegisterLocalLocalRegisterCopy"]
CMCI_MMIO["LvtCmciRegisterMmioReadWrite"]
CMCI_LOCAL["LvtCmciRegisterLocalLocalRegisterCopy"]
end
CMCI_LOCAL --> CACHED_LOCAL
CMCI_MMIO --> DIRECT_MMIO
ERROR_LOCAL --> CACHED_LOCAL
ERROR_MMIO --> DIRECT_MMIO
LVT_CMCI_BITFIELD --> CMCI_LOCAL
LVT_CMCI_BITFIELD --> CMCI_MMIO
LVT_ERROR_BITFIELD --> ERROR_LOCAL
LVT_ERROR_BITFIELD --> ERROR_MMIO
Sources: src/regs/lvt/error.rs(L37 - L44) src/regs/lvt/cmci.rs(L66 - L73)
LVT Error Register
The LVT Error Register handles interrupts generated when the APIC detects internal error conditions. It is located at offset 0x370 in the APIC register space.
Register Structure
| Bits | Field | Description |
|---|---|---|
| 31-17 | Reserved2 | Reserved bits |
| 16 | Mask | Interrupt mask (0=NotMasked, 1=Masked) |
| 15-13 | Reserved1 | Reserved bits |
| 12 | DeliveryStatus | Read-only delivery status (0=Idle, 1=SendPending) |
| 11-8 | Reserved0 | Reserved bits |
| 7-0 | Vector | Interrupt vector number |
The LVT Error Register has a simplified structure compared to other LVT registers, lacking delivery mode configuration since error interrupts are always delivered using Fixed mode.
Key Characteristics
- Fixed Delivery Mode Only: Unlike the CMCI register, the Error register implicitly uses Fixed delivery mode
- Simple Configuration: Only requires vector number and mask bit configuration
- Internal Error Detection: Triggers on APIC hardware-detected error conditions
- Read-Only Status: The
DeliveryStatusfield provides read-only interrupt delivery state
Sources: src/regs/lvt/error.rs(L5 - L33) src/regs/lvt/error.rs(L35 - L36)
LVT CMCI Register
The LVT CMCI (Corrected Machine Check Interrupt) Register handles interrupts for corrected machine check errors when error counts reach threshold values. It is located at offset 0x2F0 in the APIC register space.
Register Structure
| Bits | Field | Description |
|---|---|---|
| 31-17 | Reserved2 | Reserved bits |
| 16 | Mask | Interrupt mask (0=NotMasked, 1=Masked) |
| 15-13 | Reserved1 | Reserved bits |
| 12 | DeliveryStatus | Read-only delivery status (0=Idle, 1=SendPending) |
| 11 | Reserved0 | Reserved bit |
| 10-8 | DeliveryMode | Interrupt delivery mode |
| 7-0 | Vector | Interrupt vector number |
Delivery Modes
The CMCI register supports multiple delivery modes for flexible error handling:
| Mode | Binary | Description | Supported |
|---|---|---|---|
| Fixed | 000 | Delivers interrupt specified in vector field | ✓ |
| SMI | 010 | System Management Interrupt | ✓ |
| NMI | 100 | Non-Maskable Interrupt | ✓ |
| INIT | 101 | Initialization request | ✗ |
| Reserved | 110 | Not supported | ✗ |
| ExtINT | 111 | External interrupt controller | ✗ |
Note: INIT and ExtINT delivery modes are explicitly not supported for the CMCI register.
Sources: src/regs/lvt/cmci.rs(L5 - L60) src/regs/lvt/cmci.rs(L44 - L45) src/regs/lvt/cmci.rs(L54 - L55)
Register Field Comparison
flowchart TD
subgraph subGraph2["CMCI_Specific[LVT CMCI Specific]"]
CMCI_RESERVED["Reserved0 (Bit 11)1 Reserved Bit"]
CMCI_DELIVERY["DeliveryMode (Bits 10-8)Fixed/SMI/NMI Support"]
end
subgraph subGraph1["Error_Specific[LVT Error Specific]"]
ERROR_RESERVED["Reserved0 (Bits 11-8)4 Reserved Bits"]
ERROR_IMPLICIT["Implicit Fixed ModeNo DeliveryMode Field"]
end
subgraph subGraph0["Common_Fields[Common LVT Fields]"]
MASK_FIELD["Mask (Bit 16)0=NotMasked, 1=Masked"]
DELIVERY_STATUS["DeliveryStatus (Bit 12)0=Idle, 1=SendPending"]
VECTOR_FIELD["Vector (Bits 7-0)Interrupt Vector Number"]
end
DELIVERY_STATUS --> CMCI_RESERVED
DELIVERY_STATUS --> ERROR_RESERVED
MASK_FIELD --> CMCI_RESERVED
MASK_FIELD --> ERROR_RESERVED
VECTOR_FIELD --> CMCI_DELIVERY
VECTOR_FIELD --> ERROR_IMPLICIT
Sources: src/regs/lvt/error.rs(L10 - L31) src/regs/lvt/cmci.rs(L10 - L58)
Error Handling Workflow
The error handling LVT registers integrate into the broader APIC interrupt delivery system to handle different categories of errors:
sequenceDiagram
participant APICHardware as "APIC Hardware"
participant LVT_ERRORRegister as "LVT_ERROR Register"
participant LVT_CMCIRegister as "LVT_CMCI Register"
participant ProcessorCore as "Processor Core"
Note over APICHardware,ProcessorCore: Internal Error Detection
APICHardware ->> LVT_ERRORRegister: Internal error detected
LVT_ERRORRegister ->> LVT_ERRORRegister: Check Mask bit
alt Error not masked
LVT_ERRORRegister ->> ProcessorCore: Generate Fixed mode interrupt
LVT_ERRORRegister ->> LVT_ERRORRegister: Set DeliveryStatus=SendPending
ProcessorCore ->> LVT_ERRORRegister: Accept interrupt
LVT_ERRORRegister ->> LVT_ERRORRegister: Set DeliveryStatus=Idle
else Error masked
LVT_ERRORRegister ->> LVT_ERRORRegister: Suppress interrupt
end
Note over APICHardware,ProcessorCore: Corrected Machine Check
APICHardware ->> LVT_CMCIRegister: Corrected error threshold reached
LVT_CMCIRegister ->> LVT_CMCIRegister: Check Mask bit and DeliveryMode
alt CMCI not masked
alt DeliveryMode=Fixed
LVT_CMCIRegister ->> ProcessorCore: Generate vector interrupt
else DeliveryMode=SMI
LVT_CMCIRegister ->> ProcessorCore: Generate SMI
else DeliveryMode=NMI
LVT_CMCIRegister ->> ProcessorCore: Generate NMI
end
LVT_CMCIRegister ->> LVT_CMCIRegister: Set DeliveryStatus=SendPending
ProcessorCore ->> LVT_CMCIRegister: Accept interrupt
LVT_CMCIRegister ->> LVT_CMCIRegister: Set DeliveryStatus=Idle
else CMCI masked
LVT_CMCIRegister ->> LVT_CMCIRegister: Suppress interrupt
end
Sources: src/regs/lvt/error.rs(L18 - L27) src/regs/lvt/cmci.rs(L18 - L27) src/regs/lvt/cmci.rs(L32 - L56)
Register Type Definitions
Both registers provide dual access patterns through the tock-registers framework:
- MMIO Types:
LvtErrorRegisterMmioandLvtCmciRegisterMmiofor direct hardware access - Local Copy Types:
LvtErrorRegisterLocalandLvtCmciRegisterLocalfor cached register copies
The local copy types enable efficient register manipulation without volatile MMIO operations for each access, which is particularly useful in virtualization scenarios where register state is maintained in memory.
Sources: src/regs/lvt/error.rs(L37 - L44) src/regs/lvt/cmci.rs(L66 - L73)
Control and Status Registers
Relevant source files
This document covers the non-LVT (Local Vector Table) control and status registers in the virtual Local APIC implementation. These registers handle APIC identification, interrupt state management, inter-processor communication, and various control functions. For information about Local Vector Table registers, see LVT System.
The control and status registers are defined as fields in the LocalAPICRegs struct and accessed through the VirtualApicRegs virtualization layer. These registers implement the core APIC functionality beyond interrupt vector configuration.
Register Categories Overview
The control and status registers can be organized into several functional categories based on their roles in the APIC system:
Register Categories and Memory Layout
Sources: src/regs/mod.rs(L13 - L104)
Spurious Interrupt Vector Register
The SVR register is the most complex control register, implemented with detailed bitfield definitions. It controls APIC software enable/disable, spurious vector configuration, and various interrupt handling behaviors.
SVR Register Bitfield Structure
flowchart TD
subgraph subGraph0["SPURIOUS_INTERRUPT_VECTOR Bitfields"]
RESERVED2["Reserved1Bits 13-3119 bits"]
EOI_SUPPRESS["EOIBroadcastSuppressionBit 121 bit0: Disabled, 1: Enabled"]
RESERVED1["Reserved0Bits 10-112 bits"]
FOCUS_CHECK["FocusProcessorCheckingBit 91 bit0: Enabled, 1: Disabled"]
APIC_ENABLE["APICSoftwareEnableDisableBit 81 bit0: Disabled, 1: Enabled"]
SPURIOUS_VEC["SPURIOUS_VECTORBits 0-78 bitsVector number"]
end
APIC_ENABLE --> SPURIOUS_VEC
EOI_SUPPRESS --> RESERVED1
FOCUS_CHECK --> APIC_ENABLE
RESERVED1 --> FOCUS_CHECK
RESERVED2 --> EOI_SUPPRESS
The register provides two type aliases for different access patterns:
SpuriousInterruptVectorRegisterMmio: Direct MMIO access viaReadWrite<u32, SPURIOUS_INTERRUPT_VECTOR::Register>SpuriousInterruptVectorRegisterLocal: Cached local copy viaLocalRegisterCopy<u32, SPURIOUS_INTERRUPT_VECTOR::Register>
Sources: src/regs/svr.rs(L1 - L71) src/regs/mod.rs(L44 - L45)
Control and Identification Registers
These registers provide basic APIC identification and priority control functionality:
| Register | Offset | Type | Purpose |
|---|---|---|---|
| ID | 0x20 | ReadWrite | Local APIC ID for processor identification |
| VERSION | 0x30 | ReadOnly | APIC version information and capabilities |
| TPR | 0x80 | ReadWrite | Task Priority Register - current task priority level |
| APR | 0x90 | ReadOnly | Arbitration Priority Register - for bus arbitration |
| PPR | 0xA0 | ReadOnly | Processor Priority Register - combined priority level |
Priority Register Relationships
Sources: src/regs/mod.rs(L17 - L30)
Interrupt State Registers
The interrupt state is maintained in three 256-bit register arrays, each consisting of 8 × 128-bit entries covering the full 256 interrupt vector space:
| Register Array | Offset Range | Type | Purpose |
|---|---|---|---|
| ISR | 0x100-0x170 | [ReadOnly | In-Service Register - currently serviced interrupts |
| TMR | 0x180-0x1F0 | [ReadOnly | Trigger Mode Register - level vs edge triggered |
| IRR | 0x200-0x270 | [ReadOnly | Interrupt Request Register - pending interrupts |
Interrupt State Array Layout
The IRR uses a specific bit mapping where bit x is located at bit position (x & 1FH) at offset (200H | ((x & E0H) >> 1)), and only the low 4 bytes of each 16-byte field are used by the processor.
Sources: src/regs/mod.rs(L47 - L60)
Communication Registers
These registers handle inter-processor communication and interrupt acknowledgment:
| Register | Offset | Type | Purpose |
|---|---|---|---|
| EOI | 0xB0 | WriteOnly | End of Interrupt - signal interrupt completion |
| RRD | 0xC0 | ReadOnly | Remote Read Register - remote APIC access |
| LDR | 0xD0 | ReadWrite | Logical Destination Register - logical addressing |
| DFR | 0xE0 | ReadWrite | Destination Format Register - addressing mode |
| ICR_LO | 0x300 | ReadWrite | Interrupt Command Register Low - IPI control |
| ICR_HI | 0x310 | ReadWrite | Interrupt Command Register High - destination |
| SELF_IPI | 0x3F0 | WriteOnly | Self IPI Register - self-directed interrupts |
Inter-Processor Communication Flow
sequenceDiagram
participant ProcessorA as "Processor A"
participant ICR_LO0x300 as "ICR_LO (0x300)"
participant ICR_HI0x310 as "ICR_HI (0x310)"
participant ProcessorB as "Processor B"
participant EOI0xB0 as "EOI (0xB0)"
participant SELF_IPI as SELF_IPI
Note over ProcessorA,ProcessorB: Inter-Processor Interrupt Sequence
ProcessorA ->> ICR_HI0x310: Write destination processor ID
ProcessorA ->> ICR_LO0x300: Write vector + delivery mode + trigger
ICR_LO0x300 ->> ProcessorB: Send IPI
ProcessorB ->> ProcessorB: Handle interrupt
ProcessorB ->> EOI0xB0: Write to signal completion
Note over ProcessorA,EOI0xB0: Self-IPI Alternative
ProcessorA ->> SELF_IPI: Write vector for self-interrupt
Sources: src/regs/mod.rs(L32 - L42) src/regs/mod.rs(L67 - L70) src/regs/mod.rs(L99 - L100)
Timer Control Registers
The timer subsystem uses dedicated control registers separate from the LVT Timer register:
| Register | Offset | Type | Purpose |
|---|---|---|---|
| ICR_TIMER | 0x380 | ReadWrite | Initial Count Register - timer start value |
| CCR_TIMER | 0x390 | ReadOnly | Current Count Register - current timer value |
| DCR_TIMER | 0x3E0 | ReadWrite | Divide Configuration Register - timer frequency |
Timer Control Register Interaction
Sources: src/regs/mod.rs(L90 - L97)
Error Status Register
The Error Status Register tracks various APIC error conditions:
| Register | Offset | Type | Purpose |
|---|---|---|---|
| ESR | 0x280 | ReadWrite | Error Status Register - APIC error flags |
This register works in conjunction with the LVT Error register to handle and report APIC internal errors.
Sources: src/regs/mod.rs(L61 - L62)
Register Access Patterns
All control and status registers are accessed through the same virtualization infrastructure as LVT registers, supporting both xAPIC MMIO and x2APIC MSR access modes:
Register Access Flow
Sources: src/regs/mod.rs(L13 - L104)
Development and Project Management
Relevant source files
This page documents the development workflow, continuous integration pipeline, build configuration, and testing procedures for the x86_vlapic crate. It covers the automated processes that ensure code quality, build consistency, and documentation deployment.
For information about the core system architecture and components, see Core Architecture. For details about the register system implementation, see Register System.
CI/CD Pipeline Overview
The x86_vlapic project uses GitHub Actions for continuous integration and deployment. The pipeline is designed to ensure code quality, build consistency across targets, and automated documentation deployment.
Pipeline Architecture
flowchart TD
subgraph subGraph3["Documentation Pipeline"]
DOC_BUILD["cargo doc --no-deps --all-features"]
DOC_INDEX["Generate index.html redirect"]
DEPLOY["Deploy to gh-pages branch"]
end
subgraph subGraph2["Code Quality Gates"]
FORMAT["cargo fmt --all --check"]
CLIPPY["cargo clippy --all-features"]
BUILD["cargo build --all-features"]
TEST["cargo test (conditional)"]
end
subgraph subGraph1["CI Job Matrix"]
MATRIX["Matrix Strategy"]
RUST_NIGHTLY["rust-toolchain: nightly"]
TARGET_X86["targets: x86_64-unknown-none"]
end
subgraph subGraph0["Trigger Events"]
PUSH["Push to Repository"]
PR["Pull Request"]
end
BUILD --> TEST
CLIPPY --> BUILD
DOC_BUILD --> DOC_INDEX
DOC_INDEX --> DEPLOY
FORMAT --> CLIPPY
MATRIX --> RUST_NIGHTLY
MATRIX --> TARGET_X86
PR --> MATRIX
PUSH --> MATRIX
RUST_NIGHTLY --> DOC_BUILD
RUST_NIGHTLY --> FORMAT
Sources: .github/workflows/ci.yml(L1 - L56)
Toolchain Configuration
The project requires specific Rust toolchain configuration to support the bare-metal x86 target:
| Component | Configuration | Purpose |
|---|---|---|
| Toolchain | nightly | Required for no_std features and advanced compiler options |
| Target | x86_64-unknown-none | Bare-metal x86_64 target for hypervisor environments |
| Components | rust-src | Source code for core library compilation |
| Components | clippy | Linting and code analysis |
| Components | rustfmt | Code formatting enforcement |
Sources: .github/workflows/ci.yml(L11 - L19)
Build Process and Quality Checks
Code Quality Workflow
sequenceDiagram
participant Developer as "Developer"
participant Repository as "Repository"
participant CIPipeline as "CI Pipeline"
participant GitHubPages as "GitHub Pages"
Developer ->> Repository: "git push / create PR"
Repository ->> CIPipeline: "Trigger workflow"
Note over CIPipeline: Setup Phase
CIPipeline ->> CIPipeline: "Setup Rust nightly toolchain"
CIPipeline ->> CIPipeline: "Install components: rust-src, clippy, rustfmt"
CIPipeline ->> CIPipeline: "Add target: x86_64-unknown-none"
Note over CIPipeline: Quality Gates
CIPipeline ->> CIPipeline: "cargo fmt --all --check"
Note over GitHubPages,CIPipeline: Fails if code not formatted
CIPipeline ->> CIPipeline: "cargo clippy --target x86_64-unknown-none --all-features"
Note over GitHubPages,CIPipeline: Allows clippy::new_without_default
CIPipeline ->> CIPipeline: "cargo build --target x86_64-unknown-none --all-features"
Note over GitHubPages,CIPipeline: Must build successfully
alt Target is x86_64-unknown-linux-gnu
CIPipeline ->> CIPipeline: "cargo test --target x86_64-unknown-linux-gnu"
Note over GitHubPages,CIPipeline: Unit tests with --nocapture
end
Note over CIPipeline: Documentation Pipeline (main branch only)
CIPipeline ->> CIPipeline: "cargo doc --no-deps --all-features"
CIPipeline ->> CIPipeline: "Generate index.html redirect"
CIPipeline ->> GitHubPages: "Deploy to gh-pages branch"
Sources: .github/workflows/ci.yml(L20 - L31) .github/workflows/ci.yml(L44 - L55)
Linting Configuration
The project uses specific Clippy configuration to balance code quality with practical considerations:
cargo clippy --target x86_64-unknown-none --all-features -- -A clippy::new_without_default
The -A clippy::new_without_default flag allows new() methods without requiring Default implementation, which is common in device driver patterns.
Sources: .github/workflows/ci.yml(L25)
Documentation Management
Documentation Build Process
The documentation pipeline includes special configuration for comprehensive API documentation:
| Configuration | Value | Purpose |
|---|---|---|
| RUSTDOCFLAGS | -D rustdoc::broken_intra_doc_links | Fail on broken internal links |
| RUSTDOCFLAGS | -D missing-docs | Require documentation for all public items |
| Build flags | --no-deps --all-features | Generate docs only for this crate with all features |
Sources: .github/workflows/ci.yml(L40) .github/workflows/ci.yml(L47)
Documentation Deployment Strategy
flowchart TD
subgraph subGraph2["Deployment Conditions"]
MAIN_ONLY["Deploy only from main branch"]
CONTINUE_ERROR["Continue on error for non-main"]
end
subgraph subGraph1["Documentation Actions"]
BUILD["cargo doc build"]
REDIRECT["Generate index.html redirect"]
DEPLOY["Single-commit deployment"]
end
subgraph subGraph0["Branch Strategy"]
MAIN["main branch"]
GHPAGES["gh-pages branch"]
PR["Pull Request"]
end
BUILD --> REDIRECT
DEPLOY --> GHPAGES
MAIN --> BUILD
MAIN_ONLY --> DEPLOY
PR --> BUILD
PR --> CONTINUE_ERROR
REDIRECT --> MAIN_ONLY
Sources: .github/workflows/ci.yml(L45) .github/workflows/ci.yml(L50 - L55)
Testing Framework
Test Execution Strategy
The testing approach is target-specific due to the bare-metal nature of the codebase:
flowchart TD
subgraph subGraph2["Test Limitations"]
NO_STD["no_std environment"]
BARE_METAL["Bare-metal target constraints"]
HOST_ONLY["Host-only test execution"]
end
subgraph subGraph1["Test Execution"]
UNIT_TESTS["cargo test --target x86_64-unknown-linux-gnu"]
NOCAPTURE["--nocapture flag"]
CONDITIONAL["Conditional execution"]
end
subgraph subGraph0["Test Targets"]
X86_NONE["x86_64-unknown-none(Build Only)"]
X86_LINUX["x86_64-unknown-linux-gnu(Build + Test)"]
end
BARE_METAL --> HOST_ONLY
HOST_ONLY --> CONDITIONAL
UNIT_TESTS --> CONDITIONAL
UNIT_TESTS --> NOCAPTURE
X86_LINUX --> UNIT_TESTS
X86_NONE --> BARE_METAL
Sources: .github/workflows/ci.yml(L28 - L30)
Development Workflow
Recommended Development Process
| Step | Command | Purpose |
|---|---|---|
| 1. Format | cargo fmt --all | Ensure consistent code formatting |
| 2. Lint | cargo clippy --target x86_64-unknown-none --all-features | Check for common issues |
| 3. Build | cargo build --target x86_64-unknown-none --all-features | Verify compilation |
| 4. Test | cargo test(on host) | Run unit tests if available |
| 5. Document | cargo doc --all-features | Generate and verify documentation |
Repository Management
The project follows standard Git practices with specific CI integration:
flowchart TD
subgraph subGraph2["Quality Gates"]
FORMAT_CHECK["Format Check"]
CLIPPY_CHECK["Clippy Check"]
BUILD_CHECK["Build Check"]
MERGE["Merge to main"]
end
subgraph subGraph1["Remote Integration"]
PUSH["git push"]
PR["Create PR"]
CI_CHECK["CI Pipeline"]
end
subgraph subGraph0["Local Development"]
EDIT["Edit Code"]
FORMAT["cargo fmt"]
TEST["cargo test"]
end
BUILD_CHECK --> MERGE
CI_CHECK --> FORMAT_CHECK
CLIPPY_CHECK --> BUILD_CHECK
EDIT --> FORMAT
FORMAT --> TEST
FORMAT_CHECK --> CLIPPY_CHECK
PR --> CI_CHECK
PUSH --> CI_CHECK
TEST --> PUSH
Sources: .github/workflows/ci.yml(L3) .gitignore(L1 - L5)
Build Artifacts and Exclusions
The .gitignore configuration excludes standard Rust build artifacts and development tools:
/target- Cargo build output directory/.vscode- Visual Studio Code workspace files.DS_Store- macOS system filesCargo.lock- Lock file (typically excluded for libraries)
Sources: .gitignore(L1 - L5)
This configuration ensures that only source code and essential project files are tracked in version control, while build artifacts and editor-specific files are excluded.
Overview
Relevant source files
Purpose and Scope
The arm_gicv2 crate provides a hardware abstraction layer for the ARM Generic Interrupt Controller version 2 (GICv2), enabling safe and efficient interrupt management in embedded systems, bare-metal applications, and hypervisors. This library offers a #![no_std] compatible interface for configuring, routing, and handling interrupts across ARM-based multi-core systems.
This overview covers the crate's fundamental architecture and interrupt classification system. For detailed information about the core interrupt handling components, see Core Architecture. For comprehensive interrupt type management, see Interrupt Types and Management. For register-level implementation details, see Register Interface.
Sources: src/lib.rs(L1 - L117) Cargo.toml(L1 - L19) README.md(L1 - L10)
Core Components Architecture
The crate exposes two primary hardware abstraction components that work together to manage the complete interrupt processing pipeline:
flowchart TD
subgraph subGraph2["Interrupt Classification"]
SGI_RANGE["SGI_RANGE (0..16)"]
PPI_RANGE["PPI_RANGE (16..32)"]
SPI_RANGE["SPI_RANGE (32..1020)"]
TRANSLATE["translate_irq()"]
end
subgraph subGraph1["arm_gicv2 Abstractions"]
GIC_DIST["GicDistributor"]
GIC_CPU["GicCpuInterface"]
LIB_RS["lib.rs exports"]
end
subgraph subGraph0["Hardware Layer"]
HW_DIST["GIC Distributor Hardware"]
HW_CPU["CPU Interface Hardware"]
ARM_CORES["ARM CPU Cores"]
end
GIC_DIST --> GIC_CPU
HW_CPU --> ARM_CORES
HW_CPU --> GIC_CPU
HW_DIST --> GIC_DIST
LIB_RS --> GIC_CPU
LIB_RS --> GIC_DIST
PPI_RANGE --> TRANSLATE
SGI_RANGE --> TRANSLATE
SPI_RANGE --> TRANSLATE
TRANSLATE --> GIC_CPU
TRANSLATE --> GIC_DIST
The GicDistributor manages system-wide interrupt configuration, priority handling, and routing decisions, while the GicCpuInterface handles per-CPU interrupt acknowledgment and completion signaling. These components are exported from the main library interface defined in src/lib.rs(L12)
Sources: src/lib.rs(L10 - L12)
Interrupt Classification and Translation System
The crate implements a comprehensive interrupt classification system that categorizes all GIC interrupts into three distinct types, each with specific characteristics and use cases:
flowchart TD
subgraph subGraph2["Translation Function"]
TRANSLATE_IRQ["translate_irq(id, int_type)-> Option"]
end
subgraph subGraph1["Interrupt ID Ranges"]
SGI_IDS["SGI_RANGE: 0..16Software Generated"]
PPI_IDS["PPI_RANGE: 16..32Private Peripheral"]
SPI_IDS["SPI_RANGE: 32..1020Shared Peripheral"]
end
subgraph subGraph0["InterruptType Enum"]
SGI["InterruptType::SGI"]
PPI["InterruptType::PPI"]
SPI["InterruptType::SPI"]
end
GIC_INTID["GIC INTID"]
PPI --> PPI_IDS
PPI_IDS --> TRANSLATE_IRQ
SGI --> SGI_IDS
SGI_IDS --> TRANSLATE_IRQ
SPI --> SPI_IDS
SPI_IDS --> TRANSLATE_IRQ
TRANSLATE_IRQ --> GIC_INTID
The translate_irq function provides safe conversion from logical interrupt IDs to GIC interrupt identifiers, ensuring type safety and preventing invalid interrupt configurations. This function implements bounds checking for each interrupt type and returns None for invalid combinations.
| Interrupt Type | ID Range | Constant | Purpose |
|---|---|---|---|
| SGI | 0-15 | SGI_RANGE | Inter-processor communication |
| PPI | 16-31 | PPI_RANGE | Single CPU peripheral interrupts |
| SPI | 32-1019 | SPI_RANGE | Multi-CPU peripheral interrupts |
Sources: src/lib.rs(L14 - L29) src/lib.rs(L74 - L89) src/lib.rs(L91 - L116)
System Integration Context
The crate integrates into the broader embedded systems ecosystem through several key characteristics:
flowchart TD
subgraph subGraph2["Target Applications"]
EMBEDDED["Embedded Systems"]
BARE_METAL["Bare Metal Applications"]
HYPERVISORS["Hypervisors/VMMs"]
ARCEOS["ArceOS Operating System"]
end
subgraph subGraph1["arm_gicv2 Crate"]
NO_STD["#![no_std] compatibility"]
EL2_FEATURE["el2 feature flagHypervisor support"]
CORE_TYPES["Core interrupt typesTriggerMode, InterruptType"]
end
subgraph Dependencies["Dependencies"]
TOCK_REGS["tock-registers 0.8Hardware register abstractions"]
end
CORE_TYPES --> NO_STD
EL2_FEATURE --> NO_STD
NO_STD --> ARCEOS
NO_STD --> BARE_METAL
NO_STD --> EMBEDDED
NO_STD --> HYPERVISORS
TOCK_REGS --> NO_STD
The crate supports both basic interrupt management and advanced hypervisor features through the optional el2 feature flag. The TriggerMode enum supports both edge-triggered and level-sensitive interrupt configurations, providing flexibility for different hardware integration scenarios.
Key system constants include:
GIC_MAX_IRQ: Maximum of 1024 supported interruptsGIC_CONFIG_BITS: 2 bits per interrupt for trigger mode configurationGICC_CTLR_EN_BITandGICD_CTLR_EN_BIT: Control register enable bits
Sources: src/lib.rs(L1 - L3) src/lib.rs(L31 - L72) Cargo.toml(L14 - L18)
Core Architecture
Relevant source files
Purpose and Scope
This document covers the fundamental architectural design of the arm_gicv2 crate, focusing on the two primary hardware abstraction components and their interaction patterns. The architecture centers around providing safe, type-safe access to ARM GICv2 hardware through two main abstractions: system-wide interrupt distribution and per-CPU interrupt handling.
For detailed information about specific interrupt types and classification, see Interrupt Types and Management. For register-level implementation details, see Register Interface.
Architectural Overview
The arm_gicv2 crate implements a two-tier architecture that mirrors the ARM GICv2 hardware design. The core architecture consists of two primary components that work in coordination to manage interrupt processing across the system.
Component Architecture Diagram
flowchart TD
subgraph subGraph3["Hardware Layer"]
HW_DIST["GIC Distributor Hardware"]
HW_CPU["CPU Interface Hardware"]
end
subgraph subGraph2["Interrupt Classification"]
SGI_RANGE["SGI_RANGE (0..16)Software Generated"]
PPI_RANGE["PPI_RANGE (16..32)Private Peripheral"]
SPI_RANGE["SPI_RANGE (32..1020)Shared Peripheral"]
translate_irq["translate_irq()Type conversion"]
end
subgraph subGraph1["Register Abstractions"]
GicDistributorRegs["GicDistributorRegs(memory-mapped registers)"]
GicCpuInterfaceRegs["GicCpuInterfaceRegs(CPU interface registers)"]
end
subgraph subGraph0["Hardware Abstraction Layer"]
GicDistributor["GicDistributor(system-wide control)"]
GicCpuInterface["GicCpuInterface(per-CPU handling)"]
end
GicCpuInterface --> GicCpuInterfaceRegs
GicCpuInterfaceRegs --> HW_CPU
GicDistributor --> GicCpuInterface
GicDistributor --> GicDistributorRegs
GicDistributorRegs --> HW_DIST
PPI_RANGE --> translate_irq
SGI_RANGE --> translate_irq
SPI_RANGE --> translate_irq
translate_irq --> GicCpuInterface
translate_irq --> GicDistributor
Sources: src/gic_v2.rs(L92 - L131) src/lib.rs(L12 - L29) src/lib.rs(L91 - L116)
Core Components
GicDistributor Component
The GicDistributor struct serves as the system-wide interrupt controller, responsible for global interrupt management and routing decisions. It encapsulates a pointer to memory-mapped distributor registers and maintains the maximum interrupt count.
Key Responsibilities:
- Global interrupt enable/disable control via
set_enable() - Priority management through
set_priority()andget_priority() - CPU targeting via
set_target_cpu()andget_target_cpu() - Interrupt configuration using
configure_interrupt() - Software-generated interrupt (SGI) distribution via
send_sgi()family of methods - System initialization through
init()
GicCpuInterface Component
The GicCpuInterface struct handles per-CPU interrupt processing, managing the interface between the distributor and individual processor cores. Each CPU in the system has its own CPU interface instance.
Key Responsibilities:
- Interrupt acknowledgment via
iar()(Interrupt Acknowledge Register) - End-of-interrupt signaling through
eoi()(End of Interrupt Register) - Interrupt deactivation using
dir()(Deactivate Interrupt Register, EL2 feature) - Priority masking and control register management
- Complete interrupt handling workflow via
handle_irq()
Sources: src/gic_v2.rs(L92 - L136) src/gic_v2.rs(L376 - L479)
Component Interaction Flow
The two core components work together to process interrupts from generation to completion. The following diagram illustrates the interaction patterns and method calls involved in typical interrupt processing scenarios.
Interrupt Processing Coordination
sequenceDiagram
participant HardwareSource as "Hardware Source"
participant GicDistributor as "GicDistributor"
participant GicCpuInterface as "GicCpuInterface"
participant InterruptHandler as "Interrupt Handler"
Note over HardwareSource,InterruptHandler: "Interrupt Configuration Phase"
GicDistributor ->> GicDistributor: "init()"
GicCpuInterface ->> GicCpuInterface: "init()"
GicDistributor ->> GicDistributor: "set_enable(vector, true)"
GicDistributor ->> GicDistributor: "set_priority(vector, priority)"
GicDistributor ->> GicDistributor: "set_target_cpu(vector, cpu_mask)"
GicDistributor ->> GicDistributor: "configure_interrupt(vector, TriggerMode)"
Note over HardwareSource,InterruptHandler: "Interrupt Processing Phase"
HardwareSource ->> GicDistributor: "Interrupt Signal"
GicDistributor ->> GicCpuInterface: "Route to Target CPU"
GicCpuInterface ->> GicCpuInterface: "iar() -> interrupt_id"
GicCpuInterface ->> InterruptHandler: "Call handler(interrupt_id)"
InterruptHandler ->> GicCpuInterface: "Processing complete"
GicCpuInterface ->> GicCpuInterface: "eoi(iar_value)"
Note over HardwareSource,InterruptHandler: "EL2 Feature: Separated Priority Drop"
alt "EL2 mode
alt enabled"
GicCpuInterface ->> GicCpuInterface: "dir(iar_value)"
end
end
Note over HardwareSource,InterruptHandler: "Software Generated Interrupt"
GicDistributor ->> GicDistributor: "send_sgi(dest_cpu, sgi_num)"
GicDistributor ->> GicCpuInterface: "SGI delivery"
GicCpuInterface ->> GicCpuInterface: "handle_irq(handler)"
Sources: src/gic_v2.rs(L201 - L223) src/gic_v2.rs(L443 - L459) src/gic_v2.rs(L342 - L373) src/gic_v2.rs(L461 - L478)
Register Architecture Integration
The core components interact with hardware through structured register abstractions. Each component maintains a base pointer to its respective register structure, providing type-safe access to memory-mapped hardware registers.
Register Structure Mapping
flowchart TD
subgraph subGraph3["GicCpuInterfaceRegs Fields"]
CPU_CTLR["CTLR(Control)"]
IAR["IAR(Acknowledge)"]
EOIR["EOIR(End of Interrupt)"]
DIR["DIR(Deactivate)"]
PMR["PMR(Priority Mask)"]
end
subgraph subGraph2["GicCpuInterface Methods"]
handle_irq["handle_irq()"]
iar["iar()"]
eoi["eoi()"]
dir["dir()"]
cpu_init["init()"]
end
subgraph subGraph1["GicDistributorRegs Fields"]
CTLR["CTLR(Control)"]
ISENABLER["ISENABLER(Set-Enable)"]
ICENABLER["ICENABLER(Clear-Enable)"]
SGIR["SGIR(SGI Register)"]
IPRIORITYR["IPRIORITYR(Priority)"]
ITARGETSR["ITARGETSR(Target)"]
ICFGR["ICFGR(Configuration)"]
end
subgraph subGraph0["GicDistributor Methods"]
init["init()"]
set_enable["set_enable()"]
send_sgi["send_sgi()"]
set_priority["set_priority()"]
configure_interrupt["configure_interrupt()"]
end
configure_interrupt --> ICFGR
cpu_init --> CPU_CTLR
cpu_init --> PMR
dir --> DIR
eoi --> EOIR
handle_irq --> DIR
handle_irq --> EOIR
handle_irq --> IAR
iar --> IAR
init --> CTLR
send_sgi --> SGIR
set_enable --> ICENABLER
set_enable --> ISENABLER
set_priority --> IPRIORITYR
Sources: src/gic_v2.rs(L20 - L62) src/gic_v2.rs(L64 - L90) src/gic_v2.rs(L147 - L149) src/gic_v2.rs(L384 - L386)
System State Management
The architecture maintains both global system state through the distributor and per-CPU state through individual CPU interfaces. The distributor manages the max_irqs field to track the hardware-supported interrupt count, while CPU interfaces remain stateless, relying on hardware register state.
Initialization and Configuration
| Component | Initialization Method | Key Configuration |
|---|---|---|
| GicDistributor | init() | Disable all interrupts, set SPI targets to CPU 0, configure edge-triggered mode, enable GICD |
| GicCpuInterface | init() | Set priority mask to maximum, enable CPU interface, configure EL2 mode if feature enabled |
The architecture ensures thread safety through Send and Sync implementations for both core components, allowing safe concurrent access across multiple threads or CPU cores.
Sources: src/gic_v2.rs(L132 - L136) src/gic_v2.rs(L342 - L373) src/gic_v2.rs(L461 - L478)
GIC Distributor Component
Relevant source files
The GIC Distributor Component provides system-wide interrupt management and routing functionality for ARM GICv2 systems. This component handles global interrupt configuration, priority management, CPU targeting, and distribution of interrupts to CPU interfaces. It serves as the central coordination point for all interrupt activity in the system.
For information about per-CPU interrupt handling and acknowledgment, see CPU Interface Component. For details on the complete interrupt flow, see Interrupt Processing Pipeline.
Overview and Architecture
The GicDistributor struct represents the hardware GIC Distributor block and provides a safe Rust abstraction over the memory-mapped distributor registers. The distributor performs interrupt prioritization and distribution to CPU interface blocks connected to processors in the system.
flowchart TD
subgraph subGraph2["Core Methods"]
INIT["init()"]
CONFIG_INT["configure_interrupt()"]
SET_ENABLE["set_enable()"]
SET_PRIORITY["set_priority()"]
SET_TARGET["set_target_cpu()"]
SEND_SGI["send_sgi()"]
end
subgraph subGraph1["Hardware Registers"]
CTLR["CTLR - Control"]
TYPER["TYPER - Type Info"]
ISENABLER["ISENABLER - Enable Set"]
ICENABLER["ICENABLER - Enable Clear"]
IPRIORITYR["IPRIORITYR - Priority"]
ITARGETSR["ITARGETSR - Target CPU"]
ICFGR["ICFGR - Configuration"]
SGIR["SGIR - Software Generated"]
end
subgraph subGraph0["GicDistributor Structure"]
DIST_STRUCT["GicDistributor"]
BASE_PTR["base: NonNull<GicDistributorRegs>"]
MAX_IRQS["max_irqs: usize"]
end
BASE_PTR --> CTLR
BASE_PTR --> ICENABLER
BASE_PTR --> ICFGR
BASE_PTR --> IPRIORITYR
BASE_PTR --> ISENABLER
BASE_PTR --> ITARGETSR
BASE_PTR --> SGIR
BASE_PTR --> TYPER
CONFIG_INT --> ICFGR
DIST_STRUCT --> BASE_PTR
DIST_STRUCT --> MAX_IRQS
INIT --> CTLR
SEND_SGI --> SGIR
SET_ENABLE --> ICENABLER
SET_ENABLE --> ISENABLER
SET_PRIORITY --> IPRIORITYR
SET_TARGET --> ITARGETSR
Sources: src/gic_v2.rs(L110 - L113) src/gic_v2.rs(L20 - L62) src/gic_v2.rs(L138 - L374)
Register Structure and Hardware Interface
The distributor manages a comprehensive set of memory-mapped registers defined in GicDistributorRegs. Each register serves specific interrupt management functions:
| Register Group | Purpose | Key Registers |
|---|---|---|
| Control | Basic distributor control | CTLR,TYPER,IIDR |
| Enable/Disable | Interrupt enable management | ISENABLER,ICENABLER |
| Pending | Interrupt pending state | ISPENDR,ICPENDR |
| Active | Interrupt active state | ISACTIVER,ICACTIVER |
| Priority | Interrupt priority levels | IPRIORITYR |
| Targeting | CPU routing configuration | ITARGETSR |
| Configuration | Trigger mode settings | ICFGR |
| Software Interrupts | SGI generation | SGIR,CPENDSGIR,SPENDSGIR |
flowchart TD
subgraph subGraph1["Register Categories"]
CTRL_REGS["Control RegistersCTLR, TYPER, IIDR"]
ENABLE_REGS["Enable RegistersISENABLER, ICENABLER"]
STATE_REGS["State RegistersISPENDR, ICPENDRISACTIVER, ICACTIVER"]
CONFIG_REGS["Config RegistersIPRIORITYR, ITARGETSRICFGR"]
SGI_REGS["SGI RegistersSGIR, CPENDSGIRSPENDSGIR"]
end
subgraph subGraph0["Register Access Pattern"]
METHOD_CALL["Method Call"]
REGS_FUNC["regs()"]
BASE_PTR["base.as_ref()"]
HW_REGS["Hardware Registers"]
end
BASE_PTR --> HW_REGS
HW_REGS --> CONFIG_REGS
HW_REGS --> CTRL_REGS
HW_REGS --> ENABLE_REGS
HW_REGS --> SGI_REGS
HW_REGS --> STATE_REGS
METHOD_CALL --> REGS_FUNC
REGS_FUNC --> BASE_PTR
Sources: src/gic_v2.rs(L20 - L62) src/gic_v2.rs(L147 - L149)
Core Functionality and Methods
Initialization and Configuration
The init() method performs complete distributor initialization, setting up default configurations for all supported interrupts:
flowchart TD START["init()"] GET_MAX["max_irqs = self.max_irqs()"] DISABLE_ALL["Disable all interruptsICENABLER, ICPENDR"] CHECK_SMP["cpu_num() > 1"] SET_TARGETS["Set SPI targets to CPU 0ITARGETSR"] CONFIG_EDGE["Configure SPIs as edge-triggeredICFGR"] ENABLE_DIST["Enable distributorCTLR |= GICD_CTLR_EN_BIT"] END["Initialization Complete"] CHECK_SMP --> CONFIG_EDGE CHECK_SMP --> SET_TARGETS CONFIG_EDGE --> ENABLE_DIST DISABLE_ALL --> CHECK_SMP ENABLE_DIST --> END GET_MAX --> DISABLE_ALL SET_TARGETS --> CONFIG_EDGE START --> GET_MAX
Sources: src/gic_v2.rs(L348 - L373)
Interrupt Management Operations
The distributor provides comprehensive interrupt control through several key method categories:
| Method Category | Methods | Register Access |
|---|---|---|
| Enable Control | set_enable(),get_enable() | ISENABLER,ICENABLER |
| Priority Management | set_priority(),get_priority() | IPRIORITYR |
| CPU Targeting | set_target_cpu(),get_target_cpu() | ITARGETSR |
| State Management | set_pend(),set_active(),get_state() | ISPENDR,ICPENDR,ISACTIVER,ICACTIVER |
| Configuration | configure_interrupt(),set_icfgr() | ICFGR |
Software Generated Interrupts (SGI)
The distributor provides three SGI targeting modes through dedicated methods:
flowchart TD
subgraph subGraph2["Targeting Modes"]
TO_CPU["ForwardToCPUTargetList"]
ALL_EXCEPT["ForwardToAllExceptRequester"]
TO_SELF["ForwardToRequester"]
end
subgraph subGraph1["SGIR Register Fields"]
TARGET_FILTER["TargetListFilter"]
CPU_TARGET["CPUTargetList"]
SGI_INTID["SGIINTID"]
end
subgraph subGraph0["SGI Methods"]
SEND_SGI["send_sgi(dest_cpu_id, sgi_num)"]
SEND_ALL_EXCEPT["send_sgi_all_except_self(sgi_num)"]
SEND_SELF["send_sgi_to_self(sgi_num)"]
end
SEND_ALL_EXCEPT --> SGI_INTID
SEND_ALL_EXCEPT --> TARGET_FILTER
SEND_SELF --> SGI_INTID
SEND_SELF --> TARGET_FILTER
SEND_SGI --> CPU_TARGET
SEND_SGI --> SGI_INTID
SEND_SGI --> TARGET_FILTER
TARGET_FILTER --> ALL_EXCEPT
TARGET_FILTER --> TO_CPU
TARGET_FILTER --> TO_SELF
Sources: src/gic_v2.rs(L202 - L223) src/regs.rs
System Information and Capabilities
The distributor provides system capability discovery through hardware register queries:
cpu_num(): Returns the number of implemented CPU interfaces fromTYPERregister bits [7:5]max_irqs(): Calculates maximum supported interrupts fromTYPERregister bits [4:0]get_typer(): Returns rawTYPERregister value for detailed configuration analysisget_iidr(): Returns implementation identification fromIIDRregister
Sources: src/gic_v2.rs(L152 - L159) src/gic_v2.rs(L322 - L330)
Thread Safety and Ownership
The GicDistributor implements Send and Sync traits through unsafe implementations, enabling shared access across CPU cores in multi-processor systems. The struct uses NonNull<GicDistributorRegs> for safe hardware register access while maintaining zero-cost abstractions.
Sources: src/gic_v2.rs(L132 - L133) src/gic_v2.rs(L110 - L113)
Integration with System Architecture
The distributor works in coordination with the CPU Interface Component to provide complete interrupt management. It handles global interrupt configuration and routing, while CPU interfaces manage per-processor interrupt delivery and acknowledgment. For detailed information about the interaction between these components, see Interrupt Processing Pipeline and CPU Interface Component.
CPU Interface Component
Relevant source files
This document covers the GicCpuInterface component, which provides per-CPU interrupt handling functionality in the ARM GICv2 implementation. The CPU Interface handles interrupt acknowledgment, priority masking, and end-of-interrupt processing for individual processor cores. For system-wide interrupt distribution and configuration, see GIC Distributor Component. For the complete interrupt processing flow between components, see Interrupt Processing Pipeline.
Architecture Overview
The GicCpuInterface struct provides a Rust abstraction over the GIC CPU Interface hardware registers, enabling safe per-CPU interrupt management. Each CPU core in the system has its own CPU Interface instance that communicates with the shared GIC Distributor.
flowchart TD
subgraph subGraph2["Register Definition"]
GICC_REGS["GicCpuInterfaceRegs struct"]
TOCK_REGS["tock-registers abstractions"]
end
subgraph subGraph1["Software Abstraction"]
GICC_STRUCT["GicCpuInterface struct"]
NEW_METHOD["new() constructor"]
IAR_METHOD["iar() method"]
EOI_METHOD["eoi() method"]
DIR_METHOD["dir() method"]
HANDLE_IRQ["handle_irq() method"]
INIT_METHOD["init() method"]
end
subgraph subGraph0["CPU Interface Hardware"]
GICC_BASE["GIC CPU Interface Base Address"]
CTLR_REG["GICC_CTLR: Control Register"]
IAR_REG["GICC_IAR: Interrupt Acknowledge"]
EOIR_REG["GICC_EOIR: End of Interrupt"]
PMR_REG["GICC_PMR: Priority Mask"]
BPR_REG["GICC_BPR: Binary Point"]
DIR_REG["GICC_DIR: Deactivate (EL2)"]
end
DIR_METHOD --> DIR_REG
EOI_METHOD --> EOIR_REG
GICC_BASE --> BPR_REG
GICC_BASE --> CTLR_REG
GICC_BASE --> DIR_REG
GICC_BASE --> EOIR_REG
GICC_BASE --> IAR_REG
GICC_BASE --> PMR_REG
GICC_REGS --> GICC_STRUCT
GICC_STRUCT --> DIR_METHOD
GICC_STRUCT --> EOI_METHOD
GICC_STRUCT --> HANDLE_IRQ
GICC_STRUCT --> IAR_METHOD
GICC_STRUCT --> INIT_METHOD
GICC_STRUCT --> NEW_METHOD
IAR_METHOD --> IAR_REG
TOCK_REGS --> GICC_REGS
Sources: src/gic_v2.rs(L128 - L130) src/gic_v2.rs(L64 - L90) src/gic_v2.rs(L376 - L479)
Register Interface Structure
The CPU Interface exposes its functionality through memory-mapped registers defined in the GicCpuInterfaceRegs struct. Each register serves a specific purpose in the interrupt handling workflow.
| Register | Offset | Type | Purpose |
|---|---|---|---|
| CTLR | 0x0000 | ReadWrite | Control register for enabling interface and configuring behavior |
| PMR | 0x0004 | ReadWrite | Priority mask register for filtering interrupts by priority |
| BPR | 0x0008 | ReadWrite | Binary point register for priority grouping |
| IAR | 0x000c | ReadOnly | Interrupt acknowledge register returning pending interrupt ID |
| EOIR | 0x0010 | WriteOnly | End of interrupt register for completing interrupt processing |
| RPR | 0x0014 | ReadOnly | Running priority register showing current interrupt priority |
| HPPIR | 0x0018 | ReadOnly | Highest priority pending interrupt register |
| IIDR | 0x00fc | ReadOnly | CPU interface identification register |
| DIR | 0x1000 | WriteOnly | Deactivate interrupt register (EL2 feature) |
Sources: src/gic_v2.rs(L64 - L90)
Core Interrupt Processing Methods
The GicCpuInterface provides several key methods for interrupt processing, each corresponding to specific hardware register operations.
flowchart TD START["Interrupt Arrives at CPU"] IAR_READ["iar() method reads GICC_IAR"] CHECK_SPURIOUS["Is interrupt ID < 1020?"] SPURIOUS["Spurious interrupt (1023)No action taken"] EXTRACT_ID["Extract interrupt vectorvector = iar & 0x3ff"] CALL_HANDLER["Call user-provided handler(vector)"] EOI_WRITE["eoi(iar) writes GICC_EOIR"] EL2_CHECK["EL2 feature enabled?"] DIR_CHECK["EOIMODENS bit set?"] DIR_WRITE["dir(iar) writes GICC_DIR"] COMPLETE["Interrupt processing complete"] CALL_HANDLER --> EOI_WRITE CHECK_SPURIOUS --> EXTRACT_ID CHECK_SPURIOUS --> SPURIOUS DIR_CHECK --> COMPLETE DIR_CHECK --> DIR_WRITE DIR_WRITE --> COMPLETE EL2_CHECK --> COMPLETE EL2_CHECK --> DIR_CHECK EOI_WRITE --> EL2_CHECK EXTRACT_ID --> CALL_HANDLER IAR_READ --> CHECK_SPURIOUS SPURIOUS --> COMPLETE START --> IAR_READ
Sources: src/gic_v2.rs(L443 - L459) src/gic_v2.rs(L394 - L396) src/gic_v2.rs(L406 - L408) src/gic_v2.rs(L416 - L418)
Interrupt Acknowledge Process
The iar() method reads the GICC_IAR register to obtain the interrupt ID of the highest priority pending interrupt. The method returns the full IAR value, which includes both the interrupt ID and the CPU ID for SGIs.
// Example usage pattern
let iar_value = cpu_interface.iar();
let interrupt_id = iar_value & 0x3ff; // Extract interrupt ID from bits [9:0]
A return value of 1023 indicates either a spurious interrupt or that no interrupts are pending.
Sources: src/gic_v2.rs(L394 - L396)
End of Interrupt Processing
The eoi() method writes to the GICC_EOIR register to signal completion of interrupt processing. The value written must be the exact IAR value returned from the acknowledge operation.
In hypervisor configurations (EL2 feature), the EOIR operation only performs priority drop, and a separate dir() call is required to fully deactivate the interrupt when GICC_CTLR_EOIMODENS_BIT is set.
Sources: src/gic_v2.rs(L406 - L408) src/gic_v2.rs(L416 - L418) src/gic_v2.rs(L452 - L455)
High-Level Interrupt Handling
The handle_irq() method provides a convenient wrapper that encapsulates the complete interrupt processing sequence. It takes a closure that handles the actual interrupt processing logic.
flowchart TD IAR_READ["Read IAR register"] VECTOR_EXTRACT["Extract vector ID"] SPURIOUS_CHECK["Vector < 1020?"] HANDLER_CALL["Call handler(vector)"] EOI_CALL["Call eoi(iar)"] EL2_DIR["Conditional dir(iar)if EL2 enabled"] SPURIOUS_END["Handle spurious"] COMPLETE["Complete"] EL2_DIR --> COMPLETE EOI_CALL --> EL2_DIR HANDLER_CALL --> EOI_CALL IAR_READ --> VECTOR_EXTRACT SPURIOUS_CHECK --> HANDLER_CALL SPURIOUS_CHECK --> SPURIOUS_END SPURIOUS_END --> COMPLETE VECTOR_EXTRACT --> SPURIOUS_CHECK
Sources: src/gic_v2.rs(L443 - L459)
Initialization and Configuration
The CPU Interface requires proper initialization through the init() method, which configures the interface for operation.
Standard Initialization (Non-EL2)
For standard operation, initialization sets:
GICC_CTLRregister withGICC_CTLR_EN_BITto enable the interfaceGICC_PMRregister to maximum value (0xFFFFFFFF) to unmask all priority levels
EL2 Hypervisor Initialization
When the el2 feature is enabled, initialization additionally sets:
GICC_CTLR_EOIMODENS_BITin the control register to separate priority drop from interrupt deactivation
This separation allows hypervisors to maintain better control over interrupt lifecycle and support virtual interrupt injection.
| Configuration | GICC_CTLR Value | PMR Value | Purpose |
|---|---|---|---|
| Standard | GICC_CTLR_EN_BIT | 0xFFFFFFFF | Basic interrupt enabling |
| EL2 Hypervisor | GICC_CTLR_EN_BIT | GICC_CTLR_EOIMODENS_BIT | 0xFFFFFFFF | Hypervisor with split EOI/deactivation |
Sources: src/gic_v2.rs(L466 - L478)
Control Register Management
The CPU Interface provides direct access to the GICC_CTLR register through getter and setter methods, allowing fine-grained control over interface behavior.
Control Register Fields
The control register manages several aspects of CPU interface operation:
- Interrupt group enabling
- Signal bypass configuration
- Binary point register selection
- Priority drop and deactivation separation (EL2)
// Example control register manipulation
let current_ctlr = cpu_interface.get_ctlr();
let modified_ctlr = current_ctlr | CUSTOM_CONTROL_BITS;
cpu_interface.set_ctlr(modified_ctlr);
Sources: src/gic_v2.rs(L424 - L433)
Thread Safety and Memory Management
The GicCpuInterface struct implements both Send and Sync traits, making it safe to share across threads. This is critical for multi-core systems where each CPU core needs access to its own CPU Interface instance.
The struct uses NonNull<GicCpuInterfaceRegs> for memory safety while maintaining the ability to perform direct hardware register access through memory-mapped I/O.
Sources: src/gic_v2.rs(L135 - L136) src/gic_v2.rs(L128 - L130)
Interrupt Processing Pipeline
Relevant source files
This document describes the complete interrupt processing pipeline in the ARM GICv2 implementation, from interrupt generation through final completion. It covers the coordination between the GicDistributor and GicCpuInterface components and the flow of control through the hardware abstraction layer.
For details about the individual components, see GIC Distributor Component and CPU Interface Component. For information about interrupt classification, see Interrupt Classification System.
Pipeline Overview
The interrupt processing pipeline consists of five main stages that coordinate between hardware interrupt sources, the GIC Distributor, CPU interfaces, and software interrupt handlers.
flowchart TD HW_SOURCE["Hardware Peripheral"] DIST_RECEIVE["GicDistributor receives interrupt"] SW_SGI["Software SGI"] DIST_CHECK["Distributor Processing:• Check enable/disable• Evaluate priority• Select target CPU• Check trigger mode"] CPU_FORWARD["Forward to GicCpuInterface"] CPU_SIGNAL["CPU Interface signals processor"] PROC_ACK["Processor acknowledges via IAR read"] HANDLER_EXEC["Interrupt handler execution"] PROC_EOI["Processor signals completion via EOIR"] OPTIONAL_DIR["Optional: Deactivation via DIR(EL2 mode only)"] COMPLETE["Interrupt processing complete"] CPU_FORWARD --> CPU_SIGNAL CPU_SIGNAL --> PROC_ACK DIST_CHECK --> CPU_FORWARD DIST_RECEIVE --> DIST_CHECK HANDLER_EXEC --> PROC_EOI HW_SOURCE --> DIST_RECEIVE OPTIONAL_DIR --> COMPLETE PROC_ACK --> HANDLER_EXEC PROC_EOI --> OPTIONAL_DIR SW_SGI --> DIST_RECEIVE
Sources: src/gic_v2.rs(L1 - L480) src/lib.rs(L1 - L117)
Interrupt Generation Stage
Interrupts enter the pipeline through three different mechanisms based on their type classification.
flowchart TD
subgraph SPI_GEN["SPI Generation (32-1019)"]
UART_INT["UART interrupts"]
GPIO_INT["GPIO interrupts"]
OTHER_SPI["Other shared peripherals"]
end
subgraph PPI_GEN["PPI Generation (16-31)"]
TIMER_INT["Timer interrupts"]
PMU_INT["PMU interrupts"]
OTHER_PPI["Other private peripherals"]
end
subgraph SGI_GEN["SGI Generation (0-15)"]
SEND_SGI["send_sgi()"]
SEND_ALL["send_sgi_all_except_self()"]
SEND_SELF["send_sgi_to_self()"]
end
GICD_SGIR["GICD_SGIR register write"]
HW_SIGNAL["Hardware interrupt signal"]
DIST_INPUT["GicDistributor input"]
GICD_SGIR --> DIST_INPUT
GPIO_INT --> HW_SIGNAL
HW_SIGNAL --> DIST_INPUT
OTHER_PPI --> HW_SIGNAL
OTHER_SPI --> HW_SIGNAL
PMU_INT --> HW_SIGNAL
SEND_ALL --> GICD_SGIR
SEND_SELF --> GICD_SGIR
SEND_SGI --> GICD_SGIR
TIMER_INT --> HW_SIGNAL
UART_INT --> HW_SIGNAL
Sources: src/gic_v2.rs(L201 - L223) src/lib.rs(L14 - L29)
Distributor Processing Stage
The GicDistributor performs comprehensive interrupt management including configuration validation, priority evaluation, and CPU targeting.
| Function | Register Access | Purpose |
|---|---|---|
| set_enable()/get_enable() | ISENABLER/ICENABLER | Enable/disable interrupt delivery |
| set_priority()/get_priority() | IPRIORITYR | Configure interrupt priority levels |
| set_target_cpu()/get_target_cpu() | ITARGETSR | Select target processor for SPI interrupts |
| configure_interrupt() | ICFGR | Set edge/level trigger mode |
| set_state()/get_state() | ISPENDR/ICPENDR/ISACTIVER/ICACTIVER | Manage pending and active states |
flowchart TD INT_INPUT["Interrupt arrives at Distributor"] TYPE_CHECK["Check interrupt type"] SGI_PROC["SGI Processing:• Check SPENDSGIR/CPENDSGIR• Inter-processor targeting"] PPI_PROC["PPI Processing:• CPU-private handling• No target selection needed"] SPI_PROC["SPI Processing:• Check ITARGETSR for target CPU• Multi-processor capable"] ENABLE_CHECK["Check ISENABLER register"] DROP["Drop interrupt"] PRIORITY_CHECK["Check IPRIORITYR priority"] TRIGGER_CHECK["Check ICFGR trigger mode"] FORWARD["Forward to target GicCpuInterface"] ENABLE_CHECK --> DROP ENABLE_CHECK --> PRIORITY_CHECK INT_INPUT --> TYPE_CHECK PPI_PROC --> ENABLE_CHECK PRIORITY_CHECK --> TRIGGER_CHECK SGI_PROC --> ENABLE_CHECK SPI_PROC --> ENABLE_CHECK TRIGGER_CHECK --> FORWARD TYPE_CHECK --> PPI_PROC TYPE_CHECK --> SGI_PROC TYPE_CHECK --> SPI_PROC
Sources: src/gic_v2.rs(L180 - L199) src/gic_v2.rs(L225 - L261) src/gic_v2.rs(L263 - L283)
CPU Interface Processing Stage
The GicCpuInterface manages per-CPU interrupt delivery, acknowledgment, and completion signaling.
flowchart TD CPU_RECEIVE["GicCpuInterface receives interrupt"] PRIORITY_MASK["Check PMR priority mask"] QUEUE["Queue interrupt"] SIGNAL_CPU["Signal connected processor"] PROC_READ_IAR["Processor reads GICC_IAR"] IAR_VALUE["IAR returns interrupt ID"] HANDLER_CALL["Call interrupt handler"] IGNORE["Ignore spurious interrupt"] HANDLER_COMPLETE["Handler completes"] EOIR_WRITE["Write GICC_EOIR"] EL2_CHECK["Check EL2 mode"] COMPLETE_NORMAL["Interrupt complete"] DIR_WRITE["Write GICC_DIR to deactivate"] COMPLETE_EL2["Interrupt complete (EL2)"] CPU_RECEIVE --> PRIORITY_MASK DIR_WRITE --> COMPLETE_EL2 EL2_CHECK --> COMPLETE_NORMAL EL2_CHECK --> DIR_WRITE EOIR_WRITE --> EL2_CHECK HANDLER_CALL --> HANDLER_COMPLETE HANDLER_COMPLETE --> EOIR_WRITE IAR_VALUE --> HANDLER_CALL IAR_VALUE --> IGNORE PRIORITY_MASK --> QUEUE PRIORITY_MASK --> SIGNAL_CPU PROC_READ_IAR --> IAR_VALUE SIGNAL_CPU --> PROC_READ_IAR
Sources: src/gic_v2.rs(L394 - L396) src/gic_v2.rs(L406 - L408) src/gic_v2.rs(L416 - L418) src/gic_v2.rs(L443 - L459)
Register-Level Pipeline Flow
This diagram shows the specific register interactions during interrupt processing, mapping high-level operations to actual hardware register access.
sequenceDiagram
participant HardwareSoftware as "Hardware/Software"
participant GICDistributorRegisters as "GIC Distributor Registers"
participant CPUInterfaceRegisters as "CPU Interface Registers"
participant ARMProcessor as "ARM Processor"
Note over HardwareSoftware,ARMProcessor: Interrupt Generation
HardwareSoftware ->> GICDistributorRegisters: Interrupt signal / GICD_SGIR write
Note over GICDistributorRegisters: Distributor Processing
GICDistributorRegisters ->> GICDistributorRegisters: Check ISENABLER[n]
GICDistributorRegisters ->> GICDistributorRegisters: Check IPRIORITYR[n]
GICDistributorRegisters ->> GICDistributorRegisters: Check ITARGETSR[n] (SPI only)
GICDistributorRegisters ->> GICDistributorRegisters: Check ICFGR[n] trigger mode
Note over GICDistributorRegisters,CPUInterfaceRegisters: Forward to CPU Interface
GICDistributorRegisters ->> CPUInterfaceRegisters: Forward interrupt to target CPU
Note over CPUInterfaceRegisters: CPU Interface Processing
CPUInterfaceRegisters ->> CPUInterfaceRegisters: Check PMR priority mask
CPUInterfaceRegisters ->> ARMProcessor: Signal interrupt to processor
Note over ARMProcessor: Processor Handling
ARMProcessor ->> CPUInterfaceRegisters: Read GICC_IAR
CPUInterfaceRegisters ->> ARMProcessor: Return interrupt ID
ARMProcessor ->> ARMProcessor: Execute interrupt handler
ARMProcessor ->> CPUInterfaceRegisters: Write GICC_EOIR
alt EL2 Mode Enabled
ARMProcessor ->> CPUInterfaceRegisters: Write GICC_DIR
end
Note over HardwareSoftware,ARMProcessor: Interrupt Complete
Sources: src/gic_v2.rs(L20 - L90) src/gic_v2.rs(L443 - L459)
Pipeline Integration Points
The pipeline integrates with external components through well-defined interfaces that abstract the hardware complexity.
flowchart TD
subgraph RUNTIME["Runtime Processing"]
HANDLE_IRQ_FUNC["GicCpuInterface::handle_irq()"]
TRANSLATE_FUNC["translate_irq()"]
end
subgraph INIT["Initialization Phase"]
DIST_INIT["GicDistributor::init()"]
CPU_INIT["GicCpuInterface::init()"]
end
subgraph CONFIG["Configuration Interface"]
SET_ENABLE["set_enable()"]
SET_PRIORITY["set_priority()"]
SET_TARGET["set_target_cpu()"]
CONFIG_INT["configure_interrupt()"]
end
IAR_READ["GICC_IAR read"]
HANDLER_EXEC["User handler execution"]
EOIR_WRITE["GICC_EOIR write"]
DIR_WRITE_OPT["Optional GICC_DIR write"]
EOIR_WRITE --> DIR_WRITE_OPT
HANDLER_EXEC --> EOIR_WRITE
HANDLE_IRQ_FUNC --> IAR_READ
IAR_READ --> HANDLER_EXEC
TRANSLATE_FUNC --> HANDLE_IRQ_FUNC
Sources: src/gic_v2.rs(L342 - L373) src/gic_v2.rs(L461 - L478) src/gic_v2.rs(L443 - L459) src/lib.rs(L91 - L116)
Error Handling and Edge Cases
The pipeline includes robust handling for edge cases and error conditions.
| Condition | Detection Method | Response |
|---|---|---|
| Spurious Interrupt | IAR & 0x3ff >= 1020 | No handler called, no EOIR write |
| Disabled Interrupt | ISENABLER[n] == 0 | Interrupt dropped at distributor |
| Invalid IRQ Range | vector >= max_irqs | Early return from configuration functions |
| Priority Masking | Priority < PMR | Interrupt queued at CPU interface |
The handle_irq() function provides a complete wrapper that handles these edge cases automatically:
// Simplified logic from handle_irq() method
let iar = self.iar();
let vector = iar & 0x3ff;
if vector < 1020 {
handler(vector);
self.eoi(iar);
#[cfg(feature = "el2")]
if self.regs().CTLR.get() & GICC_CTLR_EOIMODENS_BIT != 0 {
self.dir(iar);
}
} else {
// spurious interrupt - no action taken
}
Sources: src/gic_v2.rs(L443 - L459) src/gic_v2.rs(L180 - L192) src/gic_v2.rs(L162 - L178)
Interrupt Types and Management
Relevant source files
This document covers the interrupt classification system and management operations provided by the arm_gicv2 crate. It details the three interrupt types supported by ARM GICv2 (SGI, PPI, SPI), their characteristics, and how they are managed through the distributor and CPU interface components.
For information about the core GIC architecture and components, see Core Architecture. For detailed register-level operations, see Register Interface.
Interrupt Classification System
The ARM GICv2 specification defines three distinct interrupt types, each serving different purposes and having specific characteristics. The crate implements this classification through constants, enums, and a translation function.
Interrupt Type Definitions
flowchart TD
subgraph subGraph2["Range Constants"]
SGI_RANGE_CONST["SGI_RANGE: 0..16"]
PPI_RANGE_CONST["PPI_RANGE: 16..32"]
SPI_RANGE_CONST["SPI_RANGE: 32..1020"]
end
subgraph subGraph1["InterruptType Enum"]
SGI_TYPE["InterruptType::SGI"]
PPI_TYPE["InterruptType::PPI"]
SPI_TYPE["InterruptType::SPI"]
end
subgraph subGraph0["Interrupt ID Space (0-1019)"]
SGI_SPACE["SGI Range0-15Software Generated"]
PPI_SPACE["PPI Range16-31Private Peripheral"]
SPI_SPACE["SPI Range32-1019Shared Peripheral"]
end
PPI_RANGE_CONST --> PPI_SPACE
PPI_TYPE --> PPI_SPACE
SGI_RANGE_CONST --> SGI_SPACE
SGI_TYPE --> SGI_SPACE
SPI_RANGE_CONST --> SPI_SPACE
SPI_TYPE --> SPI_SPACE
Sources: src/lib.rs(L14 - L29) src/lib.rs(L74 - L89)
| Interrupt Type | Range | Purpose | CPU Scope |
|---|---|---|---|
| SGI (Software Generated) | 0-15 | Inter-processor communication | All CPUs |
| PPI (Private Peripheral) | 16-31 | CPU-specific peripherals | Single CPU |
| SPI (Shared Peripheral) | 32-1019 | System-wide peripherals | Multiple CPUs |
Interrupt Translation System
The translate_irq function provides a safe way to convert logical interrupt IDs within each type to absolute GIC interrupt IDs.
flowchart TD INPUT["Logical ID + InterruptType"] TRANSLATE_IRQ["translate_irq()"] SGI_CHECK["id < 16?"] PPI_CHECK["id < 16?"] SPI_CHECK["id < 988?"] SGI_RESULT["Some(id)"] SGI_NONE["None"] PPI_RESULT["Some(id + 16)"] PPI_NONE["None"] SPI_RESULT["Some(id + 32)"] SPI_NONE["None"] GIC_INTID["GIC INTID"] INPUT --> TRANSLATE_IRQ PPI_CHECK --> PPI_NONE PPI_CHECK --> PPI_RESULT PPI_RESULT --> GIC_INTID SGI_CHECK --> SGI_NONE SGI_CHECK --> SGI_RESULT SGI_RESULT --> GIC_INTID SPI_CHECK --> SPI_NONE SPI_CHECK --> SPI_RESULT SPI_RESULT --> GIC_INTID TRANSLATE_IRQ --> PPI_CHECK TRANSLATE_IRQ --> SGI_CHECK TRANSLATE_IRQ --> SPI_CHECK
Sources: src/lib.rs(L91 - L116)
Interrupt Management Operations
The GIC provides different management capabilities depending on the interrupt type. These operations are primarily handled through the GicDistributor component.
Core Management Functions
flowchart TD
subgraph subGraph1["Interrupt Type Applicability"]
SGI_OPS["SGI Operations• send_sgi()• send_sgi_all_except_self()• send_sgi_to_self()• set_pend() (special)"]
PPI_OPS["PPI Operations• set_enable()• set_priority()• set_state()"]
SPI_OPS["SPI Operations• configure_interrupt()• set_enable()• set_priority()• set_target_cpu()• set_state()"]
end
subgraph subGraph0["GicDistributor Management Methods"]
CONFIGURE["configure_interrupt()"]
ENABLE["set_enable() / get_enable()"]
PRIORITY["set_priority() / get_priority()"]
TARGET["set_target_cpu() / get_target_cpu()"]
STATE["set_state() / get_state()"]
PEND["set_pend()"]
end
CONFIGURE --> SPI_OPS
ENABLE --> PPI_OPS
ENABLE --> SPI_OPS
PEND --> SGI_OPS
PRIORITY --> PPI_OPS
PRIORITY --> SPI_OPS
STATE --> PPI_OPS
STATE --> SGI_OPS
STATE --> SPI_OPS
TARGET --> SPI_OPS
Sources: src/gic_v2.rs(L161 - L283) src/gic_v2.rs(L201 - L223)
Trigger Mode Configuration
Only SPI interrupts support configurable trigger modes. The system restricts trigger mode configuration to the SPI range.
flowchart TD CONFIG_REQ["configure_interrupt(vector, trigger_mode)"] RANGE_CHECK["vector >= 32?"] EARLY_RETURN["return (invalid)"] MAX_CHECK["vector < max_irqs?"] CALCULATE["Calculate ICFGR register"] REG_ACCESS["Access ICFGR[reg_idx]"] MODE_CHECK["TriggerMode?"] SET_EDGE["Set bit (Edge)"] CLEAR_BIT["Clear bit (Level)"] WRITE_REG["Write ICFGR register"] CALCULATE --> REG_ACCESS CLEAR_BIT --> WRITE_REG CONFIG_REQ --> RANGE_CHECK MAX_CHECK --> CALCULATE MAX_CHECK --> EARLY_RETURN MODE_CHECK --> CLEAR_BIT MODE_CHECK --> SET_EDGE RANGE_CHECK --> EARLY_RETURN RANGE_CHECK --> MAX_CHECK REG_ACCESS --> MODE_CHECK SET_EDGE --> WRITE_REG
Sources: src/gic_v2.rs(L161 - L178)
Software Generated Interrupt Management
SGIs have special handling due to their role in inter-processor communication. They use dedicated register interfaces and targeting mechanisms.
SGI Generation Methods
| Method | Target | Use Case |
|---|---|---|
| send_sgi(dest_cpu_id, sgi_num) | Specific CPU | Direct communication |
| send_sgi_all_except_self(sgi_num) | All other CPUs | Broadcast operations |
| send_sgi_to_self(sgi_num) | Current CPU | Self-signaling |
flowchart TD
subgraph subGraph1["GICD_SGIR Register Fields"]
TARGET_FILTER["TargetListFilter"]
CPU_LIST["CPUTargetList"]
SGI_ID["SGIINTID"]
end
subgraph subGraph0["SGI Targeting Modes"]
SPECIFIC["ForwardToCPUTargetList+ CPUTargetList"]
BROADCAST["ForwardToAllExceptRequester"]
SELF["ForwardToRequester"]
end
GICD_SGIR_REG["GICD_SGIR Register"]
BROADCAST --> TARGET_FILTER
CPU_LIST --> GICD_SGIR_REG
SELF --> TARGET_FILTER
SGI_ID --> GICD_SGIR_REG
SPECIFIC --> CPU_LIST
SPECIFIC --> TARGET_FILTER
TARGET_FILTER --> GICD_SGIR_REG
Sources: src/gic_v2.rs(L201 - L223) src/regs/gicd_sgir.rs
SGI Pending State Management
SGIs require special handling for pending state management due to their per-CPU nature:
flowchart TD SET_PEND["set_pend(int_id, is_pend, current_cpu_id)"] SGI_CHECK["int_id in SGI_RANGE?"] SGI_CALC["Calculate register offset"] NORMAL_PEND["Use ISPENDR/ICPENDR"] PEND_CHECK["is_pend?"] SET_SPENDSGIR["Set SPENDSGIR bit"] CLEAR_CPENDSGIR["Clear CPENDSGIR bits"] CPU_SPECIFIC["Target specific CPU"] ALL_CPUS["Clear for all CPUs"] CLEAR_CPENDSGIR --> ALL_CPUS PEND_CHECK --> CLEAR_CPENDSGIR PEND_CHECK --> SET_SPENDSGIR SET_PEND --> SGI_CHECK SET_SPENDSGIR --> CPU_SPECIFIC SGI_CALC --> PEND_CHECK SGI_CHECK --> NORMAL_PEND SGI_CHECK --> SGI_CALC
Sources: src/gic_v2.rs(L264 - L283)
Integration with GIC Components
Interrupt management operations interface with both the distributor and CPU interface components, with different responsibilities for each type.
Distributor vs CPU Interface Responsibilities
flowchart TD
subgraph subGraph2["Interrupt Flow"]
PERIPHERAL["Peripheral Device"]
HANDLER["Interrupt Handler"]
end
subgraph subGraph1["GicCpuInterface Responsibilities"]
CPU_ACK["Acknowledgment• iar() register read• Interrupt priority"]
CPU_EOI["Completion• eoi() priority drop• dir() deactivation"]
CPU_MASK["Priority Masking• PMR register• Preemption control"]
end
subgraph subGraph0["GicDistributor Responsibilities"]
DIST_CONFIG["Configuration• Trigger modes• Priority levels• CPU targeting"]
DIST_ENABLE["Enable/Disable• Per-interrupt control• Group assignment"]
DIST_SGI["SGI Generation• Inter-processor signals• Targeting modes"]
DIST_STATE["State Management• Pending state• Active state"]
end
CPU_ACK --> HANDLER
DIST_CONFIG --> CPU_ACK
HANDLER --> CPU_EOI
PERIPHERAL --> DIST_CONFIG
Sources: src/gic_v2.rs(L92 - L131) src/gic_v2.rs(L376 - L479)
The interrupt management system provides a complete abstraction over ARM GICv2 hardware while maintaining type safety and proper encapsulation of interrupt-type-specific behaviors.
Interrupt Classification System
Relevant source files
Purpose and Scope
This document covers the interrupt classification system implemented in the ARM GICv2 crate, which categorizes interrupts into three distinct types based on their ID ranges and intended usage patterns. The classification system provides the foundation for interrupt routing, priority management, and inter-processor communication within the GIC architecture.
For detailed information about how these classified interrupts are processed through the system, see Interrupt Processing Pipeline. For specific implementation details of Software Generated Interrupts, see Software Generated Interrupts.
Interrupt Type Classification
The ARM GICv2 specification defines three fundamental interrupt types, each with specific characteristics and use cases. The crate implements this classification through predefined ranges and an enumeration system.
Interrupt Type Definitions
flowchart TD
subgraph subGraph2["Range Constants"]
SGI_RANGE_CONST["SGI_RANGE: 0..16"]
PPI_RANGE_CONST["PPI_RANGE: 16..32"]
SPI_RANGE_CONST["SPI_RANGE: 32..1020"]
end
subgraph subGraph1["InterruptType Enum"]
SGI_ENUM["InterruptType::SGI"]
PPI_ENUM["InterruptType::PPI"]
SPI_ENUM["InterruptType::SPI"]
end
subgraph subGraph0["Interrupt ID Space (0-1019)"]
SGI_IDS["SGI Range: 0-15Software Generated"]
PPI_IDS["PPI Range: 16-31Private Peripheral"]
SPI_IDS["SPI Range: 32-1019Shared Peripheral"]
RESERVED["Reserved: 1020-1023"]
end
PPI_ENUM --> PPI_RANGE_CONST
PPI_IDS --> PPI_ENUM
SGI_ENUM --> SGI_RANGE_CONST
SGI_IDS --> SGI_ENUM
SPI_ENUM --> SPI_RANGE_CONST
SPI_IDS --> SPI_ENUM
Sources: src/lib.rs(L14 - L29) src/lib.rs(L74 - L89)
| Interrupt Type | ID Range | Count | Primary Use Case |
|---|---|---|---|
| SGI (Software Generated) | 0-15 | 16 | Inter-processor communication |
| PPI (Private Peripheral) | 16-31 | 16 | CPU-specific peripheral interrupts |
| SPI (Shared Peripheral) | 32-1019 | 988 | System-wide peripheral interrupts |
Software Generated Interrupts (SGI)
SGIs occupy interrupt IDs 0-15 and are generated through software writes to the GICD_SGIR register. These interrupts enable communication between processors in multi-core systems.
Key Characteristics:
- Always generated by software, never by hardware peripherals
- Can target specific CPUs or groups of CPUs
- Used for synchronization and coordination between cores
- Highest priority in the interrupt ID space
Private Peripheral Interrupts (PPI)
PPIs occupy interrupt IDs 16-31 and are specific to individual processors. Each CPU core has its own private set of these interrupt sources.
Key Characteristics:
- Processor-specific interrupt sources
- Cannot be routed to other CPUs
- Typically used for CPU timers, PMU events, and other core-local peripherals
- Each CPU core sees the same PPI ID but from different physical sources
Shared Peripheral Interrupts (SPI)
SPIs occupy interrupt IDs 32-1019 and represent the largest category of interrupts. These can be routed to any available CPU in the system.
Key Characteristics:
- System-wide interrupt sources
- Configurable CPU targeting and routing
- Generated by external peripherals and devices
- Support for load balancing and affinity settings
Interrupt Translation Mechanism
The crate provides a translation function that converts relative interrupt IDs within each type to absolute GIC interrupt IDs.
flowchart TD
subgraph Output["Output"]
ABS_ID["Absolute GIC ID(0-1019)"]
NONE_VAL["None (invalid input)"]
end
subgraph subGraph1["translate_irq Function"]
VALIDATE["Validate ID againsttype-specific limits"]
CALCULATE["Calculate absoluteGIC interrupt ID"]
RETURN["Return Option"]
end
subgraph subGraph0["Input Parameters"]
REL_ID["Relative ID(within type)"]
INT_TYPE["InterruptType(SGI/PPI/SPI)"]
end
CALCULATE --> RETURN
INT_TYPE --> VALIDATE
REL_ID --> VALIDATE
RETURN --> ABS_ID
RETURN --> NONE_VAL
VALIDATE --> CALCULATE
Sources: src/lib.rs(L91 - L116)
The translate_irq function performs the following transformations:
| Input Type | Calculation | Example |
|---|---|---|
| InterruptType::SGI | id(direct mapping) | translate_irq(5, SGI)→Some(5) |
| InterruptType::PPI | id + PPI_RANGE.start | translate_irq(3, PPI)→Some(19) |
| InterruptType::SPI | id + SPI_RANGE.start | translate_irq(10, SPI)→Some(42) |
Range Validation Logic
The translation function includes bounds checking to ensure valid interrupt IDs:
- SGI validation:
id < SGI_RANGE.end(must be < 16) - PPI validation:
id < PPI_RANGE.end - PPI_RANGE.start(must be < 16) - SPI validation:
id < SPI_RANGE.end - SPI_RANGE.start(must be < 988)
Invalid inputs return None, providing safe interrupt ID translation.
System Integration
The interrupt classification system integrates with the broader GIC architecture through well-defined constants and type-safe interfaces.
flowchart TD
subgraph subGraph2["Usage Examples"]
ENABLE_INT["Enable interrupt by type"]
SET_PRIORITY["Set interrupt priority"]
ROUTE_CPU["Route SPI to CPU"]
SEND_SGI["Generate SGI"]
end
subgraph subGraph1["Core GIC Components"]
DISTRIBUTOR["GicDistributor"]
CPU_INTERFACE["GicCpuInterface"]
end
subgraph subGraph0["Classification Constants"]
SGI_RANGE["SGI_RANGE: Range"]
PPI_RANGE["PPI_RANGE: Range"]
SPI_RANGE["SPI_RANGE: Range"]
GIC_MAX_IRQ["GIC_MAX_IRQ: usize"]
end
CPU_INTERFACE --> ENABLE_INT
DISTRIBUTOR --> ENABLE_INT
DISTRIBUTOR --> ROUTE_CPU
DISTRIBUTOR --> SEND_SGI
DISTRIBUTOR --> SET_PRIORITY
GIC_MAX_IRQ --> DISTRIBUTOR
PPI_RANGE --> DISTRIBUTOR
SGI_RANGE --> DISTRIBUTOR
SPI_RANGE --> DISTRIBUTOR
Sources: src/lib.rs(L14 - L32) src/lib.rs(L12)
Configuration Constants
The system defines several key constants that govern interrupt handling:
GIC_MAX_IRQ: Maximum interrupt count (1024)GIC_CONFIG_BITS: Bits per interrupt configuration (2)- Range constants: Define valid ID spaces for each interrupt type
These constants ensure consistent behavior across the GIC implementation and provide compile-time guarantees about interrupt ID validity.
Sources: src/lib.rs(L31 - L35)
Software Generated Interrupts
Relevant source files
This document covers Software Generated Interrupts (SGIs) in the ARM GICv2 implementation, including their generation methods, register interface, and targeting mechanisms. SGIs are a critical component for inter-processor communication in multi-core systems.
For information about the overall interrupt classification system, see Interrupt Classification System. For details about the underlying register implementations, see GICD_SGIR Register Details.
SGI Overview and Characteristics
Software Generated Interrupts occupy interrupt IDs 0-15 and serve as the primary mechanism for inter-processor communication in ARM GICv2 systems. Unlike peripheral interrupts, SGIs are generated entirely through software writes to the GICD_SGIR register.
| Characteristic | Value |
|---|---|
| Interrupt ID Range | 0-15 |
| Total Count | 16 SGIs |
| Primary Use Case | Inter-processor communication |
| Generation Method | Software write to GICD_SGIR |
| Targeting | Flexible CPU targeting modes |
SGIs are defined by the SGI_RANGE constant and classified through the InterruptType::SGI enum variant. The translate_irq function handles SGI ID validation, ensuring only IDs 0-15 are accepted for SGI operations.
SGI Generation Flow
flowchart TD A["Software Request"] B["GicDistributor::send_sgi*"] C["GICD_SGIR Register Write"] D["TargetListFilter Mode"] E["ForwardToCPUTargetList"] F["ForwardToAllExceptRequester"] G["ForwardToRequester"] H["CPUTargetList Field"] I["All CPUs Except Current"] J["Current CPU Only"] K["Target CPU Interfaces"] L["SGI Delivery"] A --> B B --> C C --> D D --> E D --> F D --> G E --> H F --> I G --> J H --> K I --> K J --> K K --> L
Sources: src/lib.rs(L14 - L18) src/lib.rs(L92 - L116) src/gic_v2.rs(L202 - L223)
SGI Generation Methods
The GicDistributor struct provides three primary methods for generating SGIs, each corresponding to different targeting strategies. These methods abstract the complexity of GICD_SGIR register programming.
send_sgi Method
The send_sgi method targets a specific CPU interface using the ForwardToCPUTargetList mode:
// Method signature from GicDistributor implementation
pub fn send_sgi(&mut self, dest_cpu_id: usize, sgi_num: usize)
This method constructs a GICD_SGIR register write with:
TargetListFilter=ForwardToCPUTargetList(0b00)CPUTargetList= destination CPU ID bitmaskSGIINTID= SGI number (0-15)
send_sgi_all_except_self Method
The send_sgi_all_except_self method broadcasts to all CPU interfaces except the requesting processor:
// Method signature from GicDistributor implementation
pub fn send_sgi_all_except_self(&mut self, sgi_num: usize)
This method uses TargetListFilter = ForwardToAllExceptRequester (0b01), eliminating the need to specify individual CPU targets.
send_sgi_to_self Method
The send_sgi_to_self method targets only the requesting CPU interface:
// Method signature from GicDistributor implementation
pub fn send_sgi_to_self(&mut self, sgi_num: usize)
This method uses TargetListFilter = ForwardToRequester (0b10) for local SGI generation.
SGI Method Mapping
flowchart TD
subgraph subGraph2["Hardware Behavior"]
G["Target specific CPU interface"]
H["Broadcast to all except sender"]
I["Self-targeting only"]
end
subgraph subGraph1["GICD_SGIR TargetListFilter"]
D["ForwardToCPUTargetList (0b00)"]
E["ForwardToAllExceptRequester (0b01)"]
F["ForwardToRequester (0b10)"]
end
subgraph subGraph0["GicDistributor Methods"]
A["send_sgi(dest_cpu_id, sgi_num)"]
B["send_sgi_all_except_self(sgi_num)"]
C["send_sgi_to_self(sgi_num)"]
end
A --> D
B --> E
C --> F
D --> G
E --> H
F --> I
Sources: src/gic_v2.rs(L202 - L208) src/gic_v2.rs(L211 - L216) src/gic_v2.rs(L219 - L223)
GICD_SGIR Register Interface
The GICD_SGIR register controls SGI generation through a structured bit field layout. The register is implemented as a write-only interface using the tock-registers abstraction.
Register Bit Fields
| Bits | Field | Purpose |
|---|---|---|
| [31:26] | Reserved | Must be zero |
| [25:24] | TargetListFilter | Determines targeting mode |
| [23:16] | CPUTargetList | CPU interface bitmask (when TargetListFilter = 0b00) |
| [15] | NSATT | Security attribute (Security Extensions only) |
| [14:4] | Reserved | Should be zero |
| [3:0] | SGIINTID | SGI interrupt ID (0-15) |
The TargetListFilter field provides four targeting modes:
| Value | Mode | Description |
|---|---|---|
| 0b00 | ForwardToCPUTargetList | Use CPUTargetList bitmask |
| 0b01 | ForwardToAllExceptRequester | Broadcast except sender |
| 0b10 | ForwardToRequester | Self-targeting only |
| 0b11 | Reserved | Invalid mode |
GICD_SGIR Register Layout
flowchart TD
subgraph subGraph0["GICD_SGIR Register (32 bits)"]
A["[31:26]Reserved"]
B["[25:24]TargetListFilter"]
C["[23:16]CPUTargetList"]
D["[15]NSATT"]
E["[14:4]Reserved"]
F["[3:0]SGIINTID"]
end
G["Targeting Mode0b00: CPUTargetList0b01: All except self0b10: Self only"]
H["8-bit CPU maskEach bit = CPU interface"]
I["SGI ID (0-15)4-bit value"]
B --> G
C --> H
F --> I
Sources: src/regs/gicd_sgir.rs(L21 - L58) src/gic_v2.rs(L18)
SGI State Management
SGIs require special handling in the GIC distributor's state management functions due to their per-CPU nature. The set_pend method implements SGI-specific logic using dedicated pending registers.
SGI Pending State Registers
SGIs use separate pending state registers compared to other interrupt types:
- SPENDSGIR: Set SGI pending registers (4 registers, 32 bits each)
- CPENDSGIR: Clear SGI pending registers (4 registers, 32 bits each)
Each register covers 4 SGIs with 8 bits per SGI (one bit per CPU interface).
SGI Pending State Implementation
The set_pend method handles SGIs using a different code path:
// SGI range check and register access pattern
if SGI_RANGE.contains(&int_id) {
let reg_idx = int_id / 4; // SGI register index (0-3)
let offset = (int_id % 4) * 8; // Bit offset within register
// Set/clear pending state per CPU
}
SGI Pending State Flow
flowchart TD A["set_pend(int_id, is_pend, current_cpu_id)"] B["SGI_RANGE.contains(int_id)?"] C["SGI Pending Path"] D["Standard Pending Path"] E["reg_idx = int_id / 4"] F["offset = (int_id % 4) * 8"] G["is_pend?"] H["SPENDSGIR[reg_idx]Set bit at offset + current_cpu_id"] I["CPENDSGIR[reg_idx]Clear 8 bits at offset"] J["ISPENDR/ICPENDR registers"] A --> B B --> C B --> D C --> E C --> F D --> J E --> G F --> G G --> H G --> I
Sources: src/gic_v2.rs(L264 - L283) src/gic_v2.rs(L55 - L58) src/lib.rs(L14 - L18)
SGI Security and Access Control
When the GIC implements Security Extensions, the NSATT bit in GICD_SGIR controls SGI security attributes. This field determines whether SGIs are forwarded based on their Group 0 or Group 1 configuration.
Security Behavior
| NSATT Value | SGI Forwarding Condition |
|---|---|
| 0 | Forward only if SGI configured as Group 0 |
| 1 | Forward only if SGI configured as Group 1 |
Non-secure writes to GICD_SGIR can only generate Group 1 SGIs, regardless of the NSATT field value. This provides hardware-enforced security isolation between secure and non-secure SGI generation.
Sources: src/regs/gicd_sgir.rs(L42 - L49)
Register Interface
Relevant source files
This document describes how the arm_gicv2 crate abstracts ARM GICv2 hardware registers and provides safe, type-checked access to GIC functionality. The register interface serves as the foundation layer that bridges Rust code with memory-mapped hardware registers.
For information about the higher-level GIC components that use these registers, see Core Architecture. For specific register details and bit field definitions, see Register Module Organization and GICD_SGIR Register Details.
Register Abstraction Architecture
The crate implements a three-layer abstraction for hardware register access:
flowchart TD
subgraph subGraph3["Hardware Layer"]
MMIO["Memory-Mapped I/O"]
GICD_HW["GIC Distributor Hardware"]
GICC_HW["CPU Interface Hardware"]
end
subgraph subGraph2["Hardware Abstraction Layer"]
TOCK_REGS["tock-registers crate"]
BITFIELDS["register_bitfields! macro"]
REGISTER_TYPES["ReadOnly, WriteOnly, ReadWrite"]
end
subgraph subGraph1["Register Structure Layer"]
DIST_REGS["GicDistributorRegs struct"]
CPU_REGS["GicCpuInterfaceRegs struct"]
REG_FIELDS["Individual Register FieldsCTLR, SGIR, IAR, EOIR, etc."]
end
subgraph subGraph0["Application Layer"]
APP["High-Level API Methods"]
DIST_METHODS["GicDistributor methodssend_sgi(), set_enable(), etc."]
CPU_METHODS["GicCpuInterface methodsiar(), eoi(), handle_irq(), etc."]
end
APP --> CPU_METHODS
APP --> DIST_METHODS
BITFIELDS --> MMIO
CPU_METHODS --> CPU_REGS
CPU_REGS --> REG_FIELDS
DIST_METHODS --> DIST_REGS
DIST_REGS --> REG_FIELDS
MMIO --> GICC_HW
MMIO --> GICD_HW
REGISTER_TYPES --> MMIO
REG_FIELDS --> BITFIELDS
REG_FIELDS --> REGISTER_TYPES
REG_FIELDS --> TOCK_REGS
TOCK_REGS --> MMIO
Sources: src/gic_v2.rs(L1 - L480) src/regs/gicd_sgir.rs(L1 - L62)
Memory-Mapped Register Structures
The crate defines two primary register structures that map directly to hardware memory layouts:
GicDistributorRegs Layout
flowchart TD
subgraph subGraph0["GicDistributorRegs Memory Layout"]
CTLR["0x0000: CTLRDistributor Control RegisterReadWrite<u32>"]
TYPER["0x0004: TYPERInterrupt Controller TypeReadOnly<u32>"]
IIDR["0x0008: IIDRImplementer IdentificationReadOnly<u32>"]
IGROUPR["0x0080: IGROUPR[0x20]Interrupt Group RegistersReadWrite<u32>"]
ISENABLER["0x0100: ISENABLER[0x20]Interrupt Set-EnableReadWrite<u32>"]
ICENABLER["0x0180: ICENABLER[0x20]Interrupt Clear-EnableReadWrite<u32>"]
IPRIORITYR["0x0400: IPRIORITYR[0x100]Interrupt PriorityReadWrite<u32>"]
ITARGETSR["0x0800: ITARGETSR[0x100]Interrupt Processor TargetsReadWrite<u32>"]
ICFGR["0x0c00: ICFGR[0x40]Interrupt ConfigurationReadWrite<u32>"]
SGIR["0x0f00: SGIRSoftware Generated InterruptGicdSgirReg (WriteOnly)"]
CPENDSGIR["0x0f10: CPENDSGIR[0x4]SGI Clear-PendingReadWrite<u32>"]
SPENDSGIR["0x0f20: SPENDSGIR[0x4]SGI Set-PendingReadWrite<u32>"]
end
CPENDSGIR --> SPENDSGIR
CTLR --> TYPER
ICENABLER --> IPRIORITYR
ICFGR --> SGIR
IGROUPR --> ISENABLER
IIDR --> IGROUPR
IPRIORITYR --> ITARGETSR
ISENABLER --> ICENABLER
ITARGETSR --> ICFGR
SGIR --> CPENDSGIR
TYPER --> IIDR
GicCpuInterfaceRegs Layout
flowchart TD
subgraph subGraph0["GicCpuInterfaceRegs Memory Layout"]
CPU_CTLR["0x0000: CTLRCPU Interface ControlReadWrite<u32>"]
PMR["0x0004: PMRInterrupt Priority MaskReadWrite<u32>"]
BPR["0x0008: BPRBinary Point RegisterReadWrite<u32>"]
IAR["0x000c: IARInterrupt AcknowledgeReadOnly<u32>"]
EOIR["0x0010: EOIREnd of InterruptWriteOnly<u32>"]
RPR["0x0014: RPRRunning PriorityReadOnly<u32>"]
HPPIR["0x0018: HPPIRHighest Priority PendingReadOnly<u32>"]
CPU_IIDR["0x00fc: IIDRCPU Interface IdentificationReadOnly<u32>"]
DIR["0x1000: DIRDeactivate InterruptWriteOnly<u32>"]
end
BPR --> IAR
CPU_CTLR --> PMR
CPU_IIDR --> DIR
EOIR --> RPR
HPPIR --> CPU_IIDR
IAR --> EOIR
PMR --> BPR
RPR --> HPPIR
Sources: src/gic_v2.rs(L20 - L90)
Register Access Patterns
The register interface implements type-safe access patterns through the tock-registers crate:
| Register Type | Access Pattern | Example Usage |
|---|---|---|
| ReadOnly | .get() | self.regs().IAR.get() |
| WriteOnly | .set(value) | self.regs().EOIR.set(iar) |
| ReadWrite | .get(),.set(value) | self.regs().CTLR.set(val) |
| GicdSgirReg | .write(fields) | self.regs().SGIR.write(fields) |
Register Structure Instantiation
flowchart TD
BASE_PTR["Raw Base Pointer*mut u8"]
NONNULL["NonNull<GicDistributorRegs>NonNull<GicCpuInterfaceRegs>"]
STRUCT_FIELD["base field inGicDistributor/GicCpuInterface"]
REGS_METHOD["regs() methodunsafe { self.base.as_ref() }"]
REGISTER_ACCESS["Direct register accessself.regs().CTLR.get()"]
BASE_PTR --> NONNULL
NONNULL --> STRUCT_FIELD
REGS_METHOD --> REGISTER_ACCESS
STRUCT_FIELD --> REGS_METHOD
Sources: src/gic_v2.rs(L140 - L149) src/gic_v2.rs(L378 - L386)
Type-Safe Bit Field Access
The GICD_SGIR register demonstrates advanced bit field abstraction:
flowchart TD
subgraph subGraph1["Enum Values for TargetListFilter"]
TLF_00["ForwardToCPUTargetList = 0b00"]
TLF_01["ForwardToAllExceptRequester = 0b01"]
TLF_10["ForwardToRequester = 0b10"]
TLF_11["Reserved = 0b11"]
end
subgraph subGraph0["GICD_SGIR Register Bit Fields"]
BITS_31_26["Bits [31:26]Reserved316 bits"]
BITS_25_24["Bits [25:24]TargetListFilter2 bits"]
BITS_23_16["Bits [23:16]CPUTargetList8 bits"]
BITS_15["Bit [15]NSATT1 bit"]
BITS_14_4["Bits [14:4]Reserved14_411 bits"]
BITS_3_0["Bits [3:0]SGIINTID4 bits"]
end
Usage Example
The register is accessed through structured field composition:
self.regs().SGIR.write(
GICD_SGIR::TargetListFilter::ForwardToCPUTargetList
+ GICD_SGIR::CPUTargetList.val(dest_cpu_id as _)
+ GICD_SGIR::SGIINTID.val(sgi_num as _)
);
Sources: src/regs/gicd_sgir.rs(L21 - L58) src/gic_v2.rs(L203 - L208)
Register Module Organization
The register definitions are organized in the regs/ module:
| Module | Purpose | Key Components |
|---|---|---|
| regs/mod.rs | Module exports | Re-exportsgicd_sgiritems |
| regs/gicd_sgir.rs | SGIR register | GICD_SGIRbitfields,GicdSgirRegtype |
The module structure allows for extensible register definitions while maintaining clean separation between different register types.
Sources: src/regs/mod.rs(L1 - L4) src/regs/gicd_sgir.rs(L1 - L62)
Register Module Organization
Relevant source files
Purpose and Scope
This document covers the structural organization of the register abstraction layer within the arm_gicv2 crate. The register module provides hardware register definitions and abstractions that enable safe, type-safe access to ARM GICv2 hardware registers. This page focuses on how register definitions are organized, modularized, and exposed to the rest of the codebase.
For detailed information about specific register implementations, see GICD_SGIR Register Details. For broader context on how these registers integrate with the main GIC components, see Core Architecture.
Module Structure Overview
The register module follows a hierarchical organization pattern where individual register definitions are contained in dedicated submodules and selectively re-exported through the main module interface.
Register Module Hierarchy
flowchart TD A["src/regs/mod.rs"] B["gicd_sgir submodule"] C["Public re-exports"] D["gicd_sgir.rs file"] E["GICD_SGIR register definition"] F["pub use gicd_sgir::*"] G["GicDistributor"] H["GicCpuInterface"] I["External consumers"] A --> B A --> C A --> I B --> D C --> F D --> E G --> A H --> A
Sources: src/regs/mod.rs(L1 - L4)
The current module organization demonstrates a focused approach where each hardware register receives its own dedicated submodule. The mod.rs file serves as the central coordination point that aggregates and exposes register definitions.
Module Declaration and Re-export Pattern
The register module employs a straightforward declaration and re-export strategy:
| Module Component | Purpose | Implementation |
|---|---|---|
| Submodule declaration | Declares register-specific modules | mod gicd_sgir; |
| Public re-export | Exposes register APIs to consumers | pub use gicd_sgir::*; |
Sources: src/regs/mod.rs(L1 - L3)
This pattern provides several architectural benefits:
- Encapsulation: Each register definition is isolated in its own module
- Selective exposure: Only intended APIs are re-exported through the main module
- Maintainability: New registers can be added by following the same pattern
- Namespace management: Wildcard re-exports flatten the API surface for consumers
Register Organization Patterns
Submodule Structure
flowchart TD A["mod.rs"] B["gicd_sgir"] C["Register struct"] D["Bit field definitions"] E["Helper methods"] F["Future register modules"] G["Additional register types"] A --> B B --> C B --> D B --> E F --> A F --> G
Sources: src/regs/mod.rs(L1)
The modular approach allows for systematic expansion of register support. Each submodule can contain:
- Register structure definitions using
tock-registersabstractions - Bit field layouts and access patterns
- Register-specific helper functions and constants
- Documentation specific to that hardware register
API Surface Management
The re-export strategy in the module creates a unified namespace for register access:
flowchart TD A["src/regs/mod.rs"] B["pub use gicd_sgir::*"] C["GICD_SGIR register types"] D["Related constants"] E["Helper functions"] F["GicDistributor"] G["External crate users"] A --> B B --> C B --> D B --> E F --> C F --> D F --> E G --> A
Sources: src/regs/mod.rs(L3)
This design enables consumers to import register definitions through a single, well-defined module path while maintaining internal organization through submodules.
Integration with Hardware Abstraction
Register Module Role in GIC Architecture
flowchart TD A["Hardware Layer"] B["Register Abstractions"] C["src/regs/mod.rs"] D["gicd_sgir module"] E["GicDistributor"] F["GicCpuInterface"] G["Memory-mapped hardware"] H["Type-safe register access"] A --> B B --> C C --> D E --> C F --> C G --> A H --> E H --> F
Sources: src/regs/mod.rs(L1 - L4)
The register module serves as the foundational layer that translates raw hardware register layouts into type-safe Rust abstractions. This enables the higher-level GicDistributor and GicCpuInterface components to interact with hardware through well-defined, compile-time-verified interfaces.
Extensibility and Future Growth
The current organization pattern supports systematic expansion for additional GIC registers:
| Register Category | Current Support | Extension Pattern |
|---|---|---|
| Software Generated Interrupts | gicd_sgirmodule | Additional SGI-related registers |
| Distributor Control | Not yet implemented | gicd_ctlr,gicd_typermodules |
| CPU Interface | Not yet implemented | gicc_*register modules |
| Priority Management | Not yet implemented | Priority and masking register modules |
Sources: src/regs/mod.rs(L1 - L4)
Each new register would follow the established pattern of creating a dedicated submodule and adding appropriate re-exports to maintain the unified API surface while preserving internal modularity.
GICD_SGIR Register Details
Relevant source files
This document provides comprehensive technical documentation for the GICD_SGIR (Software Generated Interrupt Register) implementation in the arm_gicv2 crate. The register controls the generation and routing of Software Generated Interrupts (SGIs) within the ARM GICv2 interrupt controller system.
For broader context on SGI interrupt types and their role in the interrupt system, see Software Generated Interrupts. For general register interface organization, see Register Module Organization.
Register Overview and Purpose
The GICD_SGIR register serves as the primary control interface for generating Software Generated Interrupts in the GIC Distributor. SGIs enable inter-processor communication in multi-core ARM systems by allowing one CPU to trigger interrupts on other CPUs or itself.
GICD_SGIR in GIC Architecture
GICD_SGIR Location in Interrupt Processing
flowchart TD CPU0["CPU 0"] DISTRIBUTOR["GicDistributor"] CPU1["CPU 1"] CPU2["CPU 2"] CPU3["CPU 3"] GICD_SGIR["GICD_SGIR RegisterWrite-Only0x00000F00 offset"] SGI_LOGIC["SGI Generation Logic"] TARGET_CPU0["Target CPU 0GicCpuInterface"] TARGET_CPU1["Target CPU 1GicCpuInterface"] TARGET_CPU2["Target CPU 2GicCpuInterface"] TARGET_CPU3["Target CPU 3GicCpuInterface"] INT0["Interrupt ID 0-15"] INT1["Interrupt ID 0-15"] INT2["Interrupt ID 0-15"] INT3["Interrupt ID 0-15"] CPU0 --> DISTRIBUTOR CPU1 --> DISTRIBUTOR CPU2 --> DISTRIBUTOR CPU3 --> DISTRIBUTOR DISTRIBUTOR --> GICD_SGIR GICD_SGIR --> SGI_LOGIC SGI_LOGIC --> TARGET_CPU0 SGI_LOGIC --> TARGET_CPU1 SGI_LOGIC --> TARGET_CPU2 SGI_LOGIC --> TARGET_CPU3 TARGET_CPU0 --> INT0 TARGET_CPU1 --> INT1 TARGET_CPU2 --> INT2 TARGET_CPU3 --> INT3
Sources: src/regs/gicd_sgir.rs(L1 - L62)
Register Bit Field Layout
GICD_SGIR Bit Field Structure
flowchart TD
subgraph subGraph0["32-bit GICD_SGIR Register Layout"]
BITS31_26["Bits 31:26ReservedOFFSET(26) NUMBITS(6)"]
BITS25_24["Bits 25:24TargetListFilterOFFSET(24) NUMBITS(2)"]
BITS23_16["Bits 23:16CPUTargetListOFFSET(16) NUMBITS(8)"]
BIT15["Bit 15NSATTOFFSET(15) NUMBITS(1)"]
BITS14_4["Bits 14:4Reserved SBZOFFSET(4) NUMBITS(11)"]
BITS3_0["Bits 3:0SGIINTIDOFFSET(0) NUMBITS(4)"]
end
BIT15 --> BITS14_4
Sources: src/regs/gicd_sgir.rs(L21 - L58)
Register Field Definitions
Core Field Components
| Field | Bits | Type | Purpose |
|---|---|---|---|
| TargetListFilter | 25:24 | Enum | Controls SGI routing behavior |
| CPUTargetList | 23:16 | Bitmap | Specifies target CPU interfaces |
| NSATT | 15 | Boolean | Security group selection |
| SGIINTID | 3:0 | Integer | SGI interrupt ID (0-15) |
Sources: src/regs/gicd_sgir.rs(L25 - L56)
TargetListFilter Modes
The TargetListFilter field defines four distinct targeting modes implemented as register bitfield values:
flowchart TD WRITE["Write to GICD_SGIR"] FILTER_CHECK["Check TargetListFilter bits 25:24"] MODE00["0b00: ForwardToCPUTargetListUse CPUTargetList bitmap"] MODE01["0b01: ForwardToAllExceptRequesterAll CPUs except requesting CPU"] MODE10["0b10: ForwardToRequesterOnly requesting CPU"] MODE11["0b11: ReservedUndefined behavior"] TARGET_BITMAP["Check CPUTargetList[7:0]Each bit = target CPU"] ALL_EXCEPT["Generate SGI for allactive CPUs except requester"] SELF_ONLY["Generate SGI only forrequesting CPU"] SGI_GEN["Generate SGI(s)with SGIINTID value"] ALL_EXCEPT --> SGI_GEN FILTER_CHECK --> MODE00 FILTER_CHECK --> MODE01 FILTER_CHECK --> MODE10 FILTER_CHECK --> MODE11 MODE00 --> TARGET_BITMAP MODE01 --> ALL_EXCEPT MODE10 --> SELF_ONLY SELF_ONLY --> SGI_GEN TARGET_BITMAP --> SGI_GEN WRITE --> FILTER_CHECK
Sources: src/regs/gicd_sgir.rs(L31 - L35)
Register Implementation Details
Rust Type Definition
The register is implemented as a write-only register using the tock-registers framework:
pub type GicdSgirReg = WriteOnly<u32, GICD_SGIR::Register>;
Register Bitfield Structure
The implementation uses the register_bitfields! macro to define field layouts and valid values:
| Constant | Value | Description |
|---|---|---|
| ForwardToCPUTargetList | 0b00 | Use explicit CPU target list |
| ForwardToAllExceptRequester | 0b01 | Broadcast except requester |
| ForwardToRequester | 0b10 | Self-targeting only |
| Reserved | 0b11 | Invalid/reserved value |
Sources: src/regs/gicd_sgir.rs(L18 - L62)
CPU Targeting Mechanisms
CPUTargetList Bitmap Interpretation
When TargetListFilter is set to ForwardToCPUTargetList (0b00), the CPUTargetList field operates as an 8-bit bitmap:
flowchart TD
subgraph subGraph0["CPUTargetList Bitmap (bits 23:16)"]
BIT23["Bit 23CPU 7"]
BIT22["Bit 22CPU 6"]
BIT21["Bit 21CPU 5"]
BIT20["Bit 20CPU 4"]
BIT19["Bit 19CPU 3"]
BIT18["Bit 18CPU 2"]
BIT17["Bit 17CPU 1"]
BIT16["Bit 16CPU 0"]
end
CPU7["GicCpuInterface 7"]
CPU6["GicCpuInterface 6"]
CPU5["GicCpuInterface 5"]
CPU4["GicCpuInterface 4"]
CPU3["GicCpuInterface 3"]
CPU2["GicCpuInterface 2"]
CPU1["GicCpuInterface 1"]
CPU0["GicCpuInterface 0"]
BIT16 --> CPU0
BIT17 --> CPU1
BIT18 --> CPU2
BIT19 --> CPU3
BIT20 --> CPU4
BIT21 --> CPU5
BIT22 --> CPU6
BIT23 --> CPU7
Sources: src/regs/gicd_sgir.rs(L37 - L41)
Security Extensions Support
NSATT Field Behavior
The NSATT (Non-Secure Attribute) field controls security group targeting when Security Extensions are implemented:
| NSATT Value | Group | Access Behavior |
|---|---|---|
| 0 | Group 0 | Forward SGI only if configured as Group 0 |
| 1 | Group 1 | Forward SGI only if configured as Group 1 |
Security Access Constraints
- Secure Access: Can write any value to NSATT field
- Non-Secure Access: Limited to Group 1 SGIs regardless of NSATT value written
Sources: src/regs/gicd_sgir.rs(L42 - L49)
SGI Interrupt ID Specification
The SGIINTID field specifies which of the 16 available Software Generated Interrupts (0-15) to trigger. This field directly maps to interrupt IDs in the SGI range:
flowchart TD SGIINTID_FIELD["SGIINTID bits 3:0"] SGI_RANGE["SGI Interrupt IDs"] ID0["ID 0: 0b0000"] ID1["ID 1: 0b0001"] ID2["ID 2: 0b0010"] ID3["ID 3: 0b0011"] DOTS["..."] ID15["ID 15: 0b1111"] CPU_INT0["CPU InterfaceSGI 0 pending"] CPU_INT3["CPU InterfaceSGI 3 pending"] CPU_INT15["CPU InterfaceSGI 15 pending"] ID0 --> CPU_INT0 ID15 --> CPU_INT15 ID3 --> CPU_INT3 SGIINTID_FIELD --> SGI_RANGE SGI_RANGE --> DOTS SGI_RANGE --> ID0 SGI_RANGE --> ID1 SGI_RANGE --> ID15 SGI_RANGE --> ID2 SGI_RANGE --> ID3
Sources: src/regs/gicd_sgir.rs(L52 - L56)
Implementation Considerations
Write-Only Register Characteristics
The GICD_SGIR register is implemented as WriteOnly<u32, GICD_SGIR::Register>, meaning:
- No read operations are supported
- Each write triggers immediate SGI processing
- Register state is not persistent
- Effects are immediate upon write completion
Reserved Field Handling
The implementation includes two reserved field ranges that must be handled appropriately:
- Bits 31:26: Reserved, implementation behavior undefined
- Bits 14:4: Reserved, Should Be Zero (SBZ)
Sources: src/regs/gicd_sgir.rs(L24 - L61)
Development and Integration
Relevant source files
This document provides a comprehensive guide for developers on integrating and working with the arm_gicv2 crate. It covers the development workflow, dependency management, build configuration, and quality assurance processes that support this hardware abstraction layer.
For specific technical details about crate dependencies and feature configuration, see Crate Configuration and Dependencies. For detailed information about the build system and CI/CD pipeline, see Build System and Development Workflow.
Integration Overview
The arm_gicv2 crate is designed as a foundational component for ARM-based systems requiring interrupt controller management. The crate follows embedded Rust best practices with no_std compatibility and minimal dependencies.
Development Ecosystem Architecture
flowchart TD
subgraph subGraph3["Integration Targets"]
ARCEOS["ArceOS Operating System"]
EMBEDDED["Embedded Systems"]
HYPERVISORS["Hypervisors/VMMs"]
BARE_METAL["Bare Metal Applications"]
end
subgraph subGraph2["Target Platforms"]
AARCH64_BARE["aarch64-unknown-none-softfloat"]
X86_TEST["x86_64-unknown-linux-gnu (testing)"]
end
subgraph subGraph1["Core Dependencies"]
TOCK_REGS["tock-registers 0.8"]
RUST_NIGHTLY["Rust Nightly Toolchain"]
NO_STD["no_std Environment"]
end
subgraph subGraph0["Development Infrastructure"]
GITHUB["GitHub Repository"]
CI_PIPELINE["CI Workflow (.github/workflows/ci.yml)"]
DOCS_DEPLOY["GitHub Pages Documentation"]
CARGO_TOML["Cargo.toml Configuration"]
end
AARCH64_BARE --> ARCEOS
AARCH64_BARE --> BARE_METAL
AARCH64_BARE --> EMBEDDED
AARCH64_BARE --> HYPERVISORS
CARGO_TOML --> CI_PIPELINE
CARGO_TOML --> TOCK_REGS
CI_PIPELINE --> AARCH64_BARE
CI_PIPELINE --> DOCS_DEPLOY
CI_PIPELINE --> X86_TEST
RUST_NIGHTLY --> NO_STD
TOCK_REGS --> RUST_NIGHTLY
Sources: Cargo.toml(L1 - L19) .github/workflows/ci.yml(L1 - L56)
Package Configuration and Metadata
The crate is configured for broad compatibility across the embedded systems ecosystem. Key configuration elements include:
| Configuration | Value | Purpose |
|---|---|---|
| Edition | 2021 | Modern Rust language features |
| License | Triple-licensed (GPL-3.0-or-later, Apache-2.0, MulanPSL-2.0) | Maximum compatibility |
| Categories | embedded,no-std,hardware-support,os | Clear ecosystem positioning |
| Keywords | arceos,arm,aarch64,gic,interrupt-controller | Discoverability |
Sources: Cargo.toml(L4 - L12)
Development Workflow Integration
Dependency Management Strategy
The crate maintains a minimal dependency footprint with a single external dependency:
flowchart TD ARM_GICV2["arm_gicv2 crate"] TOCK_REGS["tock-registers 0.8"] REG_ABSTRACTION["Hardware Register Abstractions"] SAFE_MMIO["Safe Memory-Mapped I/O"] EL2_FEATURE["el2 feature flag"] HYPERVISOR_SUPPORT["Hypervisor Extensions"] ARM_GICV2 --> EL2_FEATURE ARM_GICV2 --> TOCK_REGS EL2_FEATURE --> HYPERVISOR_SUPPORT REG_ABSTRACTION --> SAFE_MMIO TOCK_REGS --> REG_ABSTRACTION
The tock-registers dependency provides type-safe hardware register access patterns essential for reliable interrupt controller management. The optional el2 feature enables hypervisor-specific functionality without imposing overhead on standard use cases.
Sources: Cargo.toml(L14 - L18)
Build Target Configuration
The development process supports multiple build targets with different purposes:
flowchart TD
subgraph subGraph2["Build Process"]
CARGO_BUILD["cargo build --target TARGET --all-features"]
CARGO_TEST["cargo test --target x86_64-unknown-linux-gnu"]
CARGO_CLIPPY["cargo clippy --target TARGET --all-features"]
end
subgraph subGraph1["Development Target"]
X86_64["x86_64-unknown-linux-gnu"]
UNIT_TESTING["Unit testing execution"]
end
subgraph subGraph0["Primary Target"]
AARCH64["aarch64-unknown-none-softfloat"]
BARE_METAL_USE["Bare metal ARM64 systems"]
end
AARCH64 --> BARE_METAL_USE
AARCH64 --> CARGO_BUILD
AARCH64 --> CARGO_CLIPPY
Sources: .github/workflows/ci.yml(L12 - L30)
Quality Assurance Process
Continuous Integration Pipeline
The CI system ensures code quality through multiple validation stages:
| Stage | Tool | Command | Purpose |
|---|---|---|---|
| Format Check | rustfmt | cargo fmt --all -- --check | Code style consistency |
| Linting | clippy | cargo clippy --target TARGET --all-features | Code quality analysis |
| Build Verification | cargo | cargo build --target TARGET --all-features | Compilation validation |
| Unit Testing | cargo | cargo test --target x86_64-unknown-linux-gnu | Functional verification |
Sources: .github/workflows/ci.yml(L22 - L30)
Documentation Generation and Deployment
flowchart TD
subgraph subGraph1["Quality Gates"]
BROKEN_LINKS["rustdoc::broken_intra_doc_links"]
MISSING_DOCS["missing-docs warnings"]
end
subgraph subGraph0["Documentation Workflow"]
DOC_BUILD["cargo doc --no-deps --all-features"]
DOC_FLAGS["RUSTDOCFLAGS validation"]
INDEX_GEN["index.html generation"]
GITHUB_PAGES["GitHub Pages deployment"]
end
DOC_BUILD --> DOC_FLAGS
DOC_BUILD --> INDEX_GEN
DOC_FLAGS --> BROKEN_LINKS
DOC_FLAGS --> MISSING_DOCS
INDEX_GEN --> GITHUB_PAGES
The documentation pipeline enforces strict quality standards, treating broken links and missing documentation as errors to ensure comprehensive API coverage.
Sources: .github/workflows/ci.yml(L32 - L55)
Platform Compatibility Matrix
The crate supports multiple deployment scenarios with varying feature requirements:
| Use Case | Target Platform | Features Required | Integration Pattern |
|---|---|---|---|
| Embedded Systems | aarch64-unknown-none-softfloat | None | Direct hardware access |
| Hypervisors | aarch64-unknown-none-softfloat | el2 | Extended privilege operations |
| Operating Systems | aarch64-unknown-none-softfloat | Optionalel2 | Kernel-level integration |
| Development/Testing | x86_64-unknown-linux-gnu | All | Cross-compilation testing |
This matrix guides developers in selecting appropriate configuration options for their specific integration requirements.
Sources: Cargo.toml(L17 - L18) .github/workflows/ci.yml(L12 - L29)
Crate Configuration and Dependencies
Relevant source files
This document covers the configuration, dependencies, and build settings of the arm_gicv2 crate. It explains how the crate's dependencies enable hardware register abstractions, the purpose of feature flags, and the package metadata that defines its target use cases.
For information about the development workflow and build targets, see Build System and Development Workflow.
Dependencies Overview
The arm_gicv2 crate has a minimal dependency footprint designed for embedded and systems programming environments. The primary dependency provides safe hardware register abstractions while maintaining no_std compatibility.
Dependency Configuration
flowchart TD
subgraph register_usage["Register Implementation Usage"]
register_trait["register::Register trait"]
readwrite_trait["register::ReadWrite trait"]
field_trait["register::Field trait"]
localregister["register::LocalRegisterCopy"]
end
subgraph external_deps["External Dependencies"]
tock_registers["tock-registers 0.8"]
end
subgraph arm_gicv2_crate["arm_gicv2 Crate"]
lib["lib.rs"]
gic_v2["gic_v2.rs"]
regs_mod["regs/ module"]
end
field_trait --> regs_mod
lib --> gic_v2
lib --> regs_mod
localregister --> gic_v2
readwrite_trait --> gic_v2
register_trait --> gic_v2
tock_registers --> field_trait
tock_registers --> localregister
tock_registers --> readwrite_trait
tock_registers --> register_trait
Dependencies and their roles in hardware abstraction
Sources: Cargo.toml(L14 - L15)
The tock-registers dependency provides the foundation for type-safe hardware register access throughout the codebase. This dependency enables the creation of register field definitions and memory-mapped I/O operations without runtime overhead.
| Dependency | Version | Purpose | Usage Location |
|---|---|---|---|
| tock-registers | 0.8 | Hardware register abstractions and type-safe MMIO | gic_v2.rs,regs/module |
Feature Configuration
The crate defines feature flags to enable optional functionality based on the target execution environment and privilege level requirements.
Feature Flag Architecture
flowchart TD
subgraph target_environments["Target Environments"]
bare_metal["Bare Metal (EL1)"]
hypervisor["Hypervisor (EL2)"]
vmm["Virtual Machine Monitor"]
end
subgraph conditional_code["Conditional Code Compilation"]
hypervisor_support["Hypervisor Support"]
el2_registers["EL2-specific Registers"]
dir_register["DIR Register Access"]
end
subgraph feature_flags["Feature Flags"]
el2_feature["el2 = []"]
end
bare_metal --> el2_feature
el2_feature --> dir_register
el2_feature --> el2_registers
el2_feature --> hypervisor_support
hypervisor_support --> hypervisor
hypervisor_support --> vmm
Feature flag configuration and target environments
Sources: Cargo.toml(L17 - L18)
EL2 Feature Details
The el2 feature flag is an empty feature that serves as a compilation flag to enable hypervisor-specific functionality:
- Purpose: Enable Exception Level 2 (hypervisor) specific register access
- Implementation: Conditional compilation using
#[cfg(feature = "el2")] - Target Use Cases: Hypervisors, Virtual Machine Monitors, Type-1 virtualization
Package Metadata and Configuration
The crate's package configuration defines its identity, licensing, and target ecosystem integration.
Package Identity Configuration
flowchart TD
subgraph licensing["Licensing Options"]
apache2["Apache-2.0"]
subgraph ecosystem_integration["Ecosystem Integration"]
homepage["homepage = arceos-org/arceos"]
repository["repository = arceos-org/arm_gicv2"]
documentation["documentation = docs.rs/arm_gicv2"]
gpl3["GPL-3.0-or-later"]
mulan["MulanPSL-2.0"]
end
end
subgraph categorization["Crate Categorization"]
keywords["keywords = [arceos, arm, aarch64, gic, interrupt-controller]"]
categories["categories = [embedded, no-std, hardware-support, os]"]
end
subgraph package_metadata["Package Metadata"]
name["name = arm_gicv2"]
version["version = 0.1.0"]
edition["edition = 2021"]
authors["authors = Yuekai Jia"]
end
Package configuration and ecosystem integration
Sources: Cargo.toml(L1 - L12)
Metadata Configuration Table
| Configuration | Value | Purpose |
|---|---|---|
| Name | arm_gicv2 | Crate identifier for Cargo registry |
| Version | 0.1.0 | Semantic version following initial release |
| Edition | 2021 | Rust language edition for modern features |
| Description | ARM GICv2 register definitions and operations | Concise functionality summary |
| License | Triple-licensed (GPL-3.0/Apache-2.0/MulanPSL-2.0) | Flexible licensing for diverse use cases |
Target Environment Compatibility
The crate configuration explicitly targets embedded and systems programming environments through its category assignments and dependency choices.
Environment Compatibility Matrix
flowchart TD
subgraph runtime_requirements["Runtime Requirements"]
no_heap["No Heap Allocation"]
no_std_lib["No Standard Library"]
mmio_access["Memory-Mapped I/O"]
privileged_mode["Privileged Execution"]
end
subgraph compatibility_targets["Compatibility Targets"]
no_std_env["no_std Environment"]
embedded_sys["Embedded Systems"]
bare_metal["Bare Metal Applications"]
hypervisors["Hypervisors/VMMs"]
end
subgraph architecture_support["Architecture Support"]
aarch64["aarch64 (ARM64)"]
cortex_a["Cortex-A Series"]
armv7["ARMv7 (32-bit)"]
end
aarch64 --> cortex_a
armv7 --> cortex_a
no_std_env --> bare_metal
no_std_env --> embedded_sys
no_std_env --> hypervisors
Target environment compatibility and requirements
Sources: Cargo.toml(L12) Cargo.toml(L6)
Category Alignment
The crate's categories directly reflect its intended use cases:
embedded: Targeting resource-constrained embedded systemsno-std: Compatible withno_stdenvironments without standard libraryhardware-support: Provides low-level hardware abstractionos: Suitable for operating system and kernel development
Build Configuration Implications
The minimal dependency configuration and feature design have specific implications for build environments and integration scenarios.
Integration Requirements
| Requirement | Configuration Source | Implication |
|---|---|---|
| Memory-mapped I/O access | tock-registersdependency | Requires privileged execution mode |
| ARM architecture | Package keywords and categories | Limited to ARM-based systems |
| no_stdcompatibility | Categories and dependency choice | Suitable for kernel/bootloader integration |
| Optional hypervisor support | el2feature flag | Configurable privilege level support |
Sources: Cargo.toml(L11 - L12) Cargo.toml(L14 - L15) Cargo.toml(L17 - L18)
Build System and Development Workflow
Relevant source files
This document covers the automated build system, continuous integration pipeline, code quality assurance processes, and development workflow for the arm_gicv2 crate. It details the GitHub Actions configuration, build targets, testing strategies, and documentation generation processes that ensure code quality and maintainability.
For information about crate dependencies and feature configuration, see Crate Configuration and Dependencies.
CI/CD Pipeline Overview
The project uses GitHub Actions for continuous integration and deployment, with a comprehensive pipeline that ensures code quality, compatibility, and automated documentation deployment.
CI/CD Pipeline Architecture
flowchart TD
subgraph DOC_JOB_STEPS["Documentation Job Steps"]
DOC_CHECKOUT["actions/checkout@v4"]
DOC_TOOLCHAIN["dtolnay/rust-toolchain@nightly"]
DOC_BUILD["cargo doc --no-deps --all-features"]
GH_PAGES["JamesIves/github-pages-deploy-action@v4"]
end
subgraph CI_JOB_STEPS["CI Job Steps"]
CHECKOUT["actions/checkout@v4"]
TOOLCHAIN["dtolnay/rust-toolchain@nightly"]
VERSION_CHECK["Check rust version"]
FORMAT["cargo fmt --all -- --check"]
CLIPPY["cargo clippy --target aarch64-unknown-none-softfloat"]
BUILD["cargo build --target aarch64-unknown-none-softfloat"]
TEST["cargo test --target x86_64-unknown-linux-gnu"]
end
PR["Pull Request"]
GHA["GitHub Actions Trigger"]
PUSH["Push to Main"]
CI_JOB["ci job"]
DOC_JOB["doc job"]
BUILD --> TEST
CHECKOUT --> TOOLCHAIN
CI_JOB --> CHECKOUT
CLIPPY --> BUILD
DOC_BUILD --> GH_PAGES
DOC_CHECKOUT --> DOC_TOOLCHAIN
DOC_JOB --> DOC_CHECKOUT
DOC_TOOLCHAIN --> DOC_BUILD
FORMAT --> CLIPPY
GHA --> CI_JOB
GHA --> DOC_JOB
PR --> GHA
PUSH --> GHA
TOOLCHAIN --> VERSION_CHECK
VERSION_CHECK --> FORMAT
Sources: .github/workflows/ci.yml(L1 - L56)
Job Configuration Matrix
The CI pipeline uses a strategy matrix to test against multiple configurations:
| Parameter | Values |
|---|---|
| rust-toolchain | nightly |
| targets | aarch64-unknown-none-softfloat |
The pipeline is configured for fail-fast: false, allowing all matrix combinations to complete even if one fails.
Sources: .github/workflows/ci.yml(L7 - L12)
Build Configuration
Target Platforms
The build system supports multiple target platforms for different use cases:
flowchart TD
subgraph TOOLCHAIN_COMPONENTS["Toolchain Components"]
RUST_SRC["rust-src"]
CLIPPY_COMP["clippy"]
RUSTFMT["rustfmt"]
end
subgraph USE_CASES["Use Cases"]
EMBEDDED["Embedded Systems"]
BARE_METAL["Bare Metal Applications"]
TESTING["Unit Testing"]
end
subgraph BUILD_TARGETS["Build Targets"]
AARCH64["aarch64-unknown-none-softfloat"]
X86_TEST["x86_64-unknown-linux-gnu"]
end
AARCH64 --> BARE_METAL
AARCH64 --> EMBEDDED
CLIPPY_COMP --> AARCH64
RUSTFMT --> AARCH64
RUST_SRC --> AARCH64
X86_TEST --> TESTING
Sources: .github/workflows/ci.yml(L12 - L19)
Primary Build Target
aarch64-unknown-none-softfloat: The primary target for ARM64 bare-metal applications- Used for clippy linting:
cargo clippy --target aarch64-unknown-none-softfloat - Used for building:
cargo build --target aarch64-unknown-none-softfloat - Supports all features:
--all-featuresflag
Testing Target
x86_64-unknown-linux-gnu: Used exclusively for unit testing- Conditional execution: Only runs when
matrix.targets == 'x86_64-unknown-linux-gnu' - Command:
cargo test --target x86_64-unknown-linux-gnu -- --nocapture
Sources: .github/workflows/ci.yml(L25 - L30)
Code Quality Assurance
The project implements multiple layers of code quality checks to maintain consistency and catch issues early.
Code Quality Pipeline
sequenceDiagram
participant Developer as "Developer"
participant GitRepository as "Git Repository"
participant CIPipeline as "CI Pipeline"
participant cargofmt as "cargo fmt"
participant cargoclippy as "cargo clippy"
participant cargobuild as "cargo build"
Developer ->> GitRepository: Push/PR
GitRepository ->> CIPipeline: Trigger workflow
CIPipeline ->> cargofmt: Check code format
cargofmt -->> CIPipeline: Pass/Fail
CIPipeline ->> cargoclippy: Run linter
cargoclippy -->> CIPipeline: Pass/Fail (with exceptions)
CIPipeline ->> cargobuild: Build target
cargobuild -->> CIPipeline: Pass/Fail
Sources: .github/workflows/ci.yml(L22 - L27)
Formatting Standards
- Tool:
cargo fmtwith--allflag - Enforcement:
--checkflag ensures code is properly formatted - Command:
cargo fmt --all -- --check - Failure Behavior: Pipeline fails if formatting is inconsistent
Linting Configuration
- Tool:
cargo clippywith comprehensive checks - Target-specific: Runs against
aarch64-unknown-none-softfloat - Features: Uses
--all-featuresto lint all code paths - Exceptions: Allows
clippy::new_without_defaultlint - Command:
cargo clippy --target aarch64-unknown-none-softfloat --all-features -- -A clippy::new_without_default
Sources: .github/workflows/ci.yml(L23 - L25)
Testing Strategy
Test Execution Model
flowchart TD
subgraph REASONS["Why x86_64 for Testing"]
FAST_EXECUTION["Faster execution on CI runners"]
STANDARD_LIB["Standard library availability"]
DEBUG_OUTPUT["Better debugging capabilities"]
end
subgraph TEST_CONDITIONS["Test Execution Conditions"]
TARGET_CHECK["matrix.targets == 'x86_64-unknown-linux-gnu'"]
UNIT_TESTS["cargo test"]
NOCAPTURE["--nocapture flag"]
end
DEBUG_OUTPUT --> NOCAPTURE
FAST_EXECUTION --> TARGET_CHECK
STANDARD_LIB --> TARGET_CHECK
TARGET_CHECK --> UNIT_TESTS
UNIT_TESTS --> NOCAPTURE
Sources: .github/workflows/ci.yml(L28 - L30)
Test Configuration
- Conditional Execution: Tests only run for x86_64 target
- Output Visibility:
--nocaptureflag ensures test output is visible - Target Specification: Explicit target to avoid cross-compilation issues during testing
Documentation System
Documentation Generation Pipeline
The documentation system automatically builds and deploys API documentation to GitHub Pages.
flowchart TD
subgraph DEPLOYMENT["Deployment"]
GH_PAGES_ACTION["JamesIves/github-pages-deploy-action@v4"]
SINGLE_COMMIT["single-commit: true"]
TARGET_FOLDER["folder: target/doc"]
end
subgraph DOC_BUILD["Documentation Build"]
CARGO_DOC["cargo doc --no-deps --all-features"]
INDEX_GEN["Generate index.html redirect"]
RUSTDOC_FLAGS["RUSTDOCFLAGS validation"]
end
subgraph DOC_TRIGGERS["Documentation Triggers"]
DEFAULT_BRANCH["Push to default branch"]
PR_EVENT["Pull Request"]
end
CARGO_DOC --> INDEX_GEN
DEFAULT_BRANCH --> CARGO_DOC
GH_PAGES_ACTION --> SINGLE_COMMIT
INDEX_GEN --> RUSTDOC_FLAGS
PR_EVENT --> CARGO_DOC
RUSTDOC_FLAGS --> GH_PAGES_ACTION
SINGLE_COMMIT --> TARGET_FOLDER
Sources: .github/workflows/ci.yml(L32 - L55)
Documentation Configuration
Build Settings
- Command:
cargo doc --no-deps --all-features - Scope: Excludes dependencies, includes all features
- Output: Generated to
target/docdirectory
Quality Enforcement
- Environment Variable:
RUSTDOCFLAGS="-D rustdoc::broken_intra_doc_links -D missing-docs" - Broken Links: Treats broken documentation links as errors
- Missing Documentation: Enforces documentation completeness
Index Generation
printf '<meta http-equiv="refresh" content="0;url=%s/index.html">' $(cargo tree | head -1 | cut -d' ' -f1) > target/doc/index.html
Sources: .github/workflows/ci.yml(L40 - L48)
Deployment Strategy
- Trigger: Only deploys from the default branch (typically
main) - Method: GitHub Pages deployment action
- Configuration:
single-commit: true- Keeps deployment history cleanbranch: gh-pages- Deploys to dedicated documentation branchfolder: target/doc- Source folder for deployment
Sources: .github/workflows/ci.yml(L49 - L55)
Development Environment Setup
Required Toolchain Components
| Component | Purpose |
|---|---|
| rust-src | Source code for cross-compilation |
| clippy | Linting and code analysis |
| rustfmt | Code formatting |
Toolchain Installation
The project uses the dtolnay/rust-toolchain action for consistent toolchain management:
# Equivalent local setup
rustup toolchain install nightly
rustup component add rust-src clippy rustfmt
rustup target add aarch64-unknown-none-softfloat
Sources: .github/workflows/ci.yml(L15 - L19)
Version Verification
The CI pipeline includes a version check step to ensure toolchain consistency:
- Command:
rustc --version --verbose - Purpose: Provides detailed version information for debugging and reproducibility
Sources: .github/workflows/ci.yml(L20 - L21)
Local Development Workflow
Developers should run these commands locally before pushing:
# Format check
cargo fmt --all -- --check
# Linting
cargo clippy --target aarch64-unknown-none-softfloat --all-features -- -A clippy::new_without_default
# Build verification
cargo build --target aarch64-unknown-none-softfloat --all-features
# Documentation build (optional)
cargo doc --no-deps --all-features
Git Configuration
The project uses standard Git ignore patterns for Rust projects:
| Pattern | Purpose |
|---|---|
| /target | Build artifacts |
| /.vscode | Editor configuration |
| .DS_Store | macOS system files |
| Cargo.lock | Dependency lock file (library crate) |
Sources: .gitignore(L1 - L4)