From b56cf99403e5b49b8e607c48a45c60a252c7be0a Mon Sep 17 00:00:00 2001 From: szy Date: Thu, 19 Mar 2026 16:50:24 +0800 Subject: [PATCH] refactor: add scripts/tests/CI workflows and docs --- .github/config.json | 7 + .github/workflows/check.yml | 69 +----- .github/workflows/deploy.yml | 125 +--------- .github/workflows/push.yml | 143 +----------- .github/workflows/release.yml | 161 ++----------- .github/workflows/test.yml | 43 +--- .gitignore | 8 + README.md | 7 +- README_CN.md | 204 ++++++++++++++++ rust-toolchain.toml | 2 +- scripts/check.sh | 35 +++ scripts/test.sh | 34 +++ tests/register_tests.rs | 425 ++++++++++++++++++++++++++++++++++ 13 files changed, 763 insertions(+), 500 deletions(-) create mode 100644 README_CN.md create mode 100755 scripts/check.sh create mode 100755 scripts/test.sh create mode 100644 tests/register_tests.rs diff --git a/.github/config.json b/.github/config.json index c750554..9c42e83 100644 --- a/.github/config.json +++ b/.github/config.json @@ -1,7 +1,14 @@ { + "component": { + "name": "riscv-h", + "crate_name": "riscv_h" + }, "targets": [ "riscv64gc-unknown-none-elf" ], + "unit_test_targets": [ + "x86_64-unknown-linux-gnu" + ], "rust_components": [ "rust-src", "clippy", diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 330fa15..765b053 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -1,66 +1,17 @@ -name: Quality Checks +# Quality Check Workflow +# References shared workflow from axci + +name: Check on: push: - branches: - - '**' - tags-ignore: - - '**' + branches: ['**'] + 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 }} - rust_components: ${{ steps.config.outputs.rust_components }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Load configuration - id: config - run: | - TARGETS=$(jq -c '.targets' .github/config.json) - COMPONENTS=$(jq -r '.rust_components | join(", ")' .github/config.json) - - echo "targets=$TARGETS" >> $GITHUB_OUTPUT - echo "rust_components=$COMPONENTS" >> $GITHUB_OUTPUT - check: - name: Check - 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 - with: - components: ${{ needs.load-config.outputs.rust_components }} - targets: ${{ matrix.target }} - - - name: Check rust version - run: rustc --version --verbose - - - name: Check code format - run: cargo fmt --all -- --check - - - name: Build - run: cargo build --target ${{ matrix.target }} --all-features - - - name: Run clippy - run: cargo clippy --target ${{ matrix.target }} --all-features -- -D warnings - - - name: Build documentation - env: - RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs - run: cargo doc --no-deps --target ${{ matrix.target }} --all-features + uses: arceos-hypervisor/axci/.github/workflows/check.yml@main + with: + targets: '["riscv64gc-unknown-none-elf"]' diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index fda9a32..8f47dbe 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,3 +1,6 @@ +# Deploy Workflow +# References shared workflow from axci + name: Deploy on: @@ -5,122 +8,10 @@ on: tags: - 'v[0-9]+.[0-9]+.[0-9]+' -permissions: - contents: read - pages: write - id-token: write - -concurrency: - group: 'pages' - cancel-in-progress: false - -env: - CARGO_TERM_COLOR: always - RUST_BACKTRACE: 1 - jobs: - verify-tag: - name: Verify Tag - runs-on: ubuntu-latest - outputs: - should_deploy: ${{ steps.check.outputs.should_deploy }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Check if tag is on main or master branch - id: check - run: | - git fetch origin main master || true - BRANCHES=$(git branch -r --contains ${{ github.ref }}) - - if echo "$BRANCHES" | grep -qE 'origin/(main|master)'; then - echo "✓ Tag is on main or master branch" - echo "should_deploy=true" >> $GITHUB_OUTPUT - else - echo "✗ Tag is not on main or master branch, skipping deployment" - echo "Tag is on: $BRANCHES" - echo "should_deploy=false" >> $GITHUB_OUTPUT - fi - - - name: Verify version consistency - if: steps.check.outputs.should_deploy == 'true' - run: | - # Extract version from git tag (remove 'v' prefix) - TAG_VERSION="${{ github.ref_name }}" - TAG_VERSION="${TAG_VERSION#v}" - # Extract version from Cargo.toml - CARGO_VERSION=$(grep -m1 '^version' Cargo.toml | sed 's/.*"\(.*\)"/\1/') - echo "Git tag version: $TAG_VERSION" - echo "Cargo.toml version: $CARGO_VERSION" - if [ "$TAG_VERSION" != "$CARGO_VERSION" ]; then - echo "ERROR: Version mismatch! Tag version ($TAG_VERSION) != Cargo.toml version ($CARGO_VERSION)" - exit 1 - fi - echo "✓ Version check passed!" - - check: - uses: ./.github/workflows/check.yml - needs: verify-tag - if: needs.verify-tag.outputs.should_deploy == 'true' - - test: - uses: ./.github/workflows/test.yml - needs: verify-tag - if: needs.verify-tag.outputs.should_deploy == 'true' - - build: - name: Build documentation - runs-on: ubuntu-latest - needs: [verify-tag, check, test] - if: needs.verify-tag.outputs.should_deploy == 'true' - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@nightly - - - name: Build docs - env: - RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs - run: | - # Build documentation - cargo doc --no-deps --all-features - - # Auto-detect documentation directory - # Check if doc exists in target/doc or target/*/doc - if [ -d "target/doc" ]; then - DOC_DIR="target/doc" - else - # Find doc directory under target/*/doc pattern - DOC_DIR=$(find target -type d -name doc -path "target/*/doc" | head -n 1) - if [ -z "$DOC_DIR" ]; then - echo "Error: Could not find documentation directory" - exit 1 - fi - fi - - echo "Documentation found in: $DOC_DIR" - printf '' $(cargo tree | head -1 | cut -d' ' -f1) > "${DOC_DIR}/index.html" - echo "DOC_DIR=${DOC_DIR}" >> $GITHUB_ENV - - - name: Upload artifact - uses: actions/upload-pages-artifact@v3 - with: - path: ${{ env.DOC_DIR }} - deploy: - name: Deploy to GitHub Pages - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - needs: [verify-tag, build] - if: needs.verify-tag.outputs.should_deploy == 'true' - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 + uses: arceos-hypervisor/axci/.github/workflows/deploy.yml@main + with: + targets: '["riscv64gc-unknown-none-elf"]' + verify_branch: true + verify_version: true diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index a18de4a..dda5717 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -1,49 +1,5 @@ -# ═══════════════════════════════════════════════════════════════════════════════ -# 组件仓库 GitHub Actions 配置模板 -# ═══════════════════════════════════════════════════════════════════════════════ -# -# 此文件用于子仓库,当子仓库有更新时通知主仓库进行 subtree pull 同步。 -# -# 【使用步骤】 -# ───────────────────────────────────────────────────────────────────────────── -# 1. 将此文件复制到子仓库的 .github/workflows/ 目录: -# cp scripts/push.yml <子仓库>/.github/workflows/push.yml -# -# 2. 在子仓库中配置 Secret: -# GitHub 仓库 → Settings → Secrets → Actions → New repository secret -# 名称: PARENT_REPO_TOKEN -# 值: 具有主仓库 repo 权限的 Personal Access Token -# -# 3. 修改下方 env 块中的一个变量(标注了「需要修改」的行): -# PARENT_REPO - 主仓库路径,例如 rcore-os/tgoskits -# (subtree 目录由主仓库自动从 git 历史中推断,无需手动指定) -# -# 【Token 权限要求】 -# ───────────────────────────────────────────────────────────────────────────── -# PARENT_REPO_TOKEN 需要 Classic Personal Access Token,权限包括: -# - repo (Full control of private repositories) -# 或 -# - Fine-grained token: Contents (Read and Write) -# -# 【触发条件】 -# ───────────────────────────────────────────────────────────────────────────── -# - 自动触发:推送到 dev 或 main 分支时 -# - 手动触发:Actions → Notify Parent Repository → Run workflow -# -# 【工作流程】 -# ───────────────────────────────────────────────────────────────────────────── -# 子仓库 push → 触发此工作流 → 调用主仓库 API → 主仓库 subtree pull -# -# 【注意事项】 -# ───────────────────────────────────────────────────────────────────────────── -# - 主仓库需要配置接收 repository_dispatch 事件的同步工作流 -# - 如果不需要子仓库到主仓库的同步,可以不使用此文件 -# -# ═══════════════════════════════════════════════════════════════════════════════ - name: Notify Parent Repository -# 当有新的推送时触发 on: push: branches: @@ -52,96 +8,9 @@ on: workflow_dispatch: jobs: - notify: - runs-on: ubuntu-latest - steps: - - name: Get repository info - id: repo - env: - GH_REPO_NAME: ${{ github.event.repository.name }} - GH_REF_NAME: ${{ github.ref_name }} - GH_SERVER_URL: ${{ github.server_url }} - GH_REPOSITORY: ${{ github.repository }} - run: | - # 直接使用 GitHub Actions 内置变量,通过 env 传入避免 shell 注入 - COMPONENT="$GH_REPO_NAME" - BRANCH="$GH_REF_NAME" - # 构造标准 HTTPS URL,供主仓库按 URL 精确匹配 repos.list - REPO_URL="${GH_SERVER_URL}/${GH_REPOSITORY}" - - echo "component=${COMPONENT}" >> $GITHUB_OUTPUT - echo "branch=${BRANCH}" >> $GITHUB_OUTPUT - echo "repo_url=${REPO_URL}" >> $GITHUB_OUTPUT - - echo "Component: ${COMPONENT}" - echo "Branch: ${BRANCH}" - echo "Repo URL: ${REPO_URL}" - - - name: Notify parent repository - env: - # ── 需要修改 ────────────────────────────────────────────────────────── - PARENT_REPO: "rcore-os/tgoskits" # 主仓库路径 - # ── 无需修改 ────────────────────────────────────────────────────────── - DISPATCH_TOKEN: ${{ secrets.PARENT_REPO_TOKEN }} - # 将用户可控内容通过 env 传入,避免直接插值到 shell 脚本 - COMMIT_MESSAGE: ${{ github.event.head_commit.message }} - GIT_ACTOR: ${{ github.actor }} - GIT_SHA: ${{ github.sha }} - STEP_COMPONENT: ${{ steps.repo.outputs.component }} - STEP_BRANCH: ${{ steps.repo.outputs.branch }} - STEP_REPO_URL: ${{ steps.repo.outputs.repo_url }} - run: | - COMPONENT="$STEP_COMPONENT" - BRANCH="$STEP_BRANCH" - REPO_URL="$STEP_REPO_URL" - - echo "Notifying parent repository about update in ${COMPONENT}:${BRANCH}" - - # 使用 jq 安全构建 JSON,避免 commit message 中任何特殊字符导致注入 - PAYLOAD=$(jq -n \ - --arg component "$COMPONENT" \ - --arg branch "$BRANCH" \ - --arg repo_url "$REPO_URL" \ - --arg commit "$GIT_SHA" \ - --arg message "$COMMIT_MESSAGE" \ - --arg author "$GIT_ACTOR" \ - '{ - event_type: "subtree-update", - client_payload: { - component: $component, - branch: $branch, - repo_url: $repo_url, - commit: $commit, - message: $message, - author: $author - } - }') - - curl --fail --show-error -X POST \ - -H "Accept: application/vnd.github.v3+json" \ - -H "Authorization: token ${DISPATCH_TOKEN}" \ - https://api.github.com/repos/${PARENT_REPO}/dispatches \ - -d "$PAYLOAD" - - echo "Notification sent successfully" - - - name: Create summary - env: - STEP_COMPONENT: ${{ steps.repo.outputs.component }} - STEP_BRANCH: ${{ steps.repo.outputs.branch }} - STEP_REPO_URL: ${{ steps.repo.outputs.repo_url }} - GIT_SHA: ${{ github.sha }} - GIT_ACTOR: ${{ github.actor }} - run: | - COMPONENT="$STEP_COMPONENT" - BRANCH="$STEP_BRANCH" - REPO_URL="$STEP_REPO_URL" - - echo "## Notification Summary" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "- **Component**: ${COMPONENT}" >> $GITHUB_STEP_SUMMARY - echo "- **Branch**: ${BRANCH}" >> $GITHUB_STEP_SUMMARY - echo "- **Repo URL**: ${REPO_URL}" >> $GITHUB_STEP_SUMMARY - echo "- **Commit**: \`${GIT_SHA}\`" >> $GITHUB_STEP_SUMMARY - echo "- **Author**: ${GIT_ACTOR}" >> $GITHUB_STEP_SUMMARY - echo "- **Status**: ✅ Notification sent" >> $GITHUB_STEP_SUMMARY + notify-parent: + name: Notify Parent Repository + # 调用 axci 仓库的可复用工作流 + uses: arceos-hypervisor/axci/.github/workflows/push.yml@main + secrets: + PARENT_REPO_TOKEN: ${{ secrets.PARENT_REPO_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2e857b4..c6da7f4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,3 +1,7 @@ +# Release Workflow +# References shared workflow from axci +# check + test must pass before release + name: Release on: @@ -6,155 +10,20 @@ on: - 'v[0-9]+.[0-9]+.[0-9]+' - 'v[0-9]+.[0-9]+.[0-9]+-pre.[0-9]+' -permissions: - contents: write - jobs: - verify-tag: - name: Verify Tag - runs-on: ubuntu-latest - outputs: - should_release: ${{ steps.check.outputs.should_release }} - is_prerelease: ${{ steps.check.outputs.is_prerelease }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Check tag type and branch - id: check - run: | - git fetch origin main master dev || true - - TAG="${{ github.ref_name }}" - BRANCHES=$(git branch -r --contains ${{ github.ref }}) - - echo "Tag: $TAG" - echo "Branches containing this tag: $BRANCHES" - - # Check if it's a prerelease tag - if [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+-pre\.[0-9]+$ ]]; then - echo "📦 Detected prerelease tag" - echo "is_prerelease=true" >> $GITHUB_OUTPUT - - if echo "$BRANCHES" | grep -q 'origin/dev'; then - echo "✓ Prerelease tag is on dev branch" - echo "should_release=true" >> $GITHUB_OUTPUT - else - echo "✗ Prerelease tag must be on dev branch, skipping release" - echo "should_release=false" >> $GITHUB_OUTPUT - fi - elif [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo "📦 Detected stable release tag" - echo "is_prerelease=false" >> $GITHUB_OUTPUT - - if echo "$BRANCHES" | grep -qE 'origin/(main|master)'; then - echo "✓ Stable release tag is on main or master branch" - echo "should_release=true" >> $GITHUB_OUTPUT - else - echo "✗ Stable release tag must be on main or master branch, skipping release" - echo "should_release=false" >> $GITHUB_OUTPUT - fi - else - echo "✗ Unknown tag format, skipping release" - echo "is_prerelease=false" >> $GITHUB_OUTPUT - echo "should_release=false" >> $GITHUB_OUTPUT - fi - - - name: Verify version consistency - if: steps.check.outputs.should_release == 'true' - run: | - # Extract version from git tag (remove 'v' prefix) - TAG_VERSION="${{ github.ref_name }}" - TAG_VERSION="${TAG_VERSION#v}" - # Extract version from Cargo.toml - CARGO_VERSION=$(grep -m1 '^version' Cargo.toml | sed 's/.*"\(.*\)"/\1/') - echo "Git tag version: $TAG_VERSION" - echo "Cargo.toml version: $CARGO_VERSION" - if [ "$TAG_VERSION" != "$CARGO_VERSION" ]; then - echo "ERROR: Version mismatch! Tag version ($TAG_VERSION) != Cargo.toml version ($CARGO_VERSION)" - exit 1 - fi - echo "✓ Version check passed!" - check: - uses: ./.github/workflows/check.yml - needs: verify-tag - if: needs.verify-tag.outputs.should_release == 'true' + uses: arceos-hypervisor/axci/.github/workflows/check.yml@main + with: + targets: '["riscv64gc-unknown-none-elf"]' test: - uses: ./.github/workflows/test.yml - needs: [verify-tag, check] - if: needs.verify-tag.outputs.should_release == 'true' + uses: arceos-hypervisor/axci/.github/workflows/test.yml@main release: - name: Create GitHub Release - runs-on: ubuntu-latest - needs: [verify-tag, check] - if: needs.verify-tag.outputs.should_release == 'true' - - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Generate release notes - id: release_notes - run: | - CURRENT_TAG="${{ github.ref_name }}" - - # Get previous tag - PREVIOUS_TAG=$(git tag --sort=-version:refname | grep -A1 "^${CURRENT_TAG}$" | tail -n1) - - if [ -z "$PREVIOUS_TAG" ] || [ "$PREVIOUS_TAG" == "$CURRENT_TAG" ]; then - echo "No previous tag found, this is the first release" - CHANGELOG="Initial release" - else - echo "Generating changelog from $PREVIOUS_TAG to $CURRENT_TAG" - - # Generate changelog with commit messages - CHANGELOG=$(git log --pretty=format:"- %s (%h)" "${PREVIOUS_TAG}..${CURRENT_TAG}") - - if [ -z "$CHANGELOG" ]; then - CHANGELOG="No changes" - fi - fi - - # Write changelog to output file (multi-line) - { - echo "changelog<> $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 0728338..6f3a252 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,11 @@ target # Added by cargo /target + +# 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 31523c4..38e200e 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,11 @@ # riscv-h -[![CI](https://github.com/arceos-hypervisor/riscv-h/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/arceos-hypervisor/riscv-h/actions/workflows/ci.yml) [![Crates.io](https://img.shields.io/crates/v/riscv-h.svg)](https://crates.io/crates/riscv-h) -[![Documentation](https://docs.rs/riscv-h/badge.svg)](https://docs.rs/riscv-h) +[![Docs.rs](https://docs.rs/riscv-h/badge.svg)](https://docs.rs/riscv-h) +[![Rust](https://img.shields.io/badge/edition-2024-orange.svg)](https://www.rust-lang.org/) +[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](https://github.com/arceos-hypervisor/riscv-h/blob/main/LICENSE) + +English | [中文](README_CN.md) RISC-V Hypervisor Extension Register Support diff --git a/README_CN.md b/README_CN.md new file mode 100644 index 0000000..4a488c2 --- /dev/null +++ b/README_CN.md @@ -0,0 +1,204 @@ +

riscv-h

+ +

RISC-V 虚拟化扩展寄存器支持库

+ +
+ +[![Crates.io](https://img.shields.io/crates/v/riscv-h.svg)](https://crates.io/crates/riscv-h) +[![Docs.rs](https://docs.rs/riscv-h/badge.svg)](https://docs.rs/riscv-h) +[![Rust](https://img.shields.io/badge/edition-2024-orange.svg)](https://www.rust-lang.org/) +[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](https://github.com/arceos-hypervisor/riscv-h/blob/main/LICENSE) + +
+ +[English](README.md) | 中文 + +# 简介 + +RISC-V 虚拟化扩展寄存器支持库,提供 RISC-V Hypervisor Extension 中定义的控制和状态寄存器(CSR)的低级访问接口。支持 `#![no_std]`,可用于裸机和操作系统内核开发。 + +本库导出以下核心模块: + +- **`register::hstatus`** — 虚拟化管理员状态寄存器 +- **`register::hgatp`** — 虚拟化客户地址翻译和保护寄存器 +- **`register::hvip`** — 虚拟化虚拟中断挂起寄存器 +- **`register::vsstatus`** — 虚拟管理员状态寄存器 +- **`register::vsatp`** — 虚拟管理员地址翻译和保护寄存器 + +所有寄存器类型均实现了 `Copy`、`Clone`、`Debug` trait,并提供类型安全的位字段访问方法。 + +## 快速上手 + +### 环境要求 + +- Rust nightly 工具链 +- Rust 组件: rust-src, clippy, rustfmt, llvm-tools +- 目标平台: riscv64gc-unknown-none-elf + +```bash +# 安装 rustup(如未安装) +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + +# 安装 nightly 工具链及组件 +rustup install nightly +rustup component add rust-src clippy rustfmt llvm-tools --toolchain nightly + +# 添加 RISC-V 目标 +rustup target add riscv64gc-unknown-none-elf --toolchain nightly +``` + +### 运行检查和测试 + +```bash +# 1. 克隆仓库 +git clone https://github.com/arceos-hypervisor/riscv-h.git +cd riscv-h + +# 2. 代码检查(格式检查 + clippy + 构建 + 文档生成) +./scripts/check.sh + +# 3. 运行测试 +# 运行全部测试(单元测试 + 集成测试) +./scripts/test.sh + +# 仅运行单元测试 +./scripts/test.sh unit + +# 仅运行集成测试 +./scripts/test.sh integration + +# 列出所有可用的测试套件 +./scripts/test.sh list + +# 指定单元测试目标 +./scripts/test.sh unit --unit-targets x86_64-unknown-linux-gnu +``` + +## 集成使用 + +### 安装 + +在 `Cargo.toml` 中添加: + +```toml +[dependencies] +riscv-h = "0.2.0" +``` + +### 使用示例 + +```rust +#![no_std] + +use riscv_h::register::{hstatus, hgatp, hvip}; + +fn main() { + // 读取虚拟化管理员状态寄存器 + let hstatus = hstatus::read(); + + // 检查是否处于虚拟化模式 + if hstatus.spv() { + // 访问各个字段 + let vsxl = hstatus.vsxl(); // 虚拟管理员 XLEN + let vtw = hstatus.vtw(); // 陷阱 WFI + let vtsr = hstatus.vtsr(); // 陷阱 SRET + let vgein = hstatus.vgein(); // 虚拟客户外部中断号 + + setup_guest_translation(); + } + + // 配置虚拟中断挂起 + let mut hvip_val = hvip::Hvip::from_bits(0); + hvip_val.set_vssip(true); // 设置虚拟管理员软件中断挂起 + hvip_val.set_vstip(true); // 设置虚拟管理员定时器中断挂起 + hvip_val.set_vseip(true); // 设置虚拟管理员外部中断挂起 + + unsafe { + hvip_val.write(); + } +} + +fn setup_guest_translation() { + unsafe { + // 配置客户地址翻译 + let mut hgatp = hgatp::Hgatp::from_bits(0); + hgatp.set_mode(hgatp::HgatpValues::Sv48x4); // 使用 Sv48x4 模式 + hgatp.set_vmid(1); // 设置 VMID + hgatp.set_ppn(0x1000); // 设置根页表 PPN + hgatp.write(); + } +} +``` + +### 异常和中断委托 + +```rust +use riscv_h::register::{hedeleg, hideleg}; + +unsafe { + // 将常见异常委托给 VS-mode + hedeleg::set_ex2(true); // 非法指令 + hedeleg::set_ex8(true); // U-mode 环境调用 + hedeleg::set_ex12(true); // 指令页面错误 + hedeleg::set_ex13(true); // 加载页面错误 + hedeleg::set_ex15(true); // 存储页面错误 + + // 将定时器和软件中断委托给 VS-mode + hideleg::set_vstie(true); // VS-mode 定时器中断 + hideleg::set_vssie(true); // VS-mode 软件中断 +} +``` + +## 支持的寄存器 + +### 虚拟化控制寄存器 + +| 寄存器 | 描述 | CSR 地址 | +|--------|------|----------| +| `hstatus` | 虚拟化管理员状态寄存器 | 0x600 | +| `hedeleg` | 虚拟化异常委托寄存器 | 0x602 | +| `hideleg` | 虚拟化中断委托寄存器 | 0x603 | +| `hie` | 虚拟化中断使能寄存器 | 0x604 | +| `hcounteren` | 虚拟化计数器使能寄存器 | 0x606 | +| `hgatp` | 虚拟化客户地址翻译和保护寄存器 | 0x680 | + +### 虚拟管理员寄存器 + +| 寄存器 | 描述 | CSR 地址 | +|--------|------|----------| +| `vsstatus` | 虚拟管理员状态寄存器 | 0x200 | +| `vsie` | 虚拟管理员中断使能寄存器 | 0x204 | +| `vstvec` | 虚拟管理员陷阱向量寄存器 | 0x205 | +| `vsscratch` | 虚拟管理员暂存寄存器 | 0x240 | +| `vsepc` | 虚拟管理员异常程序计数器 | 0x241 | +| `vscause` | 虚拟管理员原因寄存器 | 0x242 | +| `vstval` | 虚拟管理员陷阱值寄存器 | 0x243 | +| `vsatp` | 虚拟管理员地址翻译和保护寄存器 | 0x280 | + +### 其他寄存器 + +- **中断管理**: `hip`, `hvip`, `hgeie`, `hgeip` +- **时间管理**: `htimedelta`, `htimedeltah` +- **陷阱信息**: `htval`, `htinst` +- **虚拟管理员中断**: `vsip` + +### 文档 + +生成并查看 API 文档: + +```bash +cargo doc --no-deps --open +``` + +在线文档:[docs.rs/riscv-h](https://docs.rs/riscv-h) + +# 贡献 + +1. Fork 仓库并创建分支 +2. 运行本地检查:`./scripts/check.sh` +3. 运行本地测试:`./scripts/test.sh` +4. 提交 PR 并通过 CI 检查 + +# 协议 + +本项目采用 Apache License, Version 2.0 许可证。详见 [LICENSE](LICENSE) 文件。 diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 6289821..adb3ba8 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,5 +1,5 @@ [toolchain] -channel = "nightly-2025-05-20" +channel = "nightly-2026-03-18" components = ["rust-src", "llvm-tools", "rustfmt", "clippy"] profile = "minimal" targets = [ diff --git a/scripts/check.sh b/scripts/check.sh new file mode 100755 index 0000000..1613cbc --- /dev/null +++ b/scripts/check.sh @@ -0,0 +1,35 @@ +#!/bin/bash +# +# riscv-h 代码检查脚本 +# 下载并调用 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..a948928 --- /dev/null +++ b/scripts/test.sh @@ -0,0 +1,34 @@ +#!/bin/bash +# +# riscv-h 测试脚本 +# 下载并调用 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/tests/register_tests.rs b/tests/register_tests.rs new file mode 100644 index 0000000..0d91b62 --- /dev/null +++ b/tests/register_tests.rs @@ -0,0 +1,425 @@ +// 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. + +//! Unit tests for individual RISC-V hypervisor registers + +use riscv_h::register::{hcounteren, hie, hip, hvip, vsatp, vsie, vsip, vstvec}; + +// ============================================================================ +// Hypervisor Control Registers Tests +// ============================================================================ + +mod hvip_tests { + use super::*; + + #[test] + fn test_hvip_bit_fields() { + let mut hvip = hvip::Hvip::from_bits(0); + + // Test VSSIP (bit 2) + assert!(!hvip.vssip()); + hvip.set_vssip(true); + assert!(hvip.vssip()); + assert_eq!(hvip.bits(), 0b100); + + // Test VSTIP (bit 6) + hvip.set_vstip(true); + assert!(hvip.vstip()); + assert_eq!(hvip.bits(), 0b100_0100); + + // Test VSEIP (bit 10) + hvip.set_vseip(true); + assert!(hvip.vseip()); + // bit 2 + bit 6 + bit 10 = 4 + 64 + 1024 = 1092 + assert_eq!(hvip.bits(), (1 << 2) | (1 << 6) | (1 << 10)); + } + + #[test] + fn test_hvip_bit_isolation() { + let mut hvip = hvip::Hvip::from_bits(0); + + // Set all interrupt pending bits + hvip.set_vssip(true); + hvip.set_vstip(true); + hvip.set_vseip(true); + + // Clear one at a time and verify others unchanged + hvip.set_vssip(false); + assert!(!hvip.vssip()); + assert!(hvip.vstip()); + assert!(hvip.vseip()); + + hvip.set_vstip(false); + assert!(!hvip.vssip()); + assert!(!hvip.vstip()); + assert!(hvip.vseip()); + } +} + +mod hcounteren_tests { + use super::*; + + #[test] + fn test_hcounteren_basic_fields() { + let mut hcounteren = hcounteren::Hcounteren::from_bits(0); + + // Test CY (bit 0) + hcounteren.set_cy(true); + assert!(hcounteren.cy()); + + // Test TM (bit 1) + hcounteren.set_tm(true); + assert!(hcounteren.tm()); + + // Test IR (bit 2) + hcounteren.set_ir(true); + assert!(hcounteren.ir()); + + assert_eq!(hcounteren.bits(), 0b111); + } + + #[test] + fn test_hcounteren_hpm_fields() { + let mut hcounteren = hcounteren::Hcounteren::from_bits(0); + + // Test a few HPM counters + hcounteren.set_hpm3(true); + hcounteren.set_hpm10(true); + hcounteren.set_hpm31(true); + + assert!(hcounteren.hpm3()); + assert!(hcounteren.hpm10()); + assert!(hcounteren.hpm31()); + + // Verify bits are at correct positions + assert_eq!(hcounteren.bits() & (1 << 3), 1 << 3); // hpm3 + assert_eq!(hcounteren.bits() & (1 << 10), 1 << 10); // hpm10 + assert_eq!(hcounteren.bits() & (1 << 31), 1 << 31); // hpm31 + } +} + +mod hie_tests { + use super::*; + + #[test] + fn test_hie_bit_fields() { + let mut hie = hie::Hie::from_bits(0); + + // Test VSSIE (bit 2) + hie.set_vssie(true); + assert!(hie.vssie()); + + // Test VSTIE (bit 6) + hie.set_vstie(true); + assert!(hie.vstie()); + + // Test VSEIE (bit 10) + hie.set_vseie(true); + assert!(hie.vseie()); + + // Test SGEIE (bit 12) + hie.set_sgeie(true); + assert!(hie.sgeie()); + + assert_eq!(hie.bits(), (1 << 2) | (1 << 6) | (1 << 10) | (1 << 12)); + } + + #[test] + fn test_hie_bit_isolation() { + let mut hie = hie::Hie::from_bits(0); + + hie.set_vssie(true); + hie.set_vstie(true); + hie.set_vseie(true); + hie.set_sgeie(true); + + // Clear one at a time + hie.set_vssie(false); + assert!(!hie.vssie()); + assert!(hie.vstie()); + assert!(hie.vseie()); + assert!(hie.sgeie()); + } +} + +mod hip_tests { + use super::*; + + #[test] + fn test_hip_bit_fields() { + let mut hip = hip::Hip::from_bits(0); + + // Test VSSIP (bit 2) + hip.set_vssip(true); + assert!(hip.vssip()); + + // Test VSTIP (bit 6) + hip.set_vstip(true); + assert!(hip.vstip()); + + // Test VSEIP (bit 10) + hip.set_vseip(true); + assert!(hip.vseip()); + + // Test SGEIP (bit 12) + hip.set_sgeip(true); + assert!(hip.sgeip()); + + assert_eq!(hip.bits(), (1 << 2) | (1 << 6) | (1 << 10) | (1 << 12)); + } + + #[test] + fn test_hip_bit_isolation() { + let mut hip = hip::Hip::from_bits(0); + + hip.set_vssip(true); + hip.set_vstip(true); + hip.set_vseip(true); + hip.set_sgeip(true); + + // Clear one at a time + hip.set_sgeip(false); + assert!(hip.vssip()); + assert!(hip.vstip()); + assert!(hip.vseip()); + assert!(!hip.sgeip()); + } +} + +// ============================================================================ +// Virtual Supervisor Registers Tests +// ============================================================================ + +mod vsatp_tests { + use super::*; + + #[test] + fn test_vsatp_mode() { + let mut vsatp = vsatp::Vsatp::from_bits(0); + + vsatp.set_mode(vsatp::HgatpValues::Bare); + assert!(matches!(vsatp.mode(), vsatp::HgatpValues::Bare)); + + vsatp.set_mode(vsatp::HgatpValues::Sv39x4); + assert!(matches!(vsatp.mode(), vsatp::HgatpValues::Sv39x4)); + + vsatp.set_mode(vsatp::HgatpValues::Sv48x4); + assert!(matches!(vsatp.mode(), vsatp::HgatpValues::Sv48x4)); + } + + #[test] + fn test_vsatp_asid() { + let mut vsatp = vsatp::Vsatp::from_bits(0); + + // ASID is 16 bits (bits 44-59) + vsatp.set_asid(0); + assert_eq!(vsatp.asid(), 0); + + vsatp.set_asid(0xFFFF); + assert_eq!(vsatp.asid(), 0xFFFF); + + vsatp.set_asid(0x1234); + assert_eq!(vsatp.asid(), 0x1234); + } + + #[test] + fn test_vsatp_ppn() { + let mut vsatp = vsatp::Vsatp::from_bits(0); + + // PPN is 44 bits (bits 0-43) + vsatp.set_ppn(0); + assert_eq!(vsatp.ppn(), 0); + + vsatp.set_ppn(0xFFFFFFFFFFF); + assert_eq!(vsatp.ppn(), 0xFFFFFFFFFFF); + } + + #[test] + fn test_vsatp_field_isolation() { + let mut vsatp = vsatp::Vsatp::from_bits(0); + + vsatp.set_mode(vsatp::HgatpValues::Sv48x4); + vsatp.set_asid(0xABCD); + vsatp.set_ppn(0x123456789); + + // Verify all fields are independent + assert!(matches!(vsatp.mode(), vsatp::HgatpValues::Sv48x4)); + assert_eq!(vsatp.asid(), 0xABCD); + assert_eq!(vsatp.ppn(), 0x123456789); + + // Modify one field shouldn't affect others + vsatp.set_asid(0); + assert!(matches!(vsatp.mode(), vsatp::HgatpValues::Sv48x4)); + assert_eq!(vsatp.asid(), 0); + assert_eq!(vsatp.ppn(), 0x123456789); + } +} + +mod vstvec_tests { + use super::*; + + #[test] + fn test_vstvec_base() { + let mut vstvec = vstvec::Vstvec::from_bits(0); + + // Base is bits 2-63, must be 4-byte aligned + vstvec.set_base(0x1000); + assert_eq!(vstvec.base(), 0x1000); + + vstvec.set_base(0x80000000); + assert_eq!(vstvec.base(), 0x80000000); + } + + #[test] + fn test_vstvec_mode() { + let mut vstvec = vstvec::Vstvec::from_bits(0); + + // Mode is bits 0-1 + vstvec.set_mode(0); // Direct + assert_eq!(vstvec.mode(), 0); + + vstvec.set_mode(1); // Vectored + assert_eq!(vstvec.mode(), 1); + } + + #[test] + fn test_vstvec_field_isolation() { + let mut vstvec = vstvec::Vstvec::from_bits(0); + + vstvec.set_base(0x12345678); + vstvec.set_mode(1); + + // base() returns the raw bits 2-63 value + assert_eq!(vstvec.base(), 0x12345678); + assert_eq!(vstvec.mode(), 1); + } +} + +mod vsie_tests { + use super::*; + + #[test] + fn test_vsie_bit_fields() { + let mut vsie = vsie::Vsie::from_bits(0); + + // Test SSIE (bit 1) + vsie.set_ssie(true); + assert!(vsie.ssie()); + + // Test STIE (bit 5) + vsie.set_stie(true); + assert!(vsie.stie()); + + // Test SEIE (bit 9) + vsie.set_seie(true); + assert!(vsie.seie()); + + assert_eq!(vsie.bits(), (1 << 1) | (1 << 5) | (1 << 9)); + } + + #[test] + fn test_vsie_bit_isolation() { + let mut vsie = vsie::Vsie::from_bits(0); + + vsie.set_ssie(true); + vsie.set_stie(true); + vsie.set_seie(true); + + vsie.set_ssie(false); + assert!(!vsie.ssie()); + assert!(vsie.stie()); + assert!(vsie.seie()); + } +} + +mod vsip_tests { + use super::*; + + #[test] + fn test_vsip_bit_fields() { + let mut vsip = vsip::Vsip::from_bits(0); + + // Test SSIP (bit 1) + vsip.set_ssip(true); + assert!(vsip.ssip()); + + // Test STIP (bit 5) + vsip.set_stip(true); + assert!(vsip.stip()); + + // Test SEIP (bit 9) + vsip.set_seip(true); + assert!(vsip.seip()); + + assert_eq!(vsip.bits(), (1 << 1) | (1 << 5) | (1 << 9)); + } + + #[test] + fn test_vsip_bit_isolation() { + let mut vsip = vsip::Vsip::from_bits(0); + + vsip.set_ssip(true); + vsip.set_stip(true); + vsip.set_seip(true); + + vsip.set_ssip(false); + assert!(!vsip.ssip()); + assert!(vsip.stip()); + assert!(vsip.seip()); + } +} + +// ============================================================================ +// Copy/Clone Trait Tests +// ============================================================================ + +mod trait_tests { + use super::*; + + #[test] + fn test_all_registers_copy_clone() { + // Test that all register types implement Copy and Clone + let hvip = hvip::Hvip::from_bits(0x123); + let _copy = hvip; + let _clone = hvip.clone(); + + let hcounteren = hcounteren::Hcounteren::from_bits(0xFF); + let _copy = hcounteren; + let _clone = hcounteren.clone(); + + let vsatp = vsatp::Vsatp::from_bits(0xABC); + let _copy = vsatp; + let _clone = vsatp.clone(); + + let vstvec = vstvec::Vstvec::from_bits(0x1000); + let _copy = vstvec; + let _clone = vstvec.clone(); + } + + #[test] + fn test_all_registers_debug() { + // Test that all register types implement Debug + let hvip = hvip::Hvip::from_bits(0x123); + assert!(format!("{:?}", hvip).contains("Hvip")); + + let hcounteren = hcounteren::Hcounteren::from_bits(0xFF); + assert!(format!("{:?}", hcounteren).contains("Hcounteren")); + + let vsatp = vsatp::Vsatp::from_bits(0xABC); + assert!(format!("{:?}", vsatp).contains("Vsatp")); + + let vstvec = vstvec::Vstvec::from_bits(0x1000); + assert!(format!("{:?}", vstvec).contains("Vstvec")); + } +}