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
running
flag 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_down
flag 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
Arc
for 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 clippy
with 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
rustfmt
with--check
flag - Linting: Comprehensive linting through
clippy
with 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-features
to 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_COUNT
for each successfully started VM - Wait until all VMs are stopped (when
RUNNING_VM_COUNT
reaches 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
AxVMConfig
for 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
VMVCpus
structure 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_run
entry 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
TaskExt
containing 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
CpuUp
exit):
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:
VMVCpus
maintains 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_COUNT
reaches 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_path
andbios_load_addr
for 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_path
andbios_load_addr
for 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_SEC
andaxhal::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
crates
directory - 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_path
to the path of the kernel image in the filesystem - Set
entry_point
to the entry address of the kernel image - Set
kernel_load_addr
to 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_path
to the relative/absolute path of the kernel image in the workspace - Set
entry_point
to the entry address of the kernel image - Set
kernel_load_addr
to 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.toml
configs/vms/arceos-x86_64.toml
configs/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.toml
configs/vms/nimbos-x86_64.toml
configs/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.toml
with device tree atconfigs/vms/linux-qemu.dts
- SMP configuration: Configuration available at
configs/vms/linux-qemu-aarch64-smp2.toml
with 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_path
to the path of the kernel image in the filesystem - Configure
entry_point
andkernel_load_addr
appropriately
- 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_path
to the path of the kernel image in your workspace - Configure
entry_point
andkernel_load_addr
appropriately
- 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-objcopy
andrust-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
fs
feature for Cargo - Sets the
AX_CONFIG_PATH
environment 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
--repo
option. - 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
VMVCpus
structure 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_COUNT
atomic counter. - Wait for Completion: The VMM waits on a wait queue until all VMs have stopped (
RUNNING_VM_COUNT
reaches 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_run
function 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_COUNT
reaches 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 defconfig
for 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}.toml
files
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_64
for x86_64 architectureqemu-system-aarch64
for ARM/aarch64 architectureqemu-system-riscv64
for 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
act
tool 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_std
compatible 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_base
as 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
alloc
feature for collections without requiring standard library - Serialization support: Serde with
derive
andalloc
features enabled - Conditional compilation:
cfg-if
for 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
: SuppliesGuestPhysAddr
and address range management for hypervisor memory operationsmemory_addr
: Low-level memory address manipulation utilities
External Dependencies
serde
: Enables serialization of device types withderive
macros andalloc
feature 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
alloc
crate 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:
GuestPhysAddr
from theaxaddrspace
crate represents individual guest physical addressesAddrRange<GuestPhysAddr>
from thememory_addr
crate 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
: Removesstd
dependencyderive
feature: Enables#[derive(Serialize, Deserialize)]
macros forEmuDeviceType
alloc
feature: 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
GuestPhysAddr
into 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 aGuestPhysAddr
parameter indicating which guest physical address is being read - Write Access:
handle_write()
receives aGuestPhysAddr
parameter 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
GuestPhysAddr
address space managed by the hypervisor - Consistent Error Handling: Device operations return standardized
AxResult
types 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_default
is 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
RUSTDOCFLAGS
to 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_top
field of the VCPU structure - SP_EL0 Handling: Saves host's
SP_EL0
to 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_EL0
is 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::CpuDown
PSCI_FN_CPU_ON
→AxVCpuExitReason::CpuUp
PSCI_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 7
ensures 128-byte vector alignment - Context Save:
SAVE_REGS_FROM_EL1
preserves 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_top
address passed inx0
- Frame Positioning: Adjusts stack pointer to point to guest
TrapFrame
base - Register Restoration: Uses
RESTORE_REGS_INTO_EL1
to restore guest state - Guest Entry: Executes
eret
to 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:
x0
contains a valid SMC function number per the SMC Calling Convention- Arguments
x1
,x2
,x3
are 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:
inout
constraints for registersx0-x3
to 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
AxArchVCpu
operations 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
AxVCpuExitReason
from theirrun()
methods - GuestPhysAddr: Physical address representation from
axaddrspace
crate - 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
MmioRead
src/exit.rs(L78 - L88) captures reads with target register informationMmioWrite
src/exit.rs(L89 - L97) includes the data being written- Both specify the
GuestPhysAddr
andAccessWidth
for precise emulation
Page Faults
NestedPageFault
src/exit.rs(L153 - L161) occurs during EPT violations on x86 or stage-2 faults on ARM- Includes
MappingFlags
to 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:
IoRead
src/exit.rs(L126 - L134) for reading from I/O portsIoWrite
src/exit.rs(L135 - L145) for writing to I/O ports- Both use
Port
type (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:
ExternalInterrupt
src/exit.rs(L146 - L152) captures hardware interrupts with vector numberHalt
src/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
CpuUp
src/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:
Nothing
src/exit.rs(L199 - L202) indicates no special handling neededFailEntry
src/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
/eax
registers - 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_id
field 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
Drop
implementation 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
percpu
crate 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
Drop
implementation 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:
Drop
implementation 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_id
isNone
(not initialized) - Uses
unsafe
code 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 clippy
with custom allow rules .github/workflows/ci.yml(L25) - Build verification: Full compilation for all target architectures .github/workflows/ci.yml(L27)
- Documentation:
cargo doc
with 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.lock
sinceaxvcpu
is 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
,percpu
use semantic versioning for API stability - Ecosystem dependency:
axaddrspace
uses 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_default
lint 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-gnu
target - Output: Detailed test output with
--nocapture
flag
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-pages
branch - Trigger: Only on default branch pushes
- Method: Single-commit deployment using
JamesIves/github-pages-deploy-action@v4
- Source:
target/doc
directory
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-gnu
for 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: false
to 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:
GuestVirtAddr
displays as"GVA:{address}"
GuestPhysAddr
displays 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
PhysFrame
for 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_4K
from thememory_addr
crate. - 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
AxMmHal
trait for memory frame allocation - Address Types: Operates on
GuestPhysAddr
andHostPhysAddr
types - 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_if
macro inarch/mod.rs
evaluates 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.rs
resolves 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
VALID
bit - Write: Controlled by absence of
S2AP_WO
bit - Execute: Controlled by absence of
XN
bit - 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
WriteBack
caching for optimal performance - Device memory regions use
Uncached
type to prevent caching side effects - The conversion is bidirectional between
MappingFlags
andEPTFlags
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
ExtendedPageTableMetadata
for architectural parameters - Uses
EPTEntry
for page table entries with Intel-specific features - Accepts a generic hardware abstraction layer
H
- Inherits all functionality from the
PageTable64
generic 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
GuestPhysAddr
for 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
Rv64PTE
type for RISC-V page table entries - page_table_multiarch: Supplies both the generic
PageTable64
framework 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
false
for both huge page flags inmap_region
- Always flushes the TLB during
unmap_region
operations - 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
PageIter4K
to 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 obtainHostPhysAddr
values - 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
clippy
with custom configuration allowingnew_without_default
warnings - 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_default
warnings using-A clippy::new_without_default
- Runs with
--all-features
to 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
cargo
commands for building and testing - Pre-commit Checks: Run
cargo fmt
,cargo clippy
, andcargo test
locally - CI Validation: All checks must pass in the automated CI pipeline
- Documentation: Ensure all public APIs are documented to meet
-D missing-docs
requirements
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:
VCpuConfig
src/vcpu.rs(L18 - L19) - Architecture-dependent configuration (currently empty)RISCVVCpuCreateConfig
src/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
hgatp
CSR (register 0x680) using inline assembly - Exception Analysis:
rust_detect_trap()
examinesscause
to 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::Unsupported
when 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
csrrw
instructions - Loads guest program counter into
sepc
- Sets trap vector (
stvec
) to_guest_exit
for handling guest traps - Stores
VmCpuRegisters
pointer insscratch
for 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
VmCpuRegisters
pointer fromsscratch
- Saves all guest general-purpose registers to the guest state structure
- Captures guest's
a0
register 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
VmCpuRegisters
pointer for fast access - During hypervisor execution: Contains the hypervisor's original
sscratch
value
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
a0
viasscratch
during guest entry - Guest's
a0
value recovered fromsscratch
during 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 << N
format 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-gnu
for host-based testing - Integration Tests: Build verification on
riscv64gc-unknown-none-elf
target - Test Output: Uses
--nocapture
flag 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-src
for cross-compilation support - Clippy: Static analysis and linting tool
- Rustfmt: Code formatting tool
- Target Support:
riscv64gc-unknown-none-elf
compilation 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_configs
field src/config.rs(L5 - L8) - Constructor function
new()
for initialization src/config.rs(L13 - L15) - Depends on
axvmconfig::EmulatedDeviceConfig
for 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 ofEmulatedDeviceConfig
objects - Storage: The configuration vector is stored in the
emu_configs
field - 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
Vec
collection - Integration: Seamless handoff to device management layer through public field access
- Memory Safety: Uses
alloc::vec::Vec
for safe dynamic memory management inno_std
environment
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:
AxVmDeviceConfig
structure coordinates withaxvmconfig
crate for VM-level device configurationEmulatedDeviceConfig
instances define individual device parameters within the VM configuration
Device Operation Integration:
BaseDeviceOps
trait fromaxdevice_base
defines the interface contract for all emulated devicesfind_dev()
method usesGuestPhysAddr
frommemory_addr
crate for address-based device lookuphandle_read()
andhandle_write()
methods returnAxResult<T>
using error types fromaxerrno
Address Space Integration:
GuestPhysAddr
andHostVirtAddr
types frommemory_addr
enable precise memory mapping- Integration with
axaddrspace
manages 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:
axdevice
specifiesdefault-features = false
foraxvmconfig
, 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
BaseDeviceOps
without modifying coreaxdevice
code - Additional VM configuration options can be added to
axvmconfig
without breaking existing device implementations - Address space management improvements in
axaddrspace
benefit 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
vmx
module - 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
GeneralRegisters
andXState
- Allocates
VmxRegion
with 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 usingvmlaunch
instructionvmx_resume()
: Subsequent VM entries usingvmresume
instruction- 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
GeneralRegisters
structure - 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:
NestedPageFaultInfo
with 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
GuestPageWalkInfo
with 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
rsp
would 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:
GeneralRegisters
stores 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 fmt
with--check
flag to ensure consistent code style - Linting: Runs
cargo clippy
with--all-features
and 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-pages
branch 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 orNone
if 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/doc
with 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/32
andhandle_write8/16/32
methods - Thread-safe state management through
VgicInner
mutex 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
id
field - 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_0000
to0x800_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
2289063
for 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_gicv2
ensures 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_if
crate for CPU virtualization interfaces - Potential use of
crate_interface
for 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:
0xFEE00000
to0xFEE00FFF
(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
0x800
to0x8FF
- 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 >> 4
converts 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_BASE
gives 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
DeliveryStatus
field 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:
LvtErrorRegisterMmio
andLvtCmciRegisterMmio
for direct hardware access - Local Copy Types:
LvtErrorRegisterLocal
andLvtCmciRegisterLocal
for 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_BIT
andGICD_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 fromTYPER
register bits [7:5]max_irqs()
: Calculates maximum supported interrupts fromTYPER
register bits [4:0]get_typer()
: Returns rawTYPER
register value for detailed configuration analysisget_iidr()
: Returns implementation identification fromIIDR
register
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_CTLR
register withGICC_CTLR_EN_BIT
to enable the interfaceGICC_PMR
register to maximum value (0xFFFFFFFF) to unmask all priority levels
EL2 Hypervisor Initialization
When the el2
feature is enabled, initialization additionally sets:
GICC_CTLR_EOIMODENS_BIT
in 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-registers
abstractions - 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_std
environments 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-features
flag
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 fmt
with--all
flag - Enforcement:
--check
flag ensures code is properly formatted - Command:
cargo fmt --all -- --check
- Failure Behavior: Pipeline fails if formatting is inconsistent
Linting Configuration
- Tool:
cargo clippy
with comprehensive checks - Target-specific: Runs against
aarch64-unknown-none-softfloat
- Features: Uses
--all-features
to lint all code paths - Exceptions: Allows
clippy::new_without_default
lint - 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:
--nocapture
flag 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/doc
directory
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)