> $GITHUB_OUTPUT
-
- - name: Create GitHub Release
- uses: softprops/action-gh-release@v2
- with:
- draft: false
- prerelease: ${{ needs.verify-tag.outputs.is_prerelease == 'true' }}
- body: |
- ## Changes
- ${{ steps.release_notes.outputs.changelog }}
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-
- publish:
- name: Publish to crates.io
- runs-on: ubuntu-latest
- needs: [verify-tag, check]
- if: needs.verify-tag.outputs.should_release == 'true'
-
- steps:
- - name: Checkout code
- uses: actions/checkout@v4
-
- - name: Install Rust toolchain
- uses: dtolnay/rust-toolchain@nightly
-
- - name: Dry run publish
- run: cargo publish --dry-run
-
- - name: Publish to crates.io
- run: cargo publish --token ${{ secrets.CARGO_REGISTRY_TOKEN }}
+ needs: [check, test]
+ uses: arceos-hypervisor/axci/.github/workflows/release.yml@main
+ with:
+ verify_branch: true
+ verify_version: true
+ secrets:
+ CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index dc3b293..6a58ef8 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -1,3 +1,6 @@
+# Integration Test Workflow
+# References shared workflow from axci
+
name: Test
on:
@@ -7,44 +10,8 @@ on:
tags-ignore:
- '**'
pull_request:
- workflow_call:
+ workflow_dispatch:
jobs:
- load-config:
- name: Load CI Configuration
- runs-on: ubuntu-latest
- outputs:
- targets: ${{ steps.config.outputs.targets }}
- steps:
- - name: Checkout code
- uses: actions/checkout@v4
-
- - name: Load configuration
- id: config
- run: |
- TARGETS=$(jq -c '.targets' .github/config.json)
- echo "targets=$TARGETS" >> $GITHUB_OUTPUT
-
test:
- name: Test
- runs-on: ubuntu-latest
- needs: load-config
- strategy:
- fail-fast: false
- matrix:
- target: ${{ fromJson(needs.load-config.outputs.targets) }}
-
- steps:
- - name: Checkout code
- uses: actions/checkout@v4
-
- - name: Install Rust toolchain
- uses: dtolnay/rust-toolchain@nightly
-
- # - name: Run tests
- # run: cargo test --target ${{ matrix.target }} --all-features -- --nocapture
-
- # - name: Run doc tests
- # run: cargo test --target ${{ matrix.target }} --doc
- - name: Run tests
- run: echo "Tests are skipped!"
+ uses: arceos-hypervisor/axci/.github/workflows/test.yml@main
diff --git a/.gitignore b/.gitignore
index ff78c42..22d7b02 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,11 @@
/.vscode
.DS_Store
Cargo.lock
+
+# Test results (generated by shared test framework)
+/test-results/
+/test_repos/
+*.log
+
+# Downloaded test framework
+/scripts/.axci/
diff --git a/README.md b/README.md
index c626dd6..3fc8398 100644
--- a/README.md
+++ b/README.md
@@ -1,143 +1,181 @@
-# axaddrspace
+axaddrspace
-**ArceOS-Hypervisor guest VM address space management module**
+ArceOS-Hypervisor guest VM address space management module
-[](https://github.com/arceos-hypervisor/axaddrspace/actions/workflows/ci.yml)
-[](https://crates.io/crates/axaddrspace)
-[](LICENSE)
+
-## Overview
+[](https://crates.io/crates/axaddrspace)
+[](https://docs.rs/axaddrspace)
+[](https://www.rust-lang.org/)
+[](https://github.com/arceos-hypervisor/axaddrspace/blob/main/LICENSE)
-`axaddrspace` is a core component of the [ArceOS-Hypervisor](https://github.com/arceos-hypervisor/) project that provides guest virtual machine address space management capabilities. The crate implements nested page tables and address translation for hypervisor environments, supporting multiple architectures including x86_64, AArch64, and RISC-V.
+
-## Features
+English | [中文](README_CN.md)
-- **Multi-architecture support**: x86_64 (VMX EPT), AArch64 (Stage 2 page tables), and RISC-V nested page tables
-- **Flexible memory mapping backends**:
- - **Linear mapping**: For contiguous physical memory regions with known addresses
- - **Allocation mapping**: Dynamic allocation with optional lazy loading support
-- **Nested page fault handling**: Comprehensive page fault management for guest VMs
-- **Hardware abstraction layer**: Clean interface for memory management operations
-- **No-std compatible**: Designed for bare-metal hypervisor environments
+# Introduction
-## Architecture Support
+`axaddrspace` is the guest address space management crate for the
+[ArceOS-Hypervisor](https://github.com/arceos-hypervisor/) project. It provides
+nested page table management, guest physical address translation, memory
+mapping backends, and nested page fault handling for hypervisor environments.
-### x86_64
-- VMX Extended Page Tables (EPT)
-- Memory type configuration (WriteBack, Uncached, etc.)
-- Execute permissions for user-mode addresses
+This crate supports multiple architectures:
-### AArch64
-- VMSAv8-64 Stage 2 translation tables
-- Configurable MAIR_EL2 memory attributes
-- EL2 privilege level support
+- **x86_64** - VMX Extended Page Tables (EPT)
+- **AArch64** - Stage-2 page tables
+- **RISC-V** - Nested page tables based on the hypervisor extension
-### RISC-V
-- Nested page table implementation
-- Hypervisor fence instructions (`hfence.vvma`)
-- Sv39 metadata support
+Key capabilities include:
-## Core Components
+- **`AddrSpace`** - address space creation, mapping, unmapping, and translation
+- **`AxMmHal`** - hardware abstraction trait for frame allocation and address conversion
+- **Linear mapping backend** - map known contiguous host physical memory ranges
+- **Allocation mapping backend** - allocate frames eagerly or lazily on page faults
+- **Guest memory helpers** - translate guest addresses to accessible host buffers
-### Address Space Management
-The `AddrSpace` struct provides:
-- Virtual address range management
-- Page table root address tracking
-- Memory area organization
-- Address translation services
+Supports `#![no_std]` and is intended for bare-metal hypervisor and kernel use.
-### Memory Mapping Backends
-Two types of mapping backends are supported:
+## Quick Start
-1. **Linear Backend**: Direct mapping with constant offset between virtual and physical addresses
-2. **Allocation Backend**: Dynamic memory allocation with optional population strategies
+### Requirements
-### Nested Page Tables
-Architecture-specific nested page table implementations:
-- **x86_64**: `ExtendedPageTable` with EPT entries
-- **AArch64**: Stage 2 page tables with descriptor attributes
-- **RISC-V**: Sv39-based nested page tables
+- Rust nightly toolchain
+- Rust components: `rust-src`, `clippy`, `rustfmt`
-## Usage
+```bash
+# Install rustup (if not installed)
+curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
-Add this to your `Cargo.toml`:
-
-```toml
-[dependencies]
-axaddrspace = "0.1.0"
+# Install nightly toolchain and components
+rustup install nightly
+rustup component add rust-src clippy rustfmt --toolchain nightly
```
-### Basic Example
+### Run Check and Test
-```rust
-use axaddrspace::{AddrSpace, MappingFlags, GuestPhysAddr};
-use page_table_multiarch::PagingHandler;
+```bash
+# 1. Clone the repository
+git clone https://github.com/arceos-hypervisor/axaddrspace.git
+cd axaddrspace
+
+# 2. Code check
+./scripts/check.sh
+
+# 3. Run tests
+./scripts/test.sh
-// Create a new address space
-let mut addr_space = AddrSpace::::new_empty(
- GuestPhysAddr::from(0x1000_0000),
- 0x1000_0000, // 256MB
-)?;
-
-// Create a linear mapping
-addr_space.map_linear(
- GuestPhysAddr::from(0x1000_0000), // Guest virtual address
- PhysAddr::from(0x8000_0000), // Host physical address
- 0x10_0000, // 1MB
- MappingFlags::READ | MappingFlags::WRITE,
-)?;
-
-// Handle a nested page fault
-let fault_handled = addr_space.handle_page_fault(
- GuestPhysAddr::from(0x1000_1000),
- MappingFlags::READ,
-);
+# 4. Run a specific integration test target directly
+cargo test --test address_space
```
-### Hardware Abstraction Layer
+The helper scripts download the shared `axci` test/check framework on first run.
+
+## Integration
+
+### Installation
+
+Add to your `Cargo.toml`:
+
+```toml
+[dependencies]
+axaddrspace = "0.3.0"
+```
-Implement the `AxMmHal` trait for your platform:
+### Example
```rust
-use axaddrspace::{AxMmHal, HostPhysAddr, HostVirtAddr};
+use axaddrspace::{AddrSpace, AxMmHal, GuestPhysAddr, HostPhysAddr, HostVirtAddr, MappingFlags};
+use memory_addr::{PhysAddr, VirtAddr};
+use page_table_multiarch::PagingHandler;
struct MyHal;
impl AxMmHal for MyHal {
fn alloc_frame() -> Option {
- // Your frame allocation implementation
+ unimplemented!()
}
- fn dealloc_frame(paddr: HostPhysAddr) {
- // Your frame deallocation implementation
+ fn dealloc_frame(_paddr: HostPhysAddr) {
+ unimplemented!()
}
- fn phys_to_virt(paddr: HostPhysAddr) -> HostVirtAddr {
- // Your physical to virtual address conversion
+ fn phys_to_virt(_paddr: HostPhysAddr) -> HostVirtAddr {
+ unimplemented!()
}
- fn virt_to_phys(vaddr: HostVirtAddr) -> HostPhysAddr {
- // Your virtual to physical address conversion
+ fn virt_to_phys(_vaddr: HostVirtAddr) -> HostPhysAddr {
+ unimplemented!()
}
}
+
+impl PagingHandler for MyHal {
+ fn alloc_frame() -> Option {
+ ::alloc_frame()
+ }
+
+ fn dealloc_frame(paddr: PhysAddr) {
+ ::dealloc_frame(paddr)
+ }
+
+ fn phys_to_virt(paddr: PhysAddr) -> VirtAddr {
+ ::phys_to_virt(paddr)
+ }
+}
+
+fn example() -> axerrno::AxResult<()> {
+ let base = GuestPhysAddr::from_usize(0x1000_0000);
+ let mut addr_space = AddrSpace::::new_empty(4, base, 0x20_0000)?;
+
+ addr_space.map_linear(
+ base,
+ PhysAddr::from_usize(0x8000_0000),
+ 0x10_0000,
+ MappingFlags::READ | MappingFlags::WRITE,
+ )?;
+
+ addr_space.map_alloc(
+ base + 0x10_0000,
+ 0x2000,
+ MappingFlags::READ | MappingFlags::WRITE,
+ false,
+ )?;
+
+ let fault_handled = addr_space.handle_page_fault(
+ base + 0x10_0000,
+ MappingFlags::READ,
+ );
+ assert!(fault_handled);
+
+ let host_paddr = addr_space.translate(base).unwrap();
+ assert_eq!(host_paddr, PhysAddr::from_usize(0x8000_0000));
+
+ Ok(())
+}
```
-## Configuration
+### Features
+
+- `arm-el2`: enable AArch64 EL2 support
+- `default`: includes `arm-el2`
-### Feature Flags
+### Documentation
-- `arm-el2`: Enable AArch64 EL2 support (default)
-- `default`: Includes `arm-el2` feature
+Generate and view API documentation:
-## Contributing
+```bash
+cargo doc --no-deps --open
+```
-Contributions are welcome! Please feel free to submit a Pull Request.
+Online documentation: [docs.rs/axaddrspace](https://docs.rs/axaddrspace)
-## Repository
+# Contributing
-- [GitHub Repository](https://github.com/arceos-hypervisor/axaddrspace)
-- [ArceOS-Hypervisor Project](https://github.com/arceos-hypervisor/)
+1. Fork the repository and create a branch
+2. Run local check: `./scripts/check.sh`
+3. Run local tests: `./scripts/test.sh`
+4. Submit PR and pass CI checks
-## License
+# License
-Axaddrspace is licensed under the Apache License, Version 2.0. See the [LICENSE](./LICENSE) file for details.
+Licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE) for details.
diff --git a/README_CN.md b/README_CN.md
new file mode 100644
index 0000000..91fcf28
--- /dev/null
+++ b/README_CN.md
@@ -0,0 +1,179 @@
+axaddrspace
+
+ArceOS-Hypervisor 客户机虚拟机地址空间管理模块
+
+
+
+[](https://crates.io/crates/axaddrspace)
+[](https://docs.rs/axaddrspace)
+[](https://www.rust-lang.org/)
+[](https://github.com/arceos-hypervisor/axaddrspace/blob/main/LICENSE)
+
+
+
+[English](README.md) | 中文
+
+# 简介
+
+`axaddrspace` 是 [ArceOS-Hypervisor](https://github.com/arceos-hypervisor/)
+项目中的客户机地址空间管理 crate,提供嵌套页表管理、客户机物理地址转换、内存映射后端以及嵌套页错误处理能力,面向 Hypervisor 场景使用。
+
+该 crate 支持多种体系结构:
+
+- **x86_64** - VMX Extended Page Tables(EPT)
+- **AArch64** - Stage-2 页表
+- **RISC-V** - 基于 Hypervisor 扩展的嵌套页表
+
+核心能力包括:
+
+- **`AddrSpace`** - 地址空间创建、映射、解除映射与地址转换
+- **`AxMmHal`** - 用于页帧分配与地址转换的硬件抽象 trait
+- **线性映射后端** - 映射已知的连续宿主物理内存区域
+- **分配映射后端** - 支持预分配或缺页时惰性分配页帧
+- **客户机内存辅助接口** - 将客户机地址转换为宿主可访问缓冲区
+
+该库支持 `#![no_std]`,适用于裸机 Hypervisor 和内核环境。
+
+## 快速开始
+
+### 环境要求
+
+- Rust nightly 工具链
+- Rust 组件:`rust-src`、`clippy`、`rustfmt`
+
+```bash
+# 安装 rustup(如未安装)
+curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
+
+# 安装 nightly 工具链和组件
+rustup install nightly
+rustup component add rust-src clippy rustfmt --toolchain nightly
+```
+
+### 运行检查和测试
+
+```bash
+# 1. 克隆仓库
+git clone https://github.com/arceos-hypervisor/axaddrspace.git
+cd axaddrspace
+
+# 2. 代码检查
+./scripts/check.sh
+
+# 3. 运行测试
+./scripts/test.sh
+
+# 4. 直接运行指定集成测试目标
+cargo test --test address_space
+```
+
+辅助脚本会在首次运行时自动下载共享的 `axci` 检查/测试框架。
+
+## 集成方式
+
+### 安装
+
+在 `Cargo.toml` 中添加:
+
+```toml
+[dependencies]
+axaddrspace = "0.3.0"
+```
+
+### 示例
+
+```rust
+use axaddrspace::{AddrSpace, AxMmHal, GuestPhysAddr, HostPhysAddr, HostVirtAddr, MappingFlags};
+use memory_addr::{PhysAddr, VirtAddr};
+use page_table_multiarch::PagingHandler;
+
+struct MyHal;
+
+impl AxMmHal for MyHal {
+ fn alloc_frame() -> Option {
+ unimplemented!()
+ }
+
+ fn dealloc_frame(_paddr: HostPhysAddr) {
+ unimplemented!()
+ }
+
+ fn phys_to_virt(_paddr: HostPhysAddr) -> HostVirtAddr {
+ unimplemented!()
+ }
+
+ fn virt_to_phys(_vaddr: HostVirtAddr) -> HostPhysAddr {
+ unimplemented!()
+ }
+}
+
+impl PagingHandler for MyHal {
+ fn alloc_frame() -> Option {
+ ::alloc_frame()
+ }
+
+ fn dealloc_frame(paddr: PhysAddr) {
+ ::dealloc_frame(paddr)
+ }
+
+ fn phys_to_virt(paddr: PhysAddr) -> VirtAddr {
+ ::phys_to_virt(paddr)
+ }
+}
+
+fn example() -> axerrno::AxResult<()> {
+ let base = GuestPhysAddr::from_usize(0x1000_0000);
+ let mut addr_space = AddrSpace::::new_empty(4, base, 0x20_0000)?;
+
+ addr_space.map_linear(
+ base,
+ PhysAddr::from_usize(0x8000_0000),
+ 0x10_0000,
+ MappingFlags::READ | MappingFlags::WRITE,
+ )?;
+
+ addr_space.map_alloc(
+ base + 0x10_0000,
+ 0x2000,
+ MappingFlags::READ | MappingFlags::WRITE,
+ false,
+ )?;
+
+ let fault_handled = addr_space.handle_page_fault(
+ base + 0x10_0000,
+ MappingFlags::READ,
+ );
+ assert!(fault_handled);
+
+ let host_paddr = addr_space.translate(base).unwrap();
+ assert_eq!(host_paddr, PhysAddr::from_usize(0x8000_0000));
+
+ Ok(())
+}
+```
+
+### 特性
+
+- `arm-el2`:启用 AArch64 EL2 支持
+- `default`:默认包含 `arm-el2`
+
+### 文档
+
+生成并查看 API 文档:
+
+```bash
+cargo doc --no-deps --open
+```
+
+在线文档:[docs.rs/axaddrspace](https://docs.rs/axaddrspace)
+
+# 贡献
+
+1. Fork 仓库并创建分支
+2. 本地运行检查:`./scripts/check.sh`
+3. 本地运行测试:`./scripts/test.sh`
+4. 提交 PR 并通过 CI 检查
+
+# 许可证
+
+本项目采用 Apache License 2.0。详见 [LICENSE](LICENSE)。
diff --git a/scripts/check.sh b/scripts/check.sh
new file mode 100755
index 0000000..1e4092c
--- /dev/null
+++ b/scripts/check.sh
@@ -0,0 +1,35 @@
+#!/bin/bash
+#
+# axaddrspace 代码检查脚本
+# 下载并调用 axci 仓库中的检查脚本
+#
+
+set -e
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+COMPONENT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
+COMPONENT_NAME="$(basename "$COMPONENT_DIR")"
+AXCI_DIR="${SCRIPT_DIR}/.axci"
+AXCI_REPO="https://github.com/arceos-hypervisor/axci.git"
+
+# 下载或更新 axci 仓库
+download_axci() {
+ if [ -d "$AXCI_DIR" ]; then
+ echo "Updating axci repository..."
+ cd "$AXCI_DIR" && git pull --quiet
+ else
+ echo "Downloading axci repository..."
+ git clone --quiet "$AXCI_REPO" "$AXCI_DIR"
+ fi
+}
+
+# 主函数
+main() {
+ download_axci
+
+ # 在组件目录中运行检查
+ cd "$COMPONENT_DIR"
+ exec bash "$AXCI_DIR/check.sh" --component-dir "$COMPONENT_DIR" "$@"
+}
+
+main "$@"
diff --git a/scripts/test.sh b/scripts/test.sh
new file mode 100755
index 0000000..0ac99ec
--- /dev/null
+++ b/scripts/test.sh
@@ -0,0 +1,34 @@
+#!/bin/bash
+#
+# axaddrspace 测试脚本
+# 下载并调用 axci 仓库中的测试框架
+#
+
+set -e
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+COMPONENT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
+AXCI_DIR="${SCRIPT_DIR}/.axci"
+AXCI_REPO="https://github.com/arceos-hypervisor/axci.git"
+
+# 下载或更新 axci 仓库
+download_axci() {
+ if [ -d "$AXCI_DIR" ]; then
+ echo "Updating axci repository..."
+ cd "$AXCI_DIR" && git pull --quiet
+ else
+ echo "Downloading axci repository..."
+ git clone --quiet "$AXCI_REPO" "$AXCI_DIR"
+ fi
+}
+
+# 主函数
+main() {
+ download_axci
+
+ # 在组件目录中运行测试,自动指定当前组件
+ cd "$COMPONENT_DIR"
+ exec bash "$AXCI_DIR/tests.sh" --component-dir "$COMPONENT_DIR" "$@"
+}
+
+main "$@"
diff --git a/src/address_space/mod.rs b/src/address_space/mod.rs
index 8cf6ebd..a566481 100644
--- a/src/address_space/mod.rs
+++ b/src/address_space/mod.rs
@@ -275,328 +275,3 @@ impl Drop for AddrSpace {
self.clear();
}
}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use crate::test_utils::{
- ALLOC_COUNT, BASE_PADDR, DEALLOC_COUNT, MEMORY_LEN, MockHal, mock_hal_test,
- test_dealloc_count,
- };
- use axin::axin;
- use core::sync::atomic::Ordering;
-
- /// Generate an address space for the test
- fn setup_test_addr_space() -> (AddrSpace, GuestPhysAddr, usize) {
- const BASE: GuestPhysAddr = GuestPhysAddr::from_usize(0x10000);
- const SIZE: usize = 0x10000;
- let addr_space = AddrSpace::::new_empty(4, BASE, SIZE).unwrap();
- (addr_space, BASE, SIZE)
- }
-
- #[test]
- #[axin(decorator(mock_hal_test), on_exit(test_dealloc_count(1)))]
- /// Check whether an address_space can be created correctly.
- /// When creating a new address_space, a frame will be allocated for the page table,
- /// thus triggering an alloc_frame operation.
- fn test_addrspace_creation() {
- let (addr_space, base, size) = setup_test_addr_space();
- assert_eq!(addr_space.base(), base);
- assert_eq!(addr_space.size(), size);
- assert_eq!(addr_space.end(), base + size);
- assert_eq!(ALLOC_COUNT.load(Ordering::SeqCst), 1);
- }
-
- #[test]
- #[axin(decorator(mock_hal_test))]
- fn test_contains_range() {
- let (addr_space, base, size) = setup_test_addr_space();
-
- // Within range
- assert!(addr_space.contains_range(base, 0x1000));
- assert!(addr_space.contains_range(base + 0x1000, 0x2000));
- assert!(addr_space.contains_range(base, size));
-
- // Out of range
- assert!(!addr_space.contains_range(base - 0x1000, 0x1000));
- assert!(!addr_space.contains_range(base + size, 0x1000));
- assert!(!addr_space.contains_range(base, size + 0x1000));
-
- // Partially out of range
- assert!(!addr_space.contains_range(base + 0x3000, 0xf000));
- }
-
- #[test]
- #[axin(decorator(mock_hal_test))]
- fn test_map_linear() {
- let (mut addr_space, _base, _size) = setup_test_addr_space();
- let vaddr = GuestPhysAddr::from_usize(0x18000);
- let paddr = PhysAddr::from_usize(0x10000);
- let map_linear_size = 0x8000; // 32KB
- let flags = MappingFlags::READ | MappingFlags::WRITE;
-
- addr_space
- .map_linear(vaddr, paddr, map_linear_size, flags)
- .unwrap();
-
- assert_eq!(addr_space.translate(vaddr).unwrap(), paddr);
- assert_eq!(
- addr_space.translate(vaddr + 0x1000).unwrap(),
- paddr + 0x1000
- );
- }
-
- #[test]
- #[axin(decorator(mock_hal_test))]
- fn test_map_alloc_populate() {
- let (mut addr_space, _base, _size) = setup_test_addr_space();
- let vaddr = GuestPhysAddr::from_usize(0x10000);
- let map_alloc_size = 0x2000; // 8KB
- let flags = MappingFlags::READ | MappingFlags::WRITE;
-
- // Frame count before allocation: 1 root page table
- let initial_allocs = ALLOC_COUNT.load(Ordering::SeqCst);
- assert_eq!(initial_allocs, 1);
-
- // Allocate physical frames immediately
- addr_space
- .map_alloc(vaddr, map_alloc_size, flags, true)
- .unwrap();
-
- // Verify additional frames were allocated
- let final_allocs = ALLOC_COUNT.load(Ordering::SeqCst);
- assert!(final_allocs > initial_allocs);
-
- // Verify mappings exist and addresses are valid
- let paddr1 = addr_space.translate(vaddr).unwrap();
- let paddr2 = addr_space.translate(vaddr + 0x1000).unwrap();
-
- // Verify physical addresses are within valid range
- assert!(paddr1.as_usize() >= BASE_PADDR && paddr1.as_usize() < BASE_PADDR + MEMORY_LEN);
- assert!(paddr2.as_usize() >= BASE_PADDR && paddr2.as_usize() < BASE_PADDR + MEMORY_LEN);
-
- // Verify two pages have different physical addresses
- assert_ne!(paddr1, paddr2);
- }
-
- #[test]
- #[axin(decorator(mock_hal_test))]
- fn test_map_alloc_lazy() {
- let (mut addr_space, _base, _size) = setup_test_addr_space();
- let vaddr = GuestPhysAddr::from_usize(0x13000);
- let map_alloc_size = 0x1000;
- let flags = MappingFlags::READ | MappingFlags::WRITE;
-
- let initial_allocs = ALLOC_COUNT.load(Ordering::SeqCst);
-
- // Lazy allocation - don't allocate physical frames immediately
- addr_space
- .map_alloc(vaddr, map_alloc_size, flags, false)
- .unwrap();
-
- // Frame count should only increase for page table structure, not data pages
- let after_map_allocs = ALLOC_COUNT.load(Ordering::SeqCst);
- assert!(after_map_allocs >= initial_allocs); // May have allocated intermediate page tables
- assert!(addr_space.translate(vaddr).is_none());
- }
-
- #[test]
- #[axin(decorator(mock_hal_test))]
- fn test_page_fault_handling() {
- let (mut addr_space, _base, _size) = setup_test_addr_space();
- let vaddr = GuestPhysAddr::from_usize(0x14000);
- let map_alloc_size = 0x1000;
- let flags = MappingFlags::READ | MappingFlags::WRITE;
-
- // Create lazy allocation mapping
- addr_space
- .map_alloc(vaddr, map_alloc_size, flags, false)
- .unwrap();
-
- let before_pf_allocs = ALLOC_COUNT.load(Ordering::SeqCst);
-
- // Simulate page fault
- let handled = addr_space.handle_page_fault(vaddr, MappingFlags::READ);
-
- // Page fault should be handled
- assert!(handled);
-
- // Should have allocated physical frames
- let after_pf_allocs = ALLOC_COUNT.load(Ordering::SeqCst);
- assert!(after_pf_allocs > before_pf_allocs);
-
- // Translation should succeed now
- let paddr = addr_space.translate(vaddr);
- assert!(paddr.is_some());
- }
-
- #[test]
- #[axin(decorator(mock_hal_test))]
- fn test_unmap() {
- let (mut addr_space, _base, _size) = setup_test_addr_space();
- let vaddr = GuestPhysAddr::from_usize(0x15000);
- let map_alloc_size = 0x2000;
- let flags = MappingFlags::READ | MappingFlags::WRITE;
-
- // Create mapping
- addr_space
- .map_alloc(vaddr, map_alloc_size, flags, true)
- .unwrap();
-
- // Verify mapping exists
- assert!(addr_space.translate(vaddr).is_some());
- assert!(addr_space.translate(vaddr + 0x1000).is_some());
-
- let before_unmap_deallocs = DEALLOC_COUNT.load(Ordering::SeqCst);
-
- // Unmap
- addr_space.unmap(vaddr, map_alloc_size).unwrap();
-
- // Verify mapping is removed
- assert!(addr_space.translate(vaddr).is_none());
- assert!(addr_space.translate(vaddr + 0x1000).is_none());
-
- // Verify frames were deallocated
- let after_unmap_deallocs = DEALLOC_COUNT.load(Ordering::SeqCst);
- assert!(after_unmap_deallocs > before_unmap_deallocs);
- }
-
- #[test]
- #[axin(decorator(mock_hal_test))]
- fn test_clear() {
- let (mut addr_space, _base, _size) = setup_test_addr_space();
- let vaddr1 = GuestPhysAddr::from_usize(0x16000);
- let vaddr2 = GuestPhysAddr::from_usize(0x17000);
- let flags = MappingFlags::READ | MappingFlags::WRITE;
- let map_alloc_size = 0x1000;
-
- // Create multiple mappings
- addr_space
- .map_alloc(vaddr1, map_alloc_size, flags, true)
- .unwrap();
- addr_space
- .map_alloc(vaddr2, map_alloc_size, flags, true)
- .unwrap();
-
- // Verify mappings exist
- assert!(addr_space.translate(vaddr1).is_some());
- assert!(addr_space.translate(vaddr2).is_some());
-
- let before_clear_deallocs = DEALLOC_COUNT.load(Ordering::SeqCst);
-
- // Clear all mappings
- addr_space.clear();
-
- // Verify all mappings are removed
- assert!(addr_space.translate(vaddr1).is_none());
- assert!(addr_space.translate(vaddr2).is_none());
-
- // Verify frames were deallocated
- let after_clear_deallocs = DEALLOC_COUNT.load(Ordering::SeqCst);
- assert!(after_clear_deallocs > before_clear_deallocs);
- }
-
- #[test]
- #[axin(decorator(mock_hal_test))]
- fn test_translate() {
- let (mut addr_space, _base, _size) = setup_test_addr_space();
- let vaddr = GuestPhysAddr::from_usize(0x18000);
- let map_alloc_size = 0x1000;
- let flags = MappingFlags::READ | MappingFlags::WRITE;
-
- // Create mapping
- addr_space
- .map_alloc(vaddr, map_alloc_size, flags, true)
- .unwrap();
-
- // Verify translation succeeds
- let paddr = addr_space.translate(vaddr).expect("Translation failed");
- assert!(paddr.as_usize() >= BASE_PADDR);
- assert!(paddr.as_usize() < BASE_PADDR + MEMORY_LEN);
-
- // Verify unmapped address translation fails
- let unmapped_vaddr = GuestPhysAddr::from_usize(0x19000);
- assert!(addr_space.translate(unmapped_vaddr).is_none());
-
- // Verify out-of-range address translation fails
- let out_of_range = GuestPhysAddr::from_usize(0x30000);
- assert!(addr_space.translate(out_of_range).is_none());
- }
-
- #[test]
- #[axin(decorator(mock_hal_test))]
- fn test_translated_byte_buffer() {
- let (mut addr_space, _base, _size) = setup_test_addr_space();
- let vaddr = GuestPhysAddr::from_usize(0x19000);
- let map_alloc_size = 0x2000; // 8KB
- let flags = MappingFlags::READ | MappingFlags::WRITE;
- let buffer_size = 0x1100;
-
- // Create mapping
- addr_space
- .map_alloc(vaddr, map_alloc_size, flags, true)
- .unwrap();
-
- // Verify byte buffer can be obtained
- let mut buffer = addr_space
- .translated_byte_buffer(vaddr, buffer_size)
- .expect("Failed to get byte buffer");
-
- // Verify data write and read
- // Fill with values ranging from 0 to 0x100
- for buffer_segment in buffer.iter_mut() {
- for (i, byte) in buffer_segment.iter_mut().enumerate() {
- *byte = (i % 0x100) as u8;
- }
- }
-
- // Verify data read correctness
- for buffer_segment in buffer.iter_mut() {
- for (i, byte) in buffer_segment.iter_mut().enumerate() {
- assert_eq!(*byte, (i % 0x100) as u8);
- }
- }
-
- // Verify exceeding area size returns None
- assert!(
- addr_space
- .translated_byte_buffer(vaddr, map_alloc_size + 0x1000)
- .is_none()
- );
-
- // Verify unmapped address returns None
- let unmapped_vaddr = GuestPhysAddr::from_usize(0x1D000);
- assert!(
- addr_space
- .translated_byte_buffer(unmapped_vaddr, 0x100)
- .is_none()
- );
- }
-
- #[test]
- #[axin(decorator(mock_hal_test))]
- fn test_translate_and_get_limit() {
- let (mut addr_space, _base, _size) = setup_test_addr_space();
- let vaddr = GuestPhysAddr::from_usize(0x1A000);
- let map_alloc_size = 0x3000; // 12KB
- let flags = MappingFlags::READ | MappingFlags::WRITE;
-
- // Create mapping
- addr_space
- .map_alloc(vaddr, map_alloc_size, flags, true)
- .unwrap();
-
- // Verify translation and area size retrieval
- let (paddr, area_size) = addr_space.translate_and_get_limit(vaddr).unwrap();
- assert!(paddr.as_usize() >= BASE_PADDR && paddr.as_usize() < BASE_PADDR + MEMORY_LEN);
- assert_eq!(area_size, map_alloc_size);
-
- // Verify unmapped address returns None
- let unmapped_vaddr = GuestPhysAddr::from_usize(0x1E000);
- assert!(addr_space.translate_and_get_limit(unmapped_vaddr).is_none());
-
- // Verify out-of-range address returns None
- let out_of_range = GuestPhysAddr::from_usize(0x30000);
- assert!(addr_space.translate_and_get_limit(out_of_range).is_none());
- }
-}
diff --git a/src/frame.rs b/src/frame.rs
index 891f7fd..b130264 100644
--- a/src/frame.rs
+++ b/src/frame.rs
@@ -86,92 +86,3 @@ impl Drop for PhysFrame {
}
}
}
-
-#[cfg(test)]
-mod test {
- use super::*;
- use crate::test_utils::{BASE_PADDR, MockHal, mock_hal_test, test_dealloc_count};
- use alloc::vec::Vec;
- use assert_matches::assert_matches;
- use axin::axin;
-
- #[test]
- #[axin(decorator(mock_hal_test), on_exit(test_dealloc_count(1)))]
- fn test_alloc_dealloc_cycle() {
- let frame = PhysFrame::::alloc()
- .unwrap_or_else(|e| panic!("Failed to allocate frame: {:?}", e));
- assert_eq!(frame.start_paddr().as_usize(), BASE_PADDR);
- // frame is dropped here, dealloc_frame should be called
- }
-
- #[test]
- #[axin(decorator(mock_hal_test), on_exit(test_dealloc_count(1)))]
- fn test_alloc_zero() {
- let frame = PhysFrame::::alloc_zero()
- .unwrap_or_else(|e| panic!("Failed to allocate zero frame: {:?}", e));
- assert_eq!(frame.start_paddr().as_usize(), BASE_PADDR);
- let ptr = frame.as_mut_ptr();
- let page = unsafe { &*(ptr as *const [u8; PAGE_SIZE]) };
- assert!(page.iter().all(|&x| x == 0));
- }
-
- #[test]
- #[axin(decorator(mock_hal_test), on_exit(test_dealloc_count(1)))]
- fn test_fill_operation() {
- let mut frame = PhysFrame::::alloc()
- .unwrap_or_else(|e| panic!("Failed to allocate frame: {:?}", e));
- assert_eq!(frame.start_paddr().as_usize(), BASE_PADDR);
- frame.fill(0xAA);
- let ptr = frame.as_mut_ptr();
- let page = unsafe { &*(ptr as *const [u8; PAGE_SIZE]) };
- assert!(page.iter().all(|&x| x == 0xAA));
- }
-
- #[test]
- #[axin(decorator(mock_hal_test), on_exit(test_dealloc_count(5)))]
- fn test_fill_multiple_frames() {
- const NUM_FRAMES: usize = 5;
-
- let mut frames = Vec::new();
- let mut patterns = Vec::new();
-
- for i in 0..NUM_FRAMES {
- let mut frame = PhysFrame::::alloc().unwrap();
- let pattern = (0xA0 + i) as u8;
- frame.fill(pattern);
- frames.push(frame);
- patterns.push(pattern);
- }
-
- for i in 0..NUM_FRAMES {
- let actual_page = unsafe { &*(frames[i].as_mut_ptr() as *mut [u8; PAGE_SIZE]) };
- let expected_page = &[patterns[i]; PAGE_SIZE];
-
- assert_eq!(
- actual_page, expected_page,
- "Frame verification failed for frame index {i}: Expected pattern 0x{:02x}",
- patterns[i]
- );
- }
- }
-
- #[test]
- #[should_panic(expected = "uninitialized PhysFrame")]
- fn test_uninit_access() {
- // This test verifies that accessing an uninitialized PhysFrame (created with `unsafe { uninit() }`)
- // leads to a panic when trying to retrieve its physical address.
- let frame = unsafe { PhysFrame::::uninit() };
- frame.start_paddr(); // This should panic
- }
-
- #[test]
- #[axin(decorator(mock_hal_test), on_exit(test_dealloc_count(0)))]
- fn test_alloc_no_memory() {
- // Configure MockHal to simulate an allocation failure.
- MockHal::set_alloc_fail(true);
- let result = PhysFrame::::alloc();
- // Assert that allocation failed and verify the specific error type.
- assert_matches!(result, Err(axerrno::AxError::NoMemory));
- MockHal::set_alloc_fail(false); // Reset for other tests
- }
-}
diff --git a/src/lib.rs b/src/lib.rs
index e91f573..b4989cc 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -57,6 +57,3 @@ fn mapping_err_to_ax_err(err: MappingError) -> AxError {
MappingError::BadState => AxError::BadState,
}
}
-
-#[cfg(test)]
-pub(crate) mod test_utils;
diff --git a/src/memory_accessor.rs b/src/memory_accessor.rs
index a0855a2..7da85b5 100644
--- a/src/memory_accessor.rs
+++ b/src/memory_accessor.rs
@@ -197,267 +197,3 @@ pub trait GuestMemoryAccessor {
self.write_obj(guest_addr, val)
}
}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use crate::test_utils::{BASE_PADDR, mock_hal_test};
- use axin::axin;
- use memory_addr::PhysAddr;
-
- /// Mock implementation of GuestMemoryAccessor for testing
- struct MockTranslator {
- base_addr: PhysAddr,
- memory_size: usize,
- }
-
- impl MockTranslator {
- pub fn new(base_addr: PhysAddr, memory_size: usize) -> Self {
- Self {
- base_addr,
- memory_size,
- }
- }
- }
-
- impl GuestMemoryAccessor for MockTranslator {
- fn translate_and_get_limit(&self, guest_addr: GuestPhysAddr) -> Option<(PhysAddr, usize)> {
- // Simple mapping: guest address directly maps to mock memory region
- let offset = guest_addr.as_usize();
- if offset < self.memory_size {
- // Convert physical address to virtual address for actual memory access
- let phys_addr =
- PhysAddr::from_usize(BASE_PADDR + self.base_addr.as_usize() + offset);
- let virt_addr = crate::test_utils::MockHal::mock_phys_to_virt(phys_addr);
- let accessible_size = self.memory_size - offset;
- Some((PhysAddr::from_usize(virt_addr.as_usize()), accessible_size))
- } else {
- None
- }
- }
- }
-
- #[test]
- #[axin(decorator(mock_hal_test))]
- fn test_basic_read_write_operations() {
- let translator =
- MockTranslator::new(PhysAddr::from_usize(0), crate::test_utils::MEMORY_LEN);
-
- // Test u32 read/write operations
- let test_addr = GuestPhysAddr::from_usize(0x100);
- let test_value: u32 = 0x12345678;
-
- // Write a u32 value
- translator
- .write_obj(test_addr, test_value)
- .expect("Failed to write u32 value");
-
- // Read back the u32 value
- let read_value: u32 = translator
- .read_obj(test_addr)
- .expect("Failed to read u32 value");
-
- assert_eq!(
- read_value, test_value,
- "Read value should match written value"
- );
-
- // Test buffer read/write operations
- let buffer_addr = GuestPhysAddr::from_usize(0x200);
- let test_buffer = [0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88];
-
- // Write buffer
- translator
- .write_buffer(buffer_addr, &test_buffer)
- .expect("Failed to write buffer");
-
- // Read buffer back
- let mut read_buffer = [0u8; 8];
- translator
- .read_buffer(buffer_addr, &mut read_buffer)
- .expect("Failed to read buffer");
-
- assert_eq!(
- read_buffer, test_buffer,
- "Read buffer should match written buffer"
- );
-
- // Test error handling with invalid address
- let invalid_addr = GuestPhysAddr::from_usize(crate::test_utils::MEMORY_LEN + 0x1000);
- let result: AxResult = translator.read_obj(invalid_addr);
- assert!(result.is_err(), "Reading from invalid address should fail");
-
- let result = translator.write_obj(invalid_addr, 42u32);
- assert!(result.is_err(), "Writing to invalid address should fail");
- }
-
- #[test]
- #[axin(decorator(mock_hal_test))]
- fn test_two_vm_isolation() {
- // Create two different translators to simulate two different VMs
- let vm1_translator =
- MockTranslator::new(PhysAddr::from_usize(0), crate::test_utils::MEMORY_LEN / 2); // Offset for VM1
- let vm2_translator = MockTranslator::new(
- PhysAddr::from_usize(crate::test_utils::MEMORY_LEN / 2),
- crate::test_utils::MEMORY_LEN,
- ); // Offset for VM2
-
- // Both VMs write to the same guest address but different host memory regions
- let guest_addr = GuestPhysAddr::from_usize(0x100);
- let vm1_data: u64 = 0xDEADBEEFCAFEBABE;
- let vm2_data: u64 = 0x1234567890ABCDEF;
-
- // VM1 writes its data
- vm1_translator
- .write_obj(guest_addr, vm1_data)
- .expect("VM1 failed to write data");
-
- // VM2 writes its data
- vm2_translator
- .write_obj(guest_addr, vm2_data)
- .expect("VM2 failed to write data");
-
- // Both VMs read back their own data - should be isolated
- let vm1_read: u64 = vm1_translator
- .read_obj(guest_addr)
- .expect("VM1 failed to read data");
- let vm2_read: u64 = vm2_translator
- .read_obj(guest_addr)
- .expect("VM2 failed to read data");
-
- // Verify isolation: each VM should read its own data
- assert_eq!(vm1_read, vm1_data, "VM1 should read its own data");
- assert_eq!(vm2_read, vm2_data, "VM2 should read its own data");
- assert_ne!(
- vm1_read, vm2_read,
- "VM1 and VM2 should have different data (isolation)"
- );
-
- // Test buffer operations with different patterns
- let buffer_addr = GuestPhysAddr::from_usize(0x200);
- let vm1_buffer = [0xAA; 16]; // Pattern for VM1
- let vm2_buffer = [0x55; 16]; // Pattern for VM2
-
- // Both VMs write their patterns
- vm1_translator
- .write_buffer(buffer_addr, &vm1_buffer)
- .expect("VM1 failed to write buffer");
- vm2_translator
- .write_buffer(buffer_addr, &vm2_buffer)
- .expect("VM2 failed to write buffer");
-
- // Read back and verify isolation
- let mut vm1_read_buffer = [0u8; 16];
- let mut vm2_read_buffer = [0u8; 16];
-
- vm1_translator
- .read_buffer(buffer_addr, &mut vm1_read_buffer)
- .expect("VM1 failed to read buffer");
- vm2_translator
- .read_buffer(buffer_addr, &mut vm2_read_buffer)
- .expect("VM2 failed to read buffer");
-
- assert_eq!(
- vm1_read_buffer, vm1_buffer,
- "VM1 should read its own buffer pattern"
- );
- assert_eq!(
- vm2_read_buffer, vm2_buffer,
- "VM2 should read its own buffer pattern"
- );
- assert_ne!(
- vm1_read_buffer, vm2_read_buffer,
- "VM buffers should be isolated"
- );
-
- // Test that VM1 cannot access VM2's address space (beyond its limit)
- let vm2_only_addr = GuestPhysAddr::from_usize(crate::test_utils::MEMORY_LEN / 2 + 0x100);
- let result: AxResult = vm1_translator.read_obj(vm2_only_addr);
- assert!(
- result.is_err(),
- "VM1 should not be able to access VM2's exclusive address space"
- );
- }
-
- #[test]
- #[axin(decorator(mock_hal_test))]
- fn test_cross_page_access() {
- let translator =
- MockTranslator::new(PhysAddr::from_usize(0), crate::test_utils::MEMORY_LEN);
-
- // Test cross-region buffer operations
- // Place buffer near a region boundary to test multi-region access
- let cross_region_addr = GuestPhysAddr::from_usize(4096 - 8); // 8 bytes before 4K boundary
- let test_data = [
- 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
- 0x0F, 0x10,
- ]; // 16 bytes
-
- // Write cross-region data
- translator
- .write_buffer(cross_region_addr, &test_data)
- .expect("Failed to write cross-region buffer");
-
- // Read cross-region data back
- let mut read_data = [0u8; 16];
- translator
- .read_buffer(cross_region_addr, &mut read_data)
- .expect("Failed to read cross-region buffer");
-
- assert_eq!(
- read_data, test_data,
- "Cross-region read should match written data"
- );
-
- // Test individual byte access across region boundary
- for (i, &expected_byte) in test_data.iter().enumerate() {
- let byte_addr = GuestPhysAddr::from_usize(cross_region_addr.as_usize() + i);
- let read_byte: u8 = translator
- .read_obj(byte_addr)
- .expect("Failed to read individual byte");
- assert_eq!(
- read_byte, expected_byte,
- "Byte at offset {} should match",
- i
- );
- }
- }
-
- #[test]
- #[axin(decorator(mock_hal_test))]
- fn test_region_boundary_edge_cases() {
- let translator =
- MockTranslator::new(PhysAddr::from_usize(0), crate::test_utils::MEMORY_LEN);
-
- let boundary_addr = GuestPhysAddr::from_usize(4096);
- let boundary_data = [0xAB, 0xCD, 0xEF, 0x12];
-
- translator
- .write_buffer(boundary_addr, &boundary_data)
- .expect("Failed to write at boundary");
-
- let mut read_boundary = [0u8; 4];
- translator
- .read_buffer(boundary_addr, &mut read_boundary)
- .expect("Failed to read at boundary");
-
- assert_eq!(read_boundary, boundary_data, "Boundary data should match");
-
- // Test zero-size buffer (should not fail)
- let empty_buffer: &[u8] = &[];
- translator
- .write_buffer(boundary_addr, empty_buffer)
- .expect("Empty buffer write should succeed");
-
- let mut empty_read: &mut [u8] = &mut [];
- translator
- .read_buffer(boundary_addr, &mut empty_read)
- .expect("Empty buffer read should succeed");
-
- // Test single byte at boundary (should work fine)
- let single_byte = [0x42];
- translator
- .write_buffer(boundary_addr, &single_byte)
- .expect("Single byte write should succeed");
- }
-}
diff --git a/src/npt/arch/x86_64.rs b/src/npt/arch/x86_64.rs
index 65b6261..82a952c 100644
--- a/src/npt/arch/x86_64.rs
+++ b/src/npt/arch/x86_64.rs
@@ -188,15 +188,18 @@ impl PagingMetaData for ExtendedPageTableMetadata {
type VirtAddr = GuestPhysAddr;
- // Under the x86 architecture, the flush_tlb operation will invoke the ring0 instruction,
- // causing the test to trigger a SIGSEGV exception.
+ // Under the x86 architecture, flushing the TLB requires privileged
+ // instructions. Hosted binaries such as integration tests run in ring 3,
+ // so issue TLB invalidations only for bare-metal targets.
#[allow(unused_variables)]
fn flush_tlb(vaddr: Option) {
- #[cfg(not(test))]
- if let Some(vaddr) = vaddr {
- unsafe { x86::tlb::flush(vaddr.into()) }
- } else {
- unsafe { x86::tlb::flush_all() }
+ #[cfg(target_os = "none")]
+ {
+ if let Some(vaddr) = vaddr {
+ unsafe { x86::tlb::flush(vaddr.into()) }
+ } else {
+ unsafe { x86::tlb::flush_all() }
+ }
}
}
}
diff --git a/tests/address_space.rs b/tests/address_space.rs
new file mode 100644
index 0000000..e78285c
--- /dev/null
+++ b/tests/address_space.rs
@@ -0,0 +1,337 @@
+// Copyright 2025 The Axvisor Team
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+mod test_utils;
+
+use axaddrspace::{AddrSpace, GuestPhysAddr, MappingFlags};
+use axin::axin;
+use core::sync::atomic::Ordering;
+use memory_addr::PhysAddr;
+use test_utils::{
+ ALLOC_COUNT, BASE_PADDR, DEALLOC_COUNT, MEMORY_LEN, MockHal, mock_hal_test, test_dealloc_count,
+};
+
+/// Generate an address space for the test
+fn setup_test_addr_space() -> (AddrSpace, GuestPhysAddr, usize) {
+ const BASE: GuestPhysAddr = GuestPhysAddr::from_usize(0x10000);
+ const SIZE: usize = 0x10000;
+ let addr_space = AddrSpace::::new_empty(4, BASE, SIZE).unwrap();
+ (addr_space, BASE, SIZE)
+}
+
+#[test]
+#[axin(decorator(mock_hal_test), on_exit(test_dealloc_count(1)))]
+/// Check whether an address_space can be created correctly.
+/// When creating a new address_space, a frame will be allocated for the page table,
+/// thus triggering an alloc_frame operation.
+fn test_addrspace_creation() {
+ let (addr_space, base, size) = setup_test_addr_space();
+ assert_eq!(addr_space.base(), base);
+ assert_eq!(addr_space.size(), size);
+ assert_eq!(addr_space.end(), base + size);
+ assert_eq!(ALLOC_COUNT.load(Ordering::SeqCst), 1);
+}
+
+#[test]
+#[axin(decorator(mock_hal_test))]
+fn test_contains_range() {
+ let (addr_space, base, size) = setup_test_addr_space();
+
+ // Within range
+ assert!(addr_space.contains_range(base, 0x1000));
+ assert!(addr_space.contains_range(base + 0x1000, 0x2000));
+ assert!(addr_space.contains_range(base, size));
+
+ // Out of range
+ assert!(!addr_space.contains_range(base - 0x1000, 0x1000));
+ assert!(!addr_space.contains_range(base + size, 0x1000));
+ assert!(!addr_space.contains_range(base, size + 0x1000));
+
+ // Partially out of range
+ assert!(!addr_space.contains_range(base + 0x3000, 0xf000));
+}
+
+#[test]
+#[axin(decorator(mock_hal_test))]
+fn test_map_linear() {
+ let (mut addr_space, _base, _size) = setup_test_addr_space();
+ let vaddr = GuestPhysAddr::from_usize(0x18000);
+ let paddr = PhysAddr::from_usize(0x10000);
+ let map_linear_size = 0x8000; // 32KB
+ let flags = MappingFlags::READ | MappingFlags::WRITE;
+
+ addr_space
+ .map_linear(vaddr, paddr, map_linear_size, flags)
+ .unwrap();
+
+ assert_eq!(addr_space.translate(vaddr).unwrap(), paddr);
+ assert_eq!(
+ addr_space.translate(vaddr + 0x1000).unwrap(),
+ paddr + 0x1000
+ );
+}
+
+#[test]
+#[axin(decorator(mock_hal_test))]
+fn test_map_alloc_populate() {
+ let (mut addr_space, _base, _size) = setup_test_addr_space();
+ let vaddr = GuestPhysAddr::from_usize(0x10000);
+ let map_alloc_size = 0x2000; // 8KB
+ let flags = MappingFlags::READ | MappingFlags::WRITE;
+
+ // Frame count before allocation: 1 root page table
+ let initial_allocs = ALLOC_COUNT.load(Ordering::SeqCst);
+ assert_eq!(initial_allocs, 1);
+
+ // Allocate physical frames immediately
+ addr_space
+ .map_alloc(vaddr, map_alloc_size, flags, true)
+ .unwrap();
+
+ // Verify additional frames were allocated
+ let final_allocs = ALLOC_COUNT.load(Ordering::SeqCst);
+ assert!(final_allocs > initial_allocs);
+
+ // Verify mappings exist and addresses are valid
+ let paddr1 = addr_space.translate(vaddr).unwrap();
+ let paddr2 = addr_space.translate(vaddr + 0x1000).unwrap();
+
+ // Verify physical addresses are within valid range
+ assert!(paddr1.as_usize() >= BASE_PADDR && paddr1.as_usize() < BASE_PADDR + MEMORY_LEN);
+ assert!(paddr2.as_usize() >= BASE_PADDR && paddr2.as_usize() < BASE_PADDR + MEMORY_LEN);
+
+ // Verify two pages have different physical addresses
+ assert_ne!(paddr1, paddr2);
+}
+
+#[test]
+#[axin(decorator(mock_hal_test))]
+fn test_map_alloc_lazy() {
+ let (mut addr_space, _base, _size) = setup_test_addr_space();
+ let vaddr = GuestPhysAddr::from_usize(0x13000);
+ let map_alloc_size = 0x1000;
+ let flags = MappingFlags::READ | MappingFlags::WRITE;
+
+ let initial_allocs = ALLOC_COUNT.load(Ordering::SeqCst);
+
+ // Lazy allocation - don't allocate physical frames immediately
+ addr_space
+ .map_alloc(vaddr, map_alloc_size, flags, false)
+ .unwrap();
+
+ // Frame count should only increase for page table structure, not data pages
+ let after_map_allocs = ALLOC_COUNT.load(Ordering::SeqCst);
+ assert!(after_map_allocs >= initial_allocs); // May have allocated intermediate page tables
+ assert!(addr_space.translate(vaddr).is_none());
+}
+
+#[test]
+#[axin(decorator(mock_hal_test))]
+fn test_page_fault_handling() {
+ let (mut addr_space, _base, _size) = setup_test_addr_space();
+ let vaddr = GuestPhysAddr::from_usize(0x14000);
+ let map_alloc_size = 0x1000;
+ let flags = MappingFlags::READ | MappingFlags::WRITE;
+
+ // Create lazy allocation mapping
+ addr_space
+ .map_alloc(vaddr, map_alloc_size, flags, false)
+ .unwrap();
+
+ let before_pf_allocs = ALLOC_COUNT.load(Ordering::SeqCst);
+
+ // Simulate page fault
+ let handled = addr_space.handle_page_fault(vaddr, MappingFlags::READ);
+
+ // Page fault should be handled
+ assert!(handled);
+
+ // Should have allocated physical frames
+ let after_pf_allocs = ALLOC_COUNT.load(Ordering::SeqCst);
+ assert!(after_pf_allocs > before_pf_allocs);
+
+ // Translation should succeed now
+ let paddr = addr_space.translate(vaddr);
+ assert!(paddr.is_some());
+}
+
+#[test]
+#[axin(decorator(mock_hal_test))]
+fn test_unmap() {
+ let (mut addr_space, _base, _size) = setup_test_addr_space();
+ let vaddr = GuestPhysAddr::from_usize(0x15000);
+ let map_alloc_size = 0x2000;
+ let flags = MappingFlags::READ | MappingFlags::WRITE;
+
+ // Create mapping
+ addr_space
+ .map_alloc(vaddr, map_alloc_size, flags, true)
+ .unwrap();
+
+ // Verify mapping exists
+ assert!(addr_space.translate(vaddr).is_some());
+ assert!(addr_space.translate(vaddr + 0x1000).is_some());
+
+ let before_unmap_deallocs = DEALLOC_COUNT.load(Ordering::SeqCst);
+
+ // Unmap
+ addr_space.unmap(vaddr, map_alloc_size).unwrap();
+
+ // Verify mapping is removed
+ assert!(addr_space.translate(vaddr).is_none());
+ assert!(addr_space.translate(vaddr + 0x1000).is_none());
+
+ // Verify frames were deallocated
+ let after_unmap_deallocs = DEALLOC_COUNT.load(Ordering::SeqCst);
+ assert!(after_unmap_deallocs > before_unmap_deallocs);
+}
+
+#[test]
+#[axin(decorator(mock_hal_test))]
+fn test_clear() {
+ let (mut addr_space, _base, _size) = setup_test_addr_space();
+ let vaddr1 = GuestPhysAddr::from_usize(0x16000);
+ let vaddr2 = GuestPhysAddr::from_usize(0x17000);
+ let flags = MappingFlags::READ | MappingFlags::WRITE;
+ let map_alloc_size = 0x1000;
+
+ // Create multiple mappings
+ addr_space
+ .map_alloc(vaddr1, map_alloc_size, flags, true)
+ .unwrap();
+ addr_space
+ .map_alloc(vaddr2, map_alloc_size, flags, true)
+ .unwrap();
+
+ // Verify mappings exist
+ assert!(addr_space.translate(vaddr1).is_some());
+ assert!(addr_space.translate(vaddr2).is_some());
+
+ let before_clear_deallocs = DEALLOC_COUNT.load(Ordering::SeqCst);
+
+ // Clear all mappings
+ addr_space.clear();
+
+ // Verify all mappings are removed
+ assert!(addr_space.translate(vaddr1).is_none());
+ assert!(addr_space.translate(vaddr2).is_none());
+
+ // Verify frames were deallocated
+ let after_clear_deallocs = DEALLOC_COUNT.load(Ordering::SeqCst);
+ assert!(after_clear_deallocs > before_clear_deallocs);
+}
+
+#[test]
+#[axin(decorator(mock_hal_test))]
+fn test_translate() {
+ let (mut addr_space, _base, _size) = setup_test_addr_space();
+ let vaddr = GuestPhysAddr::from_usize(0x18000);
+ let map_alloc_size = 0x1000;
+ let flags = MappingFlags::READ | MappingFlags::WRITE;
+
+ // Create mapping
+ addr_space
+ .map_alloc(vaddr, map_alloc_size, flags, true)
+ .unwrap();
+
+ // Verify translation succeeds
+ let paddr = addr_space.translate(vaddr).expect("Translation failed");
+ assert!(paddr.as_usize() >= BASE_PADDR);
+ assert!(paddr.as_usize() < BASE_PADDR + MEMORY_LEN);
+
+ // Verify unmapped address translation fails
+ let unmapped_vaddr = GuestPhysAddr::from_usize(0x19000);
+ assert!(addr_space.translate(unmapped_vaddr).is_none());
+
+ // Verify out-of-range address translation fails
+ let out_of_range = GuestPhysAddr::from_usize(0x30000);
+ assert!(addr_space.translate(out_of_range).is_none());
+}
+
+#[test]
+#[axin(decorator(mock_hal_test))]
+fn test_translated_byte_buffer() {
+ let (mut addr_space, _base, _size) = setup_test_addr_space();
+ let vaddr = GuestPhysAddr::from_usize(0x19000);
+ let map_alloc_size = 0x2000; // 8KB
+ let flags = MappingFlags::READ | MappingFlags::WRITE;
+ let buffer_size = 0x1100;
+
+ // Create mapping
+ addr_space
+ .map_alloc(vaddr, map_alloc_size, flags, true)
+ .unwrap();
+
+ // Verify byte buffer can be obtained
+ let mut buffer = addr_space
+ .translated_byte_buffer(vaddr, buffer_size)
+ .expect("Failed to get byte buffer");
+
+ // Verify data write and read
+ // Fill with values ranging from 0 to 0x100
+ for buffer_segment in buffer.iter_mut() {
+ for (i, byte) in buffer_segment.iter_mut().enumerate() {
+ *byte = (i % 0x100) as u8;
+ }
+ }
+
+ // Verify data read correctness
+ for buffer_segment in buffer.iter_mut() {
+ for (i, byte) in buffer_segment.iter_mut().enumerate() {
+ assert_eq!(*byte, (i % 0x100) as u8);
+ }
+ }
+
+ // Verify exceeding area size returns None
+ assert!(
+ addr_space
+ .translated_byte_buffer(vaddr, map_alloc_size + 0x1000)
+ .is_none()
+ );
+
+ // Verify unmapped address returns None
+ let unmapped_vaddr = GuestPhysAddr::from_usize(0x1D000);
+ assert!(
+ addr_space
+ .translated_byte_buffer(unmapped_vaddr, 0x100)
+ .is_none()
+ );
+}
+
+#[test]
+#[axin(decorator(mock_hal_test))]
+fn test_translate_and_get_limit() {
+ let (mut addr_space, _base, _size) = setup_test_addr_space();
+ let vaddr = GuestPhysAddr::from_usize(0x1A000);
+ let map_alloc_size = 0x3000; // 12KB
+ let flags = MappingFlags::READ | MappingFlags::WRITE;
+
+ // Create mapping
+ addr_space
+ .map_alloc(vaddr, map_alloc_size, flags, true)
+ .unwrap();
+
+ // Verify translation and area size retrieval
+ let (paddr, area_size) = addr_space.translate_and_get_limit(vaddr).unwrap();
+ assert!(paddr.as_usize() >= BASE_PADDR && paddr.as_usize() < BASE_PADDR + MEMORY_LEN);
+ assert_eq!(area_size, map_alloc_size);
+
+ // Verify unmapped address returns None
+ let unmapped_vaddr = GuestPhysAddr::from_usize(0x1E000);
+ assert!(addr_space.translate_and_get_limit(unmapped_vaddr).is_none());
+
+ // Verify out-of-range address returns None
+ let out_of_range = GuestPhysAddr::from_usize(0x30000);
+ assert!(addr_space.translate_and_get_limit(out_of_range).is_none());
+}
diff --git a/tests/frame.rs b/tests/frame.rs
new file mode 100644
index 0000000..eab6bd2
--- /dev/null
+++ b/tests/frame.rs
@@ -0,0 +1,104 @@
+// Copyright 2025 The Axvisor Team
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+extern crate alloc;
+
+mod test_utils;
+
+use alloc::vec::Vec;
+use assert_matches::assert_matches;
+use axaddrspace::PhysFrame;
+use axin::axin;
+use memory_addr::PAGE_SIZE_4K as PAGE_SIZE;
+use test_utils::{BASE_PADDR, MockHal, mock_hal_test, test_dealloc_count};
+
+#[test]
+#[axin(decorator(mock_hal_test), on_exit(test_dealloc_count(1)))]
+fn test_alloc_dealloc_cycle() {
+ let frame = PhysFrame::::alloc()
+ .unwrap_or_else(|e| panic!("Failed to allocate frame: {:?}", e));
+ assert_eq!(frame.start_paddr().as_usize(), BASE_PADDR);
+ // frame is dropped here, dealloc_frame should be called
+}
+
+#[test]
+#[axin(decorator(mock_hal_test), on_exit(test_dealloc_count(1)))]
+fn test_alloc_zero() {
+ let frame = PhysFrame::::alloc_zero()
+ .unwrap_or_else(|e| panic!("Failed to allocate zero frame: {:?}", e));
+ assert_eq!(frame.start_paddr().as_usize(), BASE_PADDR);
+ let ptr = frame.as_mut_ptr();
+ let page = unsafe { &*(ptr as *const [u8; PAGE_SIZE]) };
+ assert!(page.iter().all(|&x| x == 0));
+}
+
+#[test]
+#[axin(decorator(mock_hal_test), on_exit(test_dealloc_count(1)))]
+fn test_fill_operation() {
+ let mut frame = PhysFrame::::alloc()
+ .unwrap_or_else(|e| panic!("Failed to allocate frame: {:?}", e));
+ assert_eq!(frame.start_paddr().as_usize(), BASE_PADDR);
+ frame.fill(0xAA);
+ let ptr = frame.as_mut_ptr();
+ let page = unsafe { &*(ptr as *const [u8; PAGE_SIZE]) };
+ assert!(page.iter().all(|&x| x == 0xAA));
+}
+
+#[test]
+#[axin(decorator(mock_hal_test), on_exit(test_dealloc_count(5)))]
+fn test_fill_multiple_frames() {
+ const NUM_FRAMES: usize = 5;
+
+ let mut frames = Vec::new();
+ let mut patterns = Vec::new();
+
+ for i in 0..NUM_FRAMES {
+ let mut frame = PhysFrame::::alloc().unwrap();
+ let pattern = (0xA0 + i) as u8;
+ frame.fill(pattern);
+ frames.push(frame);
+ patterns.push(pattern);
+ }
+
+ for i in 0..NUM_FRAMES {
+ let actual_page = unsafe { &*(frames[i].as_mut_ptr() as *mut [u8; PAGE_SIZE]) };
+ let expected_page = &[patterns[i]; PAGE_SIZE];
+
+ assert_eq!(
+ actual_page, expected_page,
+ "Frame verification failed for frame index {i}: Expected pattern 0x{:02x}",
+ patterns[i]
+ );
+ }
+}
+
+#[test]
+#[should_panic(expected = "uninitialized PhysFrame")]
+fn test_uninit_access() {
+ // This test verifies that accessing an uninitialized PhysFrame (created with `unsafe { uninit() }`)
+ // leads to a panic when trying to retrieve its physical address.
+ let frame = unsafe { PhysFrame::::uninit() };
+ frame.start_paddr(); // This should panic
+}
+
+#[test]
+#[axin(decorator(mock_hal_test), on_exit(test_dealloc_count(0)))]
+fn test_alloc_no_memory() {
+ // Configure MockHal to simulate an allocation failure.
+ MockHal::set_alloc_fail(true);
+ let result = PhysFrame::::alloc();
+ // Assert that allocation failed and verify the specific error type.
+ assert_matches!(result, Err(axerrno::AxError::NoMemory));
+ MockHal::set_alloc_fail(false); // Reset for other tests
+}
diff --git a/tests/memory_accessor.rs b/tests/memory_accessor.rs
new file mode 100644
index 0000000..95f4dc4
--- /dev/null
+++ b/tests/memory_accessor.rs
@@ -0,0 +1,269 @@
+// Copyright 2025 The Axvisor Team
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+mod test_utils;
+
+use axaddrspace::{GuestMemoryAccessor, GuestPhysAddr};
+use axerrno::AxResult;
+use axin::axin;
+use memory_addr::PhysAddr;
+use test_utils::{BASE_PADDR, MEMORY_LEN, MockHal, mock_hal_test};
+
+/// Mock implementation of GuestMemoryAccessor for testing
+struct MockTranslator {
+ base_addr: PhysAddr,
+ memory_size: usize,
+}
+
+impl MockTranslator {
+ pub fn new(base_addr: PhysAddr, memory_size: usize) -> Self {
+ Self {
+ base_addr,
+ memory_size,
+ }
+ }
+}
+
+impl GuestMemoryAccessor for MockTranslator {
+ fn translate_and_get_limit(&self, guest_addr: GuestPhysAddr) -> Option<(PhysAddr, usize)> {
+ // Simple mapping: guest address directly maps to mock memory region
+ let offset = guest_addr.as_usize();
+ if offset < self.memory_size {
+ // Convert physical address to virtual address for actual memory access
+ let phys_addr = PhysAddr::from_usize(BASE_PADDR + self.base_addr.as_usize() + offset);
+ let virt_addr = MockHal::mock_phys_to_virt(phys_addr);
+ let accessible_size = self.memory_size - offset;
+ Some((PhysAddr::from_usize(virt_addr.as_usize()), accessible_size))
+ } else {
+ None
+ }
+ }
+}
+
+#[test]
+#[axin(decorator(mock_hal_test))]
+fn test_basic_read_write_operations() {
+ let translator = MockTranslator::new(PhysAddr::from_usize(0), MEMORY_LEN);
+
+ // Test u32 read/write operations
+ let test_addr = GuestPhysAddr::from_usize(0x100);
+ let test_value: u32 = 0x12345678;
+
+ // Write a u32 value
+ translator
+ .write_obj(test_addr, test_value)
+ .expect("Failed to write u32 value");
+
+ // Read back the u32 value
+ let read_value: u32 = translator
+ .read_obj(test_addr)
+ .expect("Failed to read u32 value");
+
+ assert_eq!(
+ read_value, test_value,
+ "Read value should match written value"
+ );
+
+ // Test buffer read/write operations
+ let buffer_addr = GuestPhysAddr::from_usize(0x200);
+ let test_buffer = [0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88];
+
+ // Write buffer
+ translator
+ .write_buffer(buffer_addr, &test_buffer)
+ .expect("Failed to write buffer");
+
+ // Read buffer back
+ let mut read_buffer = [0u8; 8];
+ translator
+ .read_buffer(buffer_addr, &mut read_buffer)
+ .expect("Failed to read buffer");
+
+ assert_eq!(
+ read_buffer, test_buffer,
+ "Read buffer should match written buffer"
+ );
+
+ // Test error handling with invalid address
+ let invalid_addr = GuestPhysAddr::from_usize(MEMORY_LEN + 0x1000);
+ let result: AxResult = translator.read_obj(invalid_addr);
+ assert!(result.is_err(), "Reading from invalid address should fail");
+
+ let result = translator.write_obj(invalid_addr, 42u32);
+ assert!(result.is_err(), "Writing to invalid address should fail");
+}
+
+#[test]
+#[axin(decorator(mock_hal_test))]
+fn test_two_vm_isolation() {
+ // Create two different translators to simulate two different VMs
+ let vm1_translator = MockTranslator::new(PhysAddr::from_usize(0), MEMORY_LEN / 2); // Offset for VM1
+ let vm2_translator = MockTranslator::new(PhysAddr::from_usize(MEMORY_LEN / 2), MEMORY_LEN); // Offset for VM2
+
+ // Both VMs write to the same guest address but different host memory regions
+ let guest_addr = GuestPhysAddr::from_usize(0x100);
+ let vm1_data: u64 = 0xDEADBEEFCAFEBABE;
+ let vm2_data: u64 = 0x1234567890ABCDEF;
+
+ // VM1 writes its data
+ vm1_translator
+ .write_obj(guest_addr, vm1_data)
+ .expect("VM1 failed to write data");
+
+ // VM2 writes its data
+ vm2_translator
+ .write_obj(guest_addr, vm2_data)
+ .expect("VM2 failed to write data");
+
+ // Both VMs read back their own data - should be isolated
+ let vm1_read: u64 = vm1_translator
+ .read_obj(guest_addr)
+ .expect("VM1 failed to read data");
+ let vm2_read: u64 = vm2_translator
+ .read_obj(guest_addr)
+ .expect("VM2 failed to read data");
+
+ // Verify isolation: each VM should read its own data
+ assert_eq!(vm1_read, vm1_data, "VM1 should read its own data");
+ assert_eq!(vm2_read, vm2_data, "VM2 should read its own data");
+ assert_ne!(
+ vm1_read, vm2_read,
+ "VM1 and VM2 should have different data (isolation)"
+ );
+
+ // Test buffer operations with different patterns
+ let buffer_addr = GuestPhysAddr::from_usize(0x200);
+ let vm1_buffer = [0xAA; 16]; // Pattern for VM1
+ let vm2_buffer = [0x55; 16]; // Pattern for VM2
+
+ // Both VMs write their patterns
+ vm1_translator
+ .write_buffer(buffer_addr, &vm1_buffer)
+ .expect("VM1 failed to write buffer");
+ vm2_translator
+ .write_buffer(buffer_addr, &vm2_buffer)
+ .expect("VM2 failed to write buffer");
+
+ // Read back and verify isolation
+ let mut vm1_read_buffer = [0u8; 16];
+ let mut vm2_read_buffer = [0u8; 16];
+
+ vm1_translator
+ .read_buffer(buffer_addr, &mut vm1_read_buffer)
+ .expect("VM1 failed to read buffer");
+ vm2_translator
+ .read_buffer(buffer_addr, &mut vm2_read_buffer)
+ .expect("VM2 failed to read buffer");
+
+ assert_eq!(
+ vm1_read_buffer, vm1_buffer,
+ "VM1 should read its own buffer pattern"
+ );
+ assert_eq!(
+ vm2_read_buffer, vm2_buffer,
+ "VM2 should read its own buffer pattern"
+ );
+ assert_ne!(
+ vm1_read_buffer, vm2_read_buffer,
+ "VM buffers should be isolated"
+ );
+
+ // Test that VM1 cannot access VM2's address space (beyond its limit)
+ let vm2_only_addr = GuestPhysAddr::from_usize(MEMORY_LEN / 2 + 0x100);
+ let result: AxResult = vm1_translator.read_obj(vm2_only_addr);
+ assert!(
+ result.is_err(),
+ "VM1 should not be able to access VM2's exclusive address space"
+ );
+}
+
+#[test]
+#[axin(decorator(mock_hal_test))]
+fn test_cross_page_access() {
+ let translator = MockTranslator::new(PhysAddr::from_usize(0), MEMORY_LEN);
+
+ // Test cross-region buffer operations
+ // Place buffer near a region boundary to test multi-region access
+ let cross_region_addr = GuestPhysAddr::from_usize(4096 - 8); // 8 bytes before 4K boundary
+ let test_data = [
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+ 0x10,
+ ]; // 16 bytes
+
+ // Write cross-region data
+ translator
+ .write_buffer(cross_region_addr, &test_data)
+ .expect("Failed to write cross-region buffer");
+
+ // Read cross-region data back
+ let mut read_data = [0u8; 16];
+ translator
+ .read_buffer(cross_region_addr, &mut read_data)
+ .expect("Failed to read cross-region buffer");
+
+ assert_eq!(
+ read_data, test_data,
+ "Cross-region read should match written data"
+ );
+
+ // Test individual byte access across region boundary
+ for (i, &expected_byte) in test_data.iter().enumerate() {
+ let byte_addr = GuestPhysAddr::from_usize(cross_region_addr.as_usize() + i);
+ let read_byte: u8 = translator
+ .read_obj(byte_addr)
+ .expect("Failed to read individual byte");
+ assert_eq!(
+ read_byte, expected_byte,
+ "Byte at offset {} should match",
+ i
+ );
+ }
+}
+
+#[test]
+#[axin(decorator(mock_hal_test))]
+fn test_region_boundary_edge_cases() {
+ let translator = MockTranslator::new(PhysAddr::from_usize(0), MEMORY_LEN);
+
+ let boundary_addr = GuestPhysAddr::from_usize(4096);
+ let boundary_data = [0xAB, 0xCD, 0xEF, 0x12];
+
+ translator
+ .write_buffer(boundary_addr, &boundary_data)
+ .expect("Failed to write at boundary");
+
+ let mut read_boundary = [0u8; 4];
+ translator
+ .read_buffer(boundary_addr, &mut read_boundary)
+ .expect("Failed to read at boundary");
+
+ assert_eq!(read_boundary, boundary_data, "Boundary data should match");
+
+ // Test zero-size buffer (should not fail)
+ let empty_buffer: &[u8] = &[];
+ translator
+ .write_buffer(boundary_addr, empty_buffer)
+ .expect("Empty buffer write should succeed");
+
+ let mut empty_read: &mut [u8] = &mut [];
+ translator
+ .read_buffer(boundary_addr, &mut empty_read)
+ .expect("Empty buffer read should succeed");
+
+ // Test single byte at boundary (should work fine)
+ let single_byte = [0x42];
+ translator
+ .write_buffer(boundary_addr, &single_byte)
+ .expect("Single byte write should succeed");
+}
diff --git a/src/test_utils/mod.rs b/tests/test_utils/mod.rs
similarity index 85%
rename from src/test_utils/mod.rs
rename to tests/test_utils/mod.rs
index e2d6a47..cc0a17b 100644
--- a/src/test_utils/mod.rs
+++ b/tests/test_utils/mod.rs
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use crate::{AxMmHal, HostPhysAddr, HostVirtAddr};
+use axaddrspace::{AxMmHal, HostPhysAddr, HostVirtAddr};
use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use lazy_static::lazy_static;
use memory_addr::{PhysAddr, VirtAddr};
@@ -23,17 +23,17 @@ use memory_addr::PAGE_SIZE_4K as PAGE_SIZE;
/// The starting physical address for the simulated memory region in tests.
/// This offset is used to map simulated physical addresses to the `MEMORY` array's virtual address space.
-pub(crate) const BASE_PADDR: usize = 0x1000;
+pub const BASE_PADDR: usize = 0x1000;
/// Static variables to simulate global state of a memory allocator in tests.
-pub(crate) static NEXT_PADDR: AtomicUsize = AtomicUsize::new(BASE_PADDR);
+pub static NEXT_PADDR: AtomicUsize = AtomicUsize::new(BASE_PADDR);
/// Total length of the simulated physical memory block for testing, in bytes.
-pub(crate) const MEMORY_LEN: usize = 0x10000; // 64KB for testing
+pub const MEMORY_LEN: usize = 0x10000; // 64KB for testing
// Use #[repr(align(4096))] to ensure 4KB alignment
#[repr(align(4096))]
-pub(crate) struct AlignedMemory([u8; MEMORY_LEN]);
+pub struct AlignedMemory([u8; MEMORY_LEN]);
impl Default for AlignedMemory {
fn default() -> Self {
@@ -43,21 +43,21 @@ impl Default for AlignedMemory {
lazy_static! {
/// Simulates the actual physical memory block used for allocation.
- pub(crate) static ref MEMORY: Mutex = Mutex::new(AlignedMemory::default());
+ pub static ref MEMORY: Mutex = Mutex::new(AlignedMemory::default());
/// Global mutex to enforce serial execution for tests that modify shared state.
/// This ensures test isolation and prevents race conditions between tests.
- pub(crate) static ref TEST_MUTEX: Mutex<()> = Mutex::new(());
+ pub static ref TEST_MUTEX: Mutex<()> = Mutex::new(());
}
/// Counter to track the number of allocations. (Added from Chen Hong's code)
-pub(crate) static ALLOC_COUNT: AtomicUsize = AtomicUsize::new(0);
+pub static ALLOC_COUNT: AtomicUsize = AtomicUsize::new(0);
/// Counter to track the number of deallocations.
-pub(crate) static DEALLOC_COUNT: AtomicUsize = AtomicUsize::new(0);
+pub static DEALLOC_COUNT: AtomicUsize = AtomicUsize::new(0);
/// Flag to simulate memory allocation failures for testing error handling.
-pub(crate) static ALLOC_SHOULD_FAIL: AtomicBool = AtomicBool::new(false);
+pub static ALLOC_SHOULD_FAIL: AtomicBool = AtomicBool::new(false);
#[derive(Debug)]
/// A mock implementation of AxMmHal for testing purposes.
@@ -65,7 +65,7 @@ pub(crate) static ALLOC_SHOULD_FAIL: AtomicBool = AtomicBool::new(false);
///
/// The `Debug` trait is derived because `assert_matches!` on `Result, _>`
/// requires `PhysFrame` (the `T` type) to implement `Debug` for diagnostic output on assertion failure.
-pub(crate) struct MockHal {}
+pub struct MockHal {}
impl AxMmHal for MockHal {
fn alloc_frame() -> Option {
@@ -121,7 +121,7 @@ impl PagingHandler for MockHal {
}
/// A utility decorator for test functions that require the MockHal state to be reset before execution.
-pub(crate) fn mock_hal_test(test_fn: F) -> R
+pub fn mock_hal_test(test_fn: F) -> R
where
F: FnOnce() -> R,
{
@@ -131,7 +131,7 @@ where
}
/// A utility function to verify the number of deallocations performed by the MockHal.
-pub(crate) fn test_dealloc_count(expected: usize) {
+pub fn test_dealloc_count(expected: usize) {
let actual_dealloc_count = DEALLOC_COUNT.load(Ordering::SeqCst);
assert_eq!(
actual_dealloc_count, expected,
@@ -141,7 +141,7 @@ pub(crate) fn test_dealloc_count(expected: usize) {
impl MockHal {
/// Simulates the allocation of a single physical frame.
- pub(crate) fn mock_alloc_frame() -> Option {
+ pub fn mock_alloc_frame() -> Option {
// Use a static mutable variable to control alloc_should_fail state
if ALLOC_SHOULD_FAIL.load(Ordering::SeqCst) {
return None;
@@ -156,14 +156,14 @@ impl MockHal {
}
/// Simulates the deallocation of a single physical frame.
- pub(crate) fn mock_dealloc_frame(_paddr: PhysAddr) {
+ pub fn mock_dealloc_frame(_paddr: PhysAddr) {
DEALLOC_COUNT.fetch_add(1, Ordering::SeqCst);
}
/// In this test mock, the "virtual address" is simply a direct pointer
/// to the corresponding location within the `MEMORY` array.
/// It simulates a physical-to-virtual memory mapping for test purposes.
- pub(crate) fn mock_phys_to_virt(paddr: PhysAddr) -> VirtAddr {
+ pub fn mock_phys_to_virt(paddr: PhysAddr) -> VirtAddr {
let paddr_usize = paddr.as_usize();
assert!(
paddr_usize >= BASE_PADDR && paddr_usize < BASE_PADDR + MEMORY_LEN,
@@ -175,7 +175,7 @@ impl MockHal {
}
/// Maps a virtual address (within the test process) back to a simulated physical address.
- pub(crate) fn mock_virt_to_phys(vaddr: VirtAddr) -> PhysAddr {
+ pub fn mock_virt_to_phys(vaddr: VirtAddr) -> PhysAddr {
let base_virt = MEMORY.lock().0.as_ptr() as usize;
let vaddr_usize = vaddr.as_usize();
assert!(
@@ -188,13 +188,13 @@ impl MockHal {
}
/// Helper function to control the simulated allocation failure.
- pub(crate) fn set_alloc_fail(fail: bool) {
+ pub fn set_alloc_fail(fail: bool) {
ALLOC_SHOULD_FAIL.store(fail, Ordering::SeqCst);
}
/// Resets all static state of the MockHal to its initial, clean state.
/// This is crucial for ensuring test isolation between individual test functions.
- pub(crate) fn reset_state() {
+ pub fn reset_state() {
NEXT_PADDR.store(BASE_PADDR, Ordering::SeqCst);
ALLOC_SHOULD_FAIL.store(false, Ordering::SeqCst);
ALLOC_COUNT.store(0, Ordering::SeqCst);