arceos-guestvdev
A standalone hypervisor application running on ArceOS unikernel, with all dependencies sourced from crates.io. Implements guest virtual device support with timer virtualization, console I/O forwarding, and nested page fault (NPF) passthrough across three architectures.
This crate is derived from the h_3_0 tutorial crate in the ArceOS ecosystem, extending it to support multiple processor architectures. The h_3_0 crate runs u_6_0 (a preemptive multi-tasking demo) as the guest OS.
What It Does
The hypervisor (arceos-guestvdev) performs the following:
- Creates a guest address space with second-stage/nested page tables
- Pre-allocates guest RAM (16 MB on RISC-V, 2 MB on x86_64) to minimize NPF exits
- Loads a guest kernel (
gkernel) from a VirtIO block device (FAT32 filesystem) - Virtualizes timer device for guest preemptive scheduling (RISC-V: SBI SetTimer + hvip injection)
- Forwards console I/O via SBI/SVC/VMMCALL hypercalls
- Handles nested page faults with MMIO passthrough mapping
- Demonstrates the h_3_0 control flow: loop → guest entry → VM exit → handle → repeat
The guest kernel (gkernel) behavior varies by architecture:
- RISC-V 64: Full ArceOS multi-tasking demo (u_6_0 style) with CFS scheduler and preemptive scheduling. Two worker threads communicate via a shared queue.
- AArch64: Bare-metal EL0 program that tests virtual device interaction (console I/O via SVC, PFlash read via NPF).
- x86_64: Bare-metal long-mode program that tests virtual device interaction (console I/O via VMMCALL, PFlash read via NPF).
Architecture Support
| Architecture | Virtualization | Guest Mode | Virtual Devices | Shutdown Mechanism |
|---|---|---|---|---|
| RISC-V 64 | H-extension (hgatp) | VS-mode | Timer (SBI), Console (SBI PutChar), PFlash (NPF) | SBI ecall (Reset) |
| AArch64 | EL1→EL0 (TTBR0) | EL0 | Console (SVC), PFlash (NPF) | SVC hypercall (exit) |
| x86_64 | AMD SVM (NPT) | Long mode | Console (VMMCALL), PFlash (NPF) | vmmcall (PSCI) |
Note on RISC-V Timer Virtualization: The hypervisor intercepts SBI SetTimer calls, forwards them to the host SBI (OpenSBI), and injects virtual supervisor timer interrupts to the guest via
hvip. This enables the guest's CFS scheduler to perform preemptive context switching between worker threads.
Note on AArch64: Because the ArceOS platform crate drops from EL2 to EL1 during boot, the hypervisor runs at EL1 and the guest at EL0. Guest page tables are managed via TTBR0_EL1, and data aborts from EL0 serve as the equivalent of nested page faults.
Note on x86_64 AMD SVM: The hypervisor uses VMRUN/VMEXIT with hardware Nested Page Tables (NPT). Guest GPRs (RCX–R15) are saved/restored by software via an
SvmGuestGprsstructure. PFlash is emulated in software.
Control Flow (h_3_0 Compatible)
Hypervisor starts
│
├─ Create guest address space (AddrSpace)
├─ Pre-allocate guest RAM
├─ Load guest binary from /sbin/gkernel
├─ Setup vCPU context
│
└─ VM Run Loop ──────────────────────┐
│ │
├─ Enter guest (vmrun/eret) │
│ │
├─ SBI call (PutChar/SetTimer) │
│ └─ Forward to host SBI ────┘
│ │
├─ Timer interrupt │
│ └─ Inject to guest (hvip) ──┘
│ │
├─ Guest accesses unmapped addr │
│ └─ NPF / Page Fault exit │
│ └─ Map the page ────────┘
│
├─ Guest issues shutdown call
│ └─ Shutdown exit
│ └─ Break loop
│
└─ "Hypervisor ok!"
Comparison with Related Crates
| Crate | Role | Description |
|---|---|---|
| arceos-guestvdev (this) | Hypervisor | Runs guest with virtual device support (like h_3_0) |
| arceos-guestaspace | Hypervisor | Runs guest with NPF handling (like h_2_0) |
| arceos-guestmode | Hypervisor | Runs minimal guest, single VM exit (like h_1_0) |
Prerequisites
-
Rust nightly toolchain (edition 2024)
-
Bare-metal targets
-
QEMU (with virtualization support)
# Ubuntu/Debian # macOS (Homebrew) -
rust-objcopy (from
cargo-binutils)
Quick Start
# Install cargo-clone
# Get source code from crates.io
# Build and run on RISC-V 64 (default)
# Build and run on other architectures
# Build only (no QEMU)
Expected Output
RISC-V 64
Starting virtualization...
Pre-allocating 16 MB guest RAM at 0x80000000...
VM created success, loading images...
app: /sbin/gkernel
Loaded XXXXX bytes from /sbin/gkernel
bsp_entry: 0x80200000; ept: 0x...
Entering VM run loop...
d8888 .d88888b. .d8888b. (guest ArceOS banner)
...
Multi-task(Preemptible) is starting ...
worker1 ... ThreadId(2)
worker1 [0]
worker2 ... ThreadId(3)
worker2: nothing to do!
worker1 [1]
...
worker2 [256]
worker2 ok!
Wait for workers to exit ...
worker1 ok!
Multi-task(Preemptible) ok!
Guest: SBI SRST shutdown
Shutdown vm normally!
AArch64
Starting virtualization...
app: /sbin/gkernel
...
Entering VM run loop...
d8888 .d88888b. .d8888b. (guest ArceOS banner)
...
arch = aarch64
platform = aarch64-qemu-virt
smp = 1
Virtual Device (vdev) Test
Reading PFlash at physical address 0x04000000...
Try to access pflash dev region [0x04000000], got 0x646c6670
Got pflash magic: pfld
Shutdown vm normally!
Hypervisor ok!
x86_64 (AMD SVM)
Starting virtualization...
Pre-allocating 2048 KB guest RAM at GPA 0x0...
VM created success, loading images...
app: /sbin/gkernel
Loaded XXXX bytes from /sbin/gkernel
Entering VM run loop...
d8888 .d88888b. .d8888b.
...
arch = x86_64
platform = x86-pc
smp = 1
Virtual Device (vdev) Test
Reading PFlash at physical address 0xFFC00000...
Try to access pflash dev region [0xFFC00000], got 0x646c6670
Got pflash magic: pfld
Shutdown vm normally!
Hypervisor ok!
Project Structure
app-guestvdev/
├── .cargo/
│ └── config.toml # cargo xtask alias & AX_CONFIG_PATH
├── payload/
│ └── gkernel/ # Guest kernel payload
│ ├── Cargo.toml # riscv64: ArceOS multitask; others: bare-metal
│ └── src/main.rs # Architecture-specific guest code
├── xtask/
│ └── src/main.rs # Build/run tool (disk image, pflash, QEMU)
├── configs/
│ ├── riscv64.toml # Platform config for riscv64-qemu-virt
│ ├── aarch64.toml # Platform config for aarch64-qemu-virt
│ └── x86_64.toml # Platform config for x86-pc
├── src/
│ ├── main.rs # Hypervisor entry: h_3_0 style VM exit handling
│ ├── loader.rs # Guest binary loader (FAT32 → address space)
│ ├── vcpu.rs # RISC-V vCPU context (registers, guest.S)
│ ├── guest.S # RISC-V guest entry/exit assembly
│ ├── regs.rs # RISC-V general-purpose registers
│ ├── csrs.rs # RISC-V hypervisor CSR definitions
│ ├── sbi/ # SBI message parsing (base, reset, fence, ...)
│ ├── aarch64/ # AArch64 EL1→EL0 vCPU, guest.S, SVC handling
│ └── x86_64/ # AMD SVM: VMCB, GPR save/restore, vmrun assembly
├── build.rs # Linker script auto-detection
├── Cargo.toml
├── rust-toolchain.toml
└── README.md
How It Works
cargo xtask run --arch <ARCH>
- Copies
configs/<ARCH>.toml→.axconfig.toml - Builds the guest payload (
gkernel) for the target architecture - Creates a 64MB FAT32 disk image with
/sbin/gkernel - For riscv64/aarch64: creates a pflash image with "pfld" magic at offset 0
- Builds the hypervisor kernel with
--features axstd - Launches QEMU with VirtIO block device and pflash
VM Exit Handling
| Architecture | NPF Exit | SBI/Hypercall Exit | Timer Handling |
|---|---|---|---|
| RISC-V 64 | scause = 20/21/23 |
scause = 10 (VSupervisorEnvCall) |
SetTimer → hvip injection |
| AArch64 | ESR EC = 0x24 (Data Abort) | ESR EC = 0x15 (SVC) | N/A (bare-metal guest) |
| x86_64 SVM | VMEXIT 0x400 (NPF) | VMEXIT 0x81 (VMMCALL) | N/A (bare-metal guest) |
Key Dependencies
| Crate | Role |
|---|---|
axstd |
ArceOS standard library (no_std replacement) |
axhal |
Hardware abstraction layer (paging, traps) |
axmm |
Memory management (address spaces, page tables) |
axfs |
Filesystem access (FAT32 disk image) |
riscv |
RISC-V register access (riscv64 only) |
sbi-spec / sbi-rt |
SBI specification and runtime (riscv64 only) |
License
GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0