Browse Source

Merge pull request #1 from woshiluo/merge

refactor: merge prototyper into main
guttatus 1 month ago
parent
commit
90e674704b
71 changed files with 7320 additions and 0 deletions
  1. 5 0
      .cargo/config.toml
  2. 50 0
      .github/workflows/prototyper.yml
  3. 6 0
      Cargo.toml
  4. 87 0
      prototyper/.pre-commit-config.yaml
  5. 6 0
      prototyper/CHANGELOG.md
  6. 56 0
      prototyper/README.md
  7. 7 0
      prototyper/_typos.toml
  8. 28 0
      prototyper/bench-kernel/Cargo.toml
  9. 52 0
      prototyper/bench-kernel/build.rs
  10. 43 0
      prototyper/bench-kernel/scripts/rustsbi-bench-kernel.its
  11. 339 0
      prototyper/bench-kernel/src/main.rs
  12. 68 0
      prototyper/cliff.toml
  13. 33 0
      prototyper/docs/booting-archlinux-in-qemu-using-uboot-and-rustsbi.md
  14. 129 0
      prototyper/docs/booting-fedora-in-qemu-using-uboot-and-rustsbi.md
  15. 147 0
      prototyper/docs/booting-freebsd-in-qemu-using-uboot-and-rustsbi.md
  16. 499 0
      prototyper/docs/booting-linux-kernel-in-qemu-using-uboot-and-opensbi.md
  17. 343 0
      prototyper/docs/booting-linux-kernel-in-qemu-using-uboot-and-rustsbi.md
  18. 107 0
      prototyper/docs/booting-openEuler-23.09-in-qemu-using-uboot-and-rustsbi.md
  19. 149 0
      prototyper/docs/booting-openwrt-in-qemu-using-uboot-and-rustsbi.md
  20. 156 0
      prototyper/docs/booting-polyos-in-qemu-using-uboot-and-rustsbi.md
  21. 319 0
      prototyper/docs/booting-test-kernel-in-qemu-using-uboot-and-rustsbi.md
  22. 101 0
      prototyper/docs/booting-ubuntu-24.04.1-in-qemu-using-edk2-and-opensbi.md
  23. 107 0
      prototyper/docs/booting-ubuntu-24.04.1-in-qemu-using-uboot-and-rustsbi.md
  24. 27 0
      prototyper/docs/openwrt-patch.patch
  25. 40 0
      prototyper/prototyper/Cargo.toml
  26. 82 0
      prototyper/prototyper/build.rs
  27. 16 0
      prototyper/prototyper/src/cfg.rs
  28. 92 0
      prototyper/prototyper/src/devicetree.rs
  29. 125 0
      prototyper/prototyper/src/fail.rs
  30. 147 0
      prototyper/prototyper/src/firmware/dynamic.rs
  31. 20 0
      prototyper/prototyper/src/firmware/jump.rs
  32. 133 0
      prototyper/prototyper/src/firmware/mod.rs
  33. 31 0
      prototyper/prototyper/src/firmware/payload.rs
  34. 49 0
      prototyper/prototyper/src/macros.rs
  35. 210 0
      prototyper/prototyper/src/main.rs
  36. 122 0
      prototyper/prototyper/src/platform/clint.rs
  37. 98 0
      prototyper/prototyper/src/platform/console.rs
  38. 434 0
      prototyper/prototyper/src/platform/mod.rs
  39. 34 0
      prototyper/prototyper/src/platform/reset.rs
  40. 63 0
      prototyper/prototyper/src/riscv/csr.rs
  41. 7 0
      prototyper/prototyper/src/riscv/mod.rs
  42. 126 0
      prototyper/prototyper/src/sbi/console.rs
  43. 21 0
      prototyper/prototyper/src/sbi/early_trap.rs
  44. 129 0
      prototyper/prototyper/src/sbi/extensions.rs
  45. 70 0
      prototyper/prototyper/src/sbi/fifo.rs
  46. 47 0
      prototyper/prototyper/src/sbi/hart_context.rs
  47. 21 0
      prototyper/prototyper/src/sbi/heap.rs
  48. 254 0
      prototyper/prototyper/src/sbi/hsm.rs
  49. 292 0
      prototyper/prototyper/src/sbi/ipi.rs
  50. 55 0
      prototyper/prototyper/src/sbi/logger.rs
  51. 50 0
      prototyper/prototyper/src/sbi/mod.rs
  52. 60 0
      prototyper/prototyper/src/sbi/reset.rs
  53. 323 0
      prototyper/prototyper/src/sbi/rfence.rs
  54. 86 0
      prototyper/prototyper/src/sbi/trap/boot.rs
  55. 182 0
      prototyper/prototyper/src/sbi/trap/handler.rs
  56. 75 0
      prototyper/prototyper/src/sbi/trap/mod.rs
  57. 88 0
      prototyper/prototyper/src/sbi/trap_stack.rs
  58. 26 0
      prototyper/test-kernel/Cargo.toml
  59. 52 0
      prototyper/test-kernel/build.rs
  60. 44 0
      prototyper/test-kernel/scripts/rustsbi-test-kernel.its
  61. 213 0
      prototyper/test-kernel/src/main.rs
  62. 5 0
      rust-toolchain.toml
  63. 11 0
      xtask/Cargo.toml
  64. 80 0
      xtask/src/bench.rs
  65. 55 0
      xtask/src/logger.rs
  66. 56 0
      xtask/src/main.rs
  67. 120 0
      xtask/src/prototyper.rs
  68. 80 0
      xtask/src/test.rs
  69. 105 0
      xtask/src/utils/cargo.rs
  70. 14 0
      xtask/src/utils/envs.rs
  71. 13 0
      xtask/src/utils/mod.rs

+ 5 - 0
.cargo/config.toml

@@ -0,0 +1,5 @@
+[alias]
+xtask = "run --package xtask --release --"
+prototyper = "xtask prototyper"
+test-kernel = "xtask test"
+bench-kernel = "xtask bench"

+ 50 - 0
.github/workflows/prototyper.yml

@@ -0,0 +1,50 @@
+# This workflow uses actions that are not certified by GitHub.
+# They are provided by a third-party and are governed by
+# separate terms of service, privacy policy, and support
+# documentation.
+# rust-clippy is a tool that runs a bunch of lints to catch common
+# mistakes in your Rust code and help improve your Rust code.
+# More details at https://github.com/rust-lang/rust-clippy
+# and https://rust-lang.github.io/rust-clippy/
+
+name: CI
+
+on:
+  pull_request:
+  push:
+    paths-ignore:
+      - '**.md'
+      - 'LICENSE'
+
+jobs:
+  rust-clippy-analyze:
+    name: Run rust-clippy analyzing
+    runs-on: ubuntu-latest
+    permissions:
+      security-events: write
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v4
+
+      - name: Check format
+        run: cargo fmt --check
+
+      - name: Run test
+        run: |
+            cargo test -p rustsbi-prototyper
+
+      - name: Install required cargo
+        run: cargo install clippy-sarif sarif-fmt
+
+      - name: Run rust-clippy
+        run: |
+          cargo clippy -p rustsbi-prototyper --target riscv64imac-unknown-none-elf  --message-format=json  | clippy-sarif | tee rust-clippy-results.sarif | sarif-fmt
+          cargo clippy -p rustsbi-test-kernel --target riscv64imac-unknown-none-elf --message-format=json  | clippy-sarif | tee rust-clippy-results.sarif | sarif-fmt
+          cargo clippy -p rustsbi-bench-kernel --target riscv64imac-unknown-none-elf --message-format=json | clippy-sarif | tee rust-clippy-results.sarif | sarif-fmt
+        continue-on-error: true
+
+      - name: Upload analysis results to GitHub
+        uses: github/codeql-action/upload-sarif@v3
+        with:
+          sarif_file: rust-clippy-results.sarif
+          wait-for-processing: true

+ 6 - 0
Cargo.toml

@@ -6,6 +6,10 @@ members = [
     "library/sbi-spec",
     "library/sbi-testing",
     "library/rustsbi",
+    "prototyper/prototyper",
+    "prototyper/bench-kernel",
+    "prototyper/test-kernel",
+    "xtask",
 ]
 
 [workspace.package]
@@ -13,4 +17,6 @@ edition = "2024"
 license = "MulanPSL-2.0 OR MIT"
 repository = "https://github.com/rustsbi/rustsbi"
 
+[profile.release]
+debug = true
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

+ 87 - 0
prototyper/.pre-commit-config.yaml

@@ -0,0 +1,87 @@
+fail_fast: false
+repos:
+  - repo: https://github.com/pre-commit/pre-commit-hooks
+    rev: v4.3.0
+    hooks:
+      - id: check-byte-order-marker
+      - id: check-case-conflict
+      - id: check-merge-conflict
+      - id: check-symlinks
+      - id: check-yaml
+      - id: end-of-file-fixer
+      - id: mixed-line-ending
+      - id: trailing-whitespace
+  - repo: https://github.com/psf/black
+    rev: 22.10.0
+    hooks:
+      - id: black
+  - repo: local
+    hooks:
+      - id: cargo-fmt
+        name: cargo fmt
+        description: Format files with rustfmt.
+        entry: bash -c 'cargo fmt -- --check'
+        language: rust
+        files: \.rs$
+        args: []
+      - id: typos
+        name: typos
+        description: check typo
+        entry: bash -c 'typos'
+        language: rust
+        files: \.*$
+        pass_filenames: false
+      - id: cargo-check
+        name: cargo check
+        description: Check the package for errors.
+        entry: |
+          bash -c '
+            # Get all packages in the workspace
+            packages=$(cargo metadata --format-version 1 | jq -r ".packages[] | select(.name != \"xtask\") | .name")
+
+            # Check each package
+            for package in $packages; do
+              echo "Checking package: $package"
+              cargo check -p "$package" --target riscv64imac-unknown-none-elf
+              check_status=$?
+
+              # If the check fails, exit with the error code
+              if [ "$check_status" -ne 0 ]; then
+                echo "Package $package check failed, exit status: $check_status!"
+                exit $check_status
+              fi
+            done
+
+            echo "All packages checked successfully."
+            exit 0
+          '
+        language: rust
+        files: \.rs$
+        pass_filenames: false
+      - id: cargo-clippy
+        name: cargo clippy
+        description: Lint Rust sources.
+        entry: |
+          bash -c '
+            # Get all packages in the workspace
+            packages=$(cargo metadata --format-version 1 | jq -r ".packages[] | select(.name != \"xtask\") | .name")
+
+            # Lint each package
+            for package in $packages; do
+              echo "Linting package: $package"
+              cargo clippy -p "$package" --target riscv64imac-unknown-none-elf -- -D warnings
+              clippy_status=$?
+
+              # If the linting fails, exit with the error code
+              if [ "$clippy_status" -ne 0 ]; then
+                echo "Package $package clippy check failed, exit status: $clippy_status!"
+                exit $clippy_status
+              fi
+            done
+
+            echo "All packages linted successfully."
+            exit 0
+          '
+        language: rust
+        files: \.rs$
+        pass_filenames: false

+ 6 - 0
prototyper/CHANGELOG.md

@@ -0,0 +1,6 @@
+# Changelog
+
+All notable changes to this project will be documented in this file. See [conventional commits](https://www.conventionalcommits.org/) for commit guidelines.
+
+---
+## [unreleased]

+ 56 - 0
prototyper/README.md

@@ -0,0 +1,56 @@
+# RustSBI Prototyper
+
+RustSBI Prototyper is a developing RISC-V Secure Bootloader solution. It can be integrated with the Rust or C language ecosystem to form a complete RISC-V bootloader ecosystem.
+
+## Setting Up the Development Environment
+
+### Packages to be installed
+
+```bash
+cargo install cargo-binutils
+sudo apt install u-boot-tools
+```
+
+
+### Optional Tools
+
+The following tools are not mandatory but can be useful for enhancing your development experience.
+
+#### Install pre-commit
+
+pre-commit is a tool that runs code checks before you commit your code.
+
+```bash
+pipx install pre-commit
+
+# After installation, run pre-commit install to set it up for your project.
+pre-commit install
+```
+
+#### Install Cargo Deny
+
+Cargo deny is a Cargo plugin used to check the security of your dependencies.
+
+```bash
+cargo install --locked cargo-deny
+```
+
+#### Install typos
+
+typos is a spell-checking tool.
+
+```bash
+cargo install typos-cli
+```
+
+#### Install git cliff
+
+git cliff is a tool for generating changelogs.
+
+```bash
+cargo install git-cliff
+```
+
+## License
+
+This project is dual-licensed under MIT or Mulan-PSL v2. See [LICENSE-MIT](./LICENSE-MIT) and [LICENSE-MULAN](./LICENSE-MULAN) for details.

+ 7 - 0
prototyper/_typos.toml

@@ -0,0 +1,7 @@
+[default.extend-words]
+rela = "rela"
+sie = "sie"
+stip = "stip"
+
+[files]
+extend-exclude = ["CHANGELOG.md"]

+ 28 - 0
prototyper/bench-kernel/Cargo.toml

@@ -0,0 +1,28 @@
+cargo-features = ["per-package-target"]
+
+[package]
+name = "rustsbi-bench-kernel"
+version = "0.0.0"
+edition.workspace = true
+license.workspace = true
+repository.workspace = true
+forced-target = "riscv64imac-unknown-none-elf"
+publish = false
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+sbi-testing = { git = "https://github.com/rustsbi/rustsbi", rev = "4821073", features = ["log"] }
+sbi-spec = { git = "https://github.com/rustsbi/rustsbi", rev = "4821073" }
+serde-device-tree = { git = "https://github.com/rustsbi/serde-device-tree", default-features = false }
+serde = { version = "1.0.202", default-features = false, features = ["derive"] }
+log = "0.4"
+riscv = "0.11.1"
+spin = "0.9"
+uart16550 = "0.0.1"
+rcore-console = "0.0.0"
+
+[[bin]]
+name = "rustsbi-bench-kernel"
+test = false
+bench = false

+ 52 - 0
prototyper/bench-kernel/build.rs

@@ -0,0 +1,52 @@
+use std::{env, path::PathBuf};
+
+fn main() {
+    let out = PathBuf::from(env::var_os("OUT_DIR").unwrap());
+    let ld = &out.join("rustsbi-test-kernel.ld");
+
+    std::fs::write(ld, LINKER_SCRIPT).unwrap();
+
+    println!("cargo:rustc-link-arg=-T{}", ld.display());
+    println!("cargo:rustc-link-search={}", out.display());
+}
+
+const LINKER_SCRIPT: &[u8] = b"OUTPUT_ARCH(riscv)
+ENTRY(_start) 
+SECTIONS {
+    . = 0x80200000;
+    istart = .;
+	  .head.text : ALIGN(8) {		
+        KEEP(*(.head.text))
+	  }
+
+    .text : ALIGN(8) { 
+        *(.text.entry)
+        *(.text .text.*)
+    }
+    .rodata : ALIGN(8) { 
+        srodata = .;
+        *(.rodata .rodata.*)
+        *(.srodata .srodata.*)
+        . = ALIGN(8);  
+        erodata = .;
+    } 
+    .data : ALIGN(8) { 
+        sdata = .;
+        *(.data .data.*)
+        *(.sdata .sdata.*)
+        . = ALIGN(8); 
+        edata = .;
+    }
+    sidata = LOADADDR(.data);
+    .bss (NOLOAD) : ALIGN(8) {  
+        *(.bss.uninit)
+        sbss = .;
+        *(.bss .bss.*)
+        *(.sbss .sbss.*)
+        ebss = .;
+    } 
+    iend = .;
+    /DISCARD/ : {
+        *(.eh_frame)
+    }
+}";

+ 43 - 0
prototyper/bench-kernel/scripts/rustsbi-bench-kernel.its

@@ -0,0 +1,43 @@
+/*
+ * Configuration to load RustSBI before RustSBI Bench Kernel
+ */
+
+/dts-v1/;
+
+/ {
+			description = "Configuration to load RustSBI before RustSBI Bench Kernel";
+
+			images {
+				kernel {
+					description = "rustsbi-bench-kernel";
+					data = /incbin/("./rustsbi-bench-kernel.bin");
+					type = "standalone";
+					os = "u-boot";
+					arch = "riscv";
+					compression = "none";
+					load = /bits/ 64 <0x80200000>;
+				};
+
+				rustsbi {
+					description = "RustSBI Firmware";
+					data = /incbin/("./rustsbi-prototyper.bin");
+					type = "firmware";
+					os = "opensbi";
+					arch = "riscv";
+					compression = "none";
+					load = /bits/ 64 <0x80100000>;
+					entry = /bits/ 64 <0x80100000>;
+				};
+
+			};
+
+		configurations {
+				default = "conf-1";
+
+				conf-1 {
+					description = "RustSBI & RustSBI Bench Kernel";
+					firmware = "rustsbi";
+					loadables = "kernel";
+				};
+		};
+};

+ 339 - 0
prototyper/bench-kernel/src/main.rs

@@ -0,0 +1,339 @@
+#![no_std]
+#![no_main]
+#![feature(naked_functions)]
+#![allow(static_mut_refs)]
+
+#[macro_use]
+extern crate rcore_console;
+
+use core::mem::MaybeUninit;
+use core::sync::{atomic::AtomicBool, atomic::AtomicU64, atomic::Ordering};
+use core::{
+    arch::{asm, naked_asm},
+    ptr::null,
+};
+use log::*;
+use sbi::SbiRet;
+use sbi_spec::binary::{HartMask, MaskError};
+use sbi_spec::hsm::hart_state;
+use sbi_testing::sbi;
+use serde::Deserialize;
+use serde_device_tree::{
+    Dtb, DtbPtr,
+    buildin::{Node, NodeSeq, Reg, StrSeq},
+};
+use uart16550::Uart16550;
+
+const RISCV_HEAD_FLAGS: u64 = 0;
+const RISCV_HEADER_VERSION: u32 = 0x2;
+const RISCV_IMAGE_MAGIC: u64 = 0x5643534952; /* Magic number, little endian, "RISCV" */
+const RISCV_IMAGE_MAGIC2: u32 = 0x05435352; /* Magic number 2, little endian, "RSC\x05" */
+
+/// boot header
+#[naked]
+#[unsafe(no_mangle)]
+#[unsafe(link_section = ".head.text")]
+unsafe extern "C" fn _boot_header() -> ! {
+    unsafe {
+        naked_asm!(
+            "j _start",
+            ".word 0",
+            ".balign 8",
+            ".dword 0x200000",
+            ".dword iend - istart",
+            ".dword {RISCV_HEAD_FLAGS}",
+            ".word  {RISCV_HEADER_VERSION}",
+            ".word  0",
+            ".dword 0",
+            ".dword {RISCV_IMAGE_MAGIC}",
+            ".balign 4",
+            ".word  {RISCV_IMAGE_MAGIC2}",
+            ".word  0",
+            RISCV_HEAD_FLAGS = const RISCV_HEAD_FLAGS,
+            RISCV_HEADER_VERSION = const RISCV_HEADER_VERSION,
+            RISCV_IMAGE_MAGIC = const RISCV_IMAGE_MAGIC,
+            RISCV_IMAGE_MAGIC2 = const RISCV_IMAGE_MAGIC2,
+        );
+    }
+}
+
+const STACK_SIZE: usize = 512 * 1024; // 512 KiB
+const MAX_HART_NUM: usize = 128;
+
+#[allow(dead_code)]
+#[derive(Copy, Clone)]
+struct HartStack([u8; STACK_SIZE]);
+
+impl HartStack {
+    #[inline]
+    pub const fn new() -> Self {
+        HartStack([0; STACK_SIZE])
+    }
+}
+
+#[unsafe(link_section = ".bss.uninit")]
+static mut STACK: HartStack = HartStack::new();
+#[unsafe(link_section = ".bss.uninit")]
+static mut HART_STACK: [HartStack; MAX_HART_NUM] = [HartStack::new(); MAX_HART_NUM];
+#[unsafe(link_section = ".bss.uninit")]
+static mut IPI_SENT: [MaybeUninit<AtomicBool>; MAX_HART_NUM] =
+    [const { MaybeUninit::uninit() }; MAX_HART_NUM];
+#[unsafe(link_section = ".bss.uninit")]
+static mut SMP_COUNT: usize = 0;
+#[unsafe(link_section = ".bss.uninit")]
+static mut BOOT_HART_ID: usize = 0;
+
+/// 内核入口。
+///
+/// # Safety
+///
+/// 裸函数。
+#[naked]
+#[unsafe(no_mangle)]
+#[unsafe(link_section = ".text.entry")]
+unsafe extern "C" fn _start(hartid: usize, device_tree_paddr: usize) -> ! {
+    unsafe {
+        naked_asm!(
+            // clear bss segment
+            "   la      t0, sbss
+            la      t1, ebss
+        1:  bgeu    t0, t1, 2f
+            sd      zero, 0(t0)
+            addi    t0, t0, 8
+            j       1b",
+            "2:",
+            "   la sp, {stack} + {stack_size}",
+            "   j  {main}",
+            stack_size = const STACK_SIZE,
+            stack      =   sym STACK,
+            main       =   sym rust_main,
+        )
+    }
+}
+
+#[naked]
+#[unsafe(no_mangle)]
+extern "C" fn init_hart(hartid: usize, opaque: usize) {
+    unsafe {
+        naked_asm!(
+            "add sp, a1, zero",
+            "csrw sscratch, sp",
+            "call {init_main}",
+            init_main = sym init_main,
+        )
+    }
+}
+
+#[naked]
+#[unsafe(no_mangle)]
+extern "C" fn core_send_ipi(hartid: usize, opaque: usize) {
+    unsafe {
+        naked_asm!(
+            "add sp, a1, zero",
+            "csrw sscratch, sp",
+            "call {send_ipi}",
+            send_ipi = sym send_ipi,
+        )
+    }
+}
+
+extern "C" fn send_ipi(hartid: usize) -> ! {
+    if unsafe { !(IPI_SENT[hartid].assume_init_mut().load(Ordering::Relaxed)) } {
+        unsafe {
+            IPI_SENT[hartid]
+                .assume_init_mut()
+                .swap(true, Ordering::AcqRel);
+        };
+        let mut mask = Some(HartMask::from_mask_base(0, 0));
+        for i in 0..unsafe { SMP_COUNT } {
+            if i == unsafe { BOOT_HART_ID } {
+                continue;
+            }
+            if let Some(ref mut mask) = mask {
+                match mask.insert(i) {
+                    Ok(_) => continue,
+                    Err(MaskError::InvalidBit) => {
+                        sbi::remote_sfence_vma(*mask, 0, 0);
+                    }
+                    Err(_) => unreachable!("Failed to construct mask"),
+                }
+            }
+            mask = Some(HartMask::from_mask_base(0b1, i));
+        }
+        if let Some(mask) = mask {
+            sbi::remote_sfence_vma(mask, 0, 0);
+        }
+        unsafe {
+            WAIT_COUNT.fetch_sub(1, Ordering::AcqRel);
+            while WAIT_COUNT.load(Ordering::Relaxed) != 0 {}
+        }
+    } else {
+        unreachable!("resend {}", hartid);
+    }
+    sbi::hart_suspend(sbi::NonRetentive, core_send_ipi as _, unsafe {
+        core::ptr::addr_of!(HART_STACK[hartid + 1]) as _
+    });
+    unreachable!()
+}
+
+extern "C" fn init_main(hartid: usize) -> ! {
+    sbi::hart_suspend(sbi::NonRetentive, core_send_ipi as _, unsafe {
+        core::ptr::addr_of!(HART_STACK[hartid + 1]) as _
+    });
+    unreachable!()
+}
+
+static mut WAIT_COUNT: AtomicU64 = AtomicU64::new(0);
+
+const SUSPENDED: SbiRet = SbiRet::success(hart_state::SUSPENDED);
+
+fn get_time() -> u64 {
+    const CSR_TIME: u32 = 0xc01;
+    let mut low_time: u64;
+    unsafe {
+        asm!("csrr {}, {CSR_TIME}", out(reg) low_time, CSR_TIME = const CSR_TIME);
+    }
+
+    low_time
+}
+
+extern "C" fn rust_main(hartid: usize, dtb_pa: usize) -> ! {
+    #[derive(Deserialize)]
+    struct Tree<'a> {
+        cpus: Cpus<'a>,
+        chosen: Chosen<'a>,
+    }
+    #[derive(Deserialize)]
+    #[serde(rename_all = "kebab-case")]
+    struct Cpus<'a> {
+        timebase_frequency: u32,
+        cpu: NodeSeq<'a>,
+    }
+    #[derive(Deserialize)]
+    #[serde(rename_all = "kebab-case")]
+    struct Chosen<'a> {
+        stdout_path: StrSeq<'a>,
+    }
+    rcore_console::init_console(&Console);
+    rcore_console::set_log_level(option_env!("LOG"));
+    let dtb_ptr = DtbPtr::from_raw(dtb_pa as _).unwrap();
+    let dtb = Dtb::from(dtb_ptr).share();
+    let root: Node = serde_device_tree::from_raw_mut(&dtb).unwrap();
+    let tree: Tree = root.deserialize();
+    let stdout_path = tree.chosen.stdout_path.iter().next().unwrap();
+    if let Some(node) = root.find(stdout_path) {
+        let reg = node.get_prop("reg").unwrap().deserialize::<Reg>();
+        let address = reg.iter().next().unwrap().0.start;
+        unsafe { UART = Uart16550Map(address as _) };
+    }
+    let smp = tree.cpus.cpu.len();
+    let frequency = tree.cpus.timebase_frequency;
+    info!(
+        r"
+ ____                  _       _  __                    _
+| __ )  ___ _ __   ___| |__   | |/ /___ _ __ _ __   ___| |
+|  _ \ / _ \ '_ \ / __| '_ \  | ' // _ \ '__| '_ \ / _ \ |
+| |_) |  __/ | | | (__| | | | | . \  __/ |  | | | |  __/ |
+|____/ \___|_| |_|\___|_| |_| |_|\_\___|_|  |_| |_|\___|_|
+==========================================================
+| boot hart id          | {hartid:20} |
+| smp                   | {smp:20} |
+| timebase frequency    | {frequency:17} Hz |
+| dtb physical address  | {dtb_pa:#20x} |
+----------------------------------------------------------"
+    );
+    unsafe {
+        SMP_COUNT = smp;
+        BOOT_HART_ID = hartid;
+    }
+    for i in 0..smp {
+        unsafe {
+            IPI_SENT[i].write(AtomicBool::new(false));
+        }
+        if i != hartid {
+            sbi::hart_start(i, init_hart as _, unsafe {
+                core::ptr::addr_of!(HART_STACK[i + 1]) as _
+            });
+            while sbi::hart_get_status(i) != SUSPENDED {
+                core::hint::spin_loop();
+            }
+        }
+    }
+    info!("Starting test");
+    for i in 0..4 {
+        info!("Test #{i} started");
+        unsafe {
+            for (i, ipi_sent) in IPI_SENT.iter_mut().enumerate().take(smp) {
+                ipi_sent.assume_init_mut().swap(false, Ordering::AcqRel);
+                if i != hartid {
+                    while sbi::hart_get_status(i) != SUSPENDED {}
+                }
+            }
+            WAIT_COUNT.swap((smp - 1) as u64, Ordering::AcqRel);
+        }
+        debug!("send ipi!");
+        let start_time = get_time();
+        let mut mask = Some(HartMask::from_mask_base(0, 0));
+        for i in 0..smp {
+            if i == hartid {
+                continue;
+            }
+            if let Some(ref mut mask) = mask {
+                match mask.insert(i) {
+                    Ok(_) => continue,
+                    Err(MaskError::InvalidBit) => {
+                        sbi::send_ipi(*mask);
+                    }
+                    Err(_) => unreachable!("Failed to construct mask"),
+                }
+            }
+            mask = Some(HartMask::from_mask_base(0b1, i));
+        }
+        if let Some(mask) = mask {
+            sbi::send_ipi(mask);
+        }
+        while unsafe { WAIT_COUNT.load(Ordering::Acquire) } != 0 {}
+        let end_time = get_time();
+        println!("Test #{}: {}", i, end_time - start_time);
+    }
+    sbi::system_reset(sbi::Shutdown, sbi::NoReason);
+    unreachable!()
+}
+
+#[cfg_attr(not(test), panic_handler)]
+fn panic(info: &core::panic::PanicInfo) -> ! {
+    let (hart_id, pc): (usize, usize);
+    unsafe { asm!("mv    {}, tp", out(reg) hart_id) };
+    unsafe { asm!("auipc {},  0", out(reg) pc) };
+    info!("[test-kernel-panic] hart {hart_id} {info}");
+    info!("[test-kernel-panic] pc = {pc:#x}");
+    info!("[test-kernel-panic] SBI test FAILED due to panic");
+    sbi::system_reset(sbi::Shutdown, sbi::SystemFailure);
+    loop {}
+}
+
+struct Console;
+static mut UART: Uart16550Map = Uart16550Map(null());
+
+pub struct Uart16550Map(*const Uart16550<u8>);
+
+unsafe impl Sync for Uart16550Map {}
+
+impl Uart16550Map {
+    #[inline]
+    pub fn get(&self) -> &Uart16550<u8> {
+        unsafe { &*self.0 }
+    }
+}
+
+impl rcore_console::Console for Console {
+    #[inline]
+    fn put_char(&self, c: u8) {
+        unsafe { UART.get().write(core::slice::from_ref(&c)) };
+    }
+
+    #[inline]
+    fn put_str(&self, s: &str) {
+        unsafe { UART.get().write(s.as_bytes()) };
+    }
+}

+ 68 - 0
prototyper/cliff.toml

@@ -0,0 +1,68 @@
+# git-cliff ~ configuration file
+# https://git-cliff.org/docs/configuration
+
+[changelog]
+header = """
+# Changelog\n
+All notable changes to this project will be documented in this file. See [conventional commits](https://www.conventionalcommits.org/) for commit guidelines.\n
+"""
+
+body = """
+---
+{% if version %}
+    {% if previous.version %}
+        ## [{{ version | trim_start_matches(pat="v") }}]($REPO/compare/{{ previous.version }}..{{ version }}) - {{ timestamp | date(format="%Y-%m-%d") }}
+    {% else %}
+        ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
+    {% endif %}
+{% else %}
+    ## [unreleased]
+{% endif %}
+{% for group, commits in commits | group_by(attribute="group") %}
+    ### {{ group | striptags | trim | upper_first }}
+    {% for commit in commits | filter(attribute="scope") | sort(attribute="scope") %}
+        - **({{commit.scope}})**{% if commit.breaking %} [**breaking**]{% endif %} {{ commit.message|trim }} - ([{{ commit.id | truncate(length=7, end="") }}]($REPO/commit/{{ commit.id }})) - {{ commit.author.name }}
+    {%- endfor -%}
+    {% raw %}\n{% endraw %}
+    {%- for commit in commits %}
+        {%- if not commit.scope -%}
+            - {% if commit.breaking %} [**breaking**]{% endif %}{{ commit.message|trim }} - ([{{ commit.id | truncate(length=7, end="") }}]($REPO/commit/{{ commit.id }})) - {{ commit.author.name }}
+        {%- endif -%}
+    {% endfor -%}
+{% endfor %}\n
+"""
+
+footer = "<!-- generated by git-cliff -->"
+trim = true
+postprocessors = [
+    { pattern = '\$REPO', replace = "https://github.com/rustsbi/prototyper" },
+]
+
+[git]
+conventional_commits = true
+filter_unconventional = false
+split_commits = false
+commit_preprocessors = []
+commit_parsers = [
+    { message = "\\[skip", skip = true },
+    { message = "\\p{Han}", skip = true },
+    { message = "^feat", group = "Features" },
+    { message = "^fix", group = "Bug Fixes" },
+    { message = "^doc", group = "Documentation" },
+    { message = "^perf", group = "Performance" },
+    { message = "^refactor", group = "Refactoring" },
+    { message = "^style", group = "Style" },
+    { message = "^revert", group = "Revert" },
+    { message = "^test", group = "Tests" },
+    { message = "^chore\\(version\\):", skip = true },
+    { message = "^chore", group = "Miscellaneous Chores" },
+    { message = ".*", group = "Other" },
+    { body = ".*security", group = "Security" },
+]
+protect_breaking_commits = false
+filter_commits = false
+tag_pattern = "v[0-9].*"
+skip_tags = "v0.1.0-beta.1"
+ignore_tags = ""
+topo_order = false
+sort_commits = "oldest"

+ 33 - 0
prototyper/docs/booting-archlinux-in-qemu-using-uboot-and-rustsbi.md

@@ -0,0 +1,33 @@
+# 使用RustSBI & U-Boot在QEMU中启动ArchLinux
+
+本教程给出了使用RustSBI和U-Boot在QEMU中启动ArchLinux的基本流程。
+
+本教程要求您使用非RISC-V Arch Linux(x86_64 或 aarch64 等)机器上运行,因为我们使用了`pacstrap`和`pacman`。
+
+本教程使用软件版本如下:
+
+|         软件          |  版本   |
+| :-------------------: | :-----: |
+| riscv64-linux-gnu-gcc | 14.1.0  |
+|  qemu-system-riscv64  |  9.0.1  |
+|  RustSBI Prototyper   |  0.0.0  |
+|        U-Boot         | 2024.04 |
+
+1. 安装依赖环境。
+``` shell
+# pacman -Syu then reboot is recommended before this
+$ sudo pacman -S arch-install-scripts git qemu-img qemu-system-riscv riscv64-linux-gnu-gcc devtools-riscv64
+```
+
+2. Clone构建脚本,构建rootfs和镜像。
+``` shell
+$ git clone -b rustsbi https://github.com/guttatus/archriscv-scriptlet.git
+$ cd archriscv-scriptlet
+$ ./mkrootfs
+$ ./mkimg
+```
+3. 使用Qemu启动Archlinux
+``` shell
+$ ./startqemu.sh
+```
+如果在最后一步中,您发现自己卡在 `[ OK ] Reached target Graphical Interface` 超过5分钟,只需按 `Ctrl`-`C` 并重新运行 `startqemu.sh`。

+ 129 - 0
prototyper/docs/booting-fedora-in-qemu-using-uboot-and-rustsbi.md

@@ -0,0 +1,129 @@
+# 使用RustSBI & U-Boot在QEMU中启动 Fedora
+
+本教程给出了使用RustSBI和U-Boot在QEMU中启动 Fedora 的基本流程。
+
+本教程使用软件版本如下:
+
+|         软件          |  版本   |
+| :-------------------: | :-----: |
+| riscv64-linux-gnu-gcc | 14.1.0  |
+|  qemu-system-riscv64  |  9.0.1  |
+|  RustSBI Prototyper   |  0.0.0  |
+|        U-Boot         | 2024.04 |
+
+## 准备RustSBI Prototyper, U-Boot ,Fedora 
+
+创建工作目录并进入该目录
+
+``` shell
+$ mkdir workshop && cd workshop
+```
+
+### Clone RustSBI Prototyper
+
+``` shell
+$ git clone https://github.com/rustsbi/prototyper.git && cd prototyper && git checkout main && cd ..
+```
+
+### Clone U-Boot
+
+``` shell
+$ git clone https://github.com/u-boot/u-boot.git && cd u-boot && git checkout v2024.04 && cd ..
+```
+### 下载 Fedora 镜像文件
+
+下载链接:<https://dl.fedoraproject.org/pub/alt/risc-v/disk_images/Fedora-40/Fedora.riscv64-40-20240429.n.0.qcow2>
+```shell
+$ mkdir -p fedora
+$ cd fedora
+$ wget https://dl.fedoraproject.org/pub/alt/risc-v/disk_images/Fedora-40/Fedora.riscv64-40-20240429.n.0.qcow2
+$ cd ..
+```
+
+## 编译RustSBI  Prototyper
+
+进入prototyper目录
+
+``` shell
+$ cd prototyper
+```
+
+编译RustSBI  Prototyper
+
+``` shell
+$ cargo prototyper
+```
+
+## 编译U-Boot SPL
+
+进入U-Boot目录
+
+``` shell
+$ cd u-boot
+```
+
+导出环境变量
+
+```shell
+$ export ARCH=riscv
+$ export CROSS_COMPILE=riscv64-linux-gnu-
+$ export OPENSBI=../prototyper/target/riscv64imac-unknown-none-elf/release/rustsbi-prototyper.bin 
+```
+
+生成`.config`文件,编译U-Boot
+
+```shell
+# To generate .config file out of board configuration file
+$ make qemu-riscv64_spl_defconfig
+$ sed -i.bak 's/CONFIG_BOOTCOMMAND=*/CONFIG_BOOTCOMMAND="fatload virtio 0:1 84000000 EFI\/Linux\/6.8.7-300.4.riscv64.fc40.riscv64.efi; setenv bootargs root=UUID=57cbf0ca-8b99-45ae-ae9d-3715598f11c4 ro rootflags=subvol=root rhgb LANG=en_US.UTF-8 console=ttyS0 earlycon=sbi; bootefi 0x84000000 - ${fdtcontroladdr};"/' .config
+$ make -j$(nproc)
+```
+
+## 配置 cloud-init
+
+```shell
+$ touch network-config
+$ touch meta-data
+$ cat >user-data <<EOF
+
+#cloud-config
+password: password
+chpasswd:
+  expire: False
+ssh_pwauth: True
+EOF
+
+genisoimage \
+    -output seed.img \
+    -volid cidata -rational-rock -joliet \
+    user-data meta-data network-config
+```
+
+## 使用RustSBI 原型系统和U-Boot启动 Fedora
+
+进入`workshop`目录
+
+``` shell
+$ cd workshop
+```
+
+运行下面命令
+
+``` shell
+$ qemu-system-riscv64 \
+    -nographic -machine virt \
+    -smp 4 -m 8G \
+    -bios ./u-boot/spl/u-boot-spl  \
+    -device loader,file=./u-boot/u-boot.itb,addr=0x80200000 \
+    -drive file=./fedora/Fedora.riscv64-40-20240429.n.0.qcow2,format=qcow2,if=none,id=hd0 \
+    -object rng-random,filename=/dev/urandom,id=rng0 \
+    -device virtio-vga \
+    -device virtio-rng-device,rng=rng0 \
+    -device virtio-blk-device,drive=hd0 \
+    -device virtio-net-device,netdev=usernet \
+    -netdev user,id=usernet,hostfwd=tcp::12055-:22 \
+    -device qemu-xhci -usb -device usb-kbd -device usb-tablet \
+    -cdrom ./seed.img
+```
+
+帐号默认为 `fedora`,密码应为 `cloud-init` 配置过程中的 `password` 项值。

+ 147 - 0
prototyper/docs/booting-freebsd-in-qemu-using-uboot-and-rustsbi.md

@@ -0,0 +1,147 @@
+# 使用RustSBI & U-Boot在QEMU中启动FreeBSD
+
+本教程给出了使用RustSBI和U-Boot在QEMU中启动FreeBSD的基本流程。
+
+请读者在其主机上安装必要的软件来尝试本教程的脚本。本教程是在Arch Linux上开发的。
+
+RustSBI 原型系统提供动态固件,根据前一个阶段传入的信息动态加载下一个阶段。
+
+本教程使用软件版本如下:
+
+|         软件          |  版本   |
+| :-------------------: | :-----: |
+| riscv64-linux-gnu-gcc | 14.2.0  |
+|  qemu-system-riscv64  |  9.1.1  |
+|  RustSBI Prototyper   |  0.0.0  |
+|        U-Boot         | 2024.04 |
+|       FreeBSD         |  14.1   |
+
+## 环境配置
+
+### 安装交叉编译器和QEMU
+
+For Arch Linux
+
+``` shell
+$ sudo pacman -S git riscv64-linux-gnu-gcc qemu-system-riscv
+```
+
+#### 测试是否成功安装
+
+For riscv64-linux-gnu-gcc:
+
+``` shell
+$ riscv64-linux-gnu-gcc --version
+```
+
+它将输出以下版本信息
+
+```
+riscv64-linux-gnu-gcc (GCC) 14.2.0
+Copyright (C) 2024 Free Software Foundation, Inc.
+This is free software; see the source for copying conditions.  There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+```
+
+For QEMU:
+
+``` shell
+$ qemu-system-riscv64 --version
+```
+
+它将输出以下版本信息
+
+```
+QEMU emulator version 9.1.1
+Copyright (c) 2003-2024 Fabrice Bellard and the QEMU Project developers
+```
+
+### 准备RustSBI Prototyper, U-Boot和FreeBSD镜像
+
+创建工作目录并进入该目录
+
+``` shell
+$ mkdir workshop && cd workshop
+```
+
+Clone RustSBI Prototyper
+
+``` shell
+$ git clone https://github.com/rustsbi/prototyper.git && cd prototyper && git checkout main && cd ..
+```
+
+Clone U-Boot
+
+``` shell
+$ git clone https://github.com/u-boot/u-boot.git && cd u-boot && git checkout v2024.04 && cd ..
+```
+
+Download FreeBSD
+``` shell
+$ wget https://download.freebsd.org/releases/VM-IMAGES/14.1-RELEASE/riscv64/Latest/FreeBSD-14.1-RELEASE-riscv-riscv64.raw.xz && xz -d FreeBSD-14.1-RELEASE-riscv-riscv64.raw.xz
+```
+
+
+## 编译RustSBI  Prototyper
+
+进入prototyper目录
+
+``` shell
+$ cd prototyper
+```
+
+编译RustSBI  Prototyper
+
+``` shell
+$ cargo prototyper
+```
+
+## 编译U-Boot SPL
+
+进入U-Boot目录
+
+``` shell
+$ cd u-boot
+```
+
+导出环境变量
+
+``` shell
+$ export ARCH=riscv
+$ export CROSS_COMPILE=riscv64-linux-gnu-
+$ export OPENSBI=../prototyper/target/riscv64imac-unknown-none-elf/release/rustsbi-prototyper.bin
+```
+
+生成`.config`文件
+
+``` shell
+# To generate .config file out of board configuration file
+$ make qemu-riscv64_spl_defconfig
+```
+
+编译U-Boot
+
+``` shell
+# To build U-Boot
+$ make -j$(nproc)
+```
+
+本小节将使用二进制文件 `./spl/u-boot-spl`和`./u-boot.itb `。
+
+## 使用RustSBI 原型系统和U-Boot启动Linux Kernel
+
+进入`workshop`目录
+
+``` shell
+$ cd workshop
+```
+
+运行下面命令
+
+``` shell
+$ qemu-system-riscv64 -M virt -smp 1 -m 256M -nographic \
+          -bios ./u-boot/spl/u-boot-spl \
+          -device loader,file=./u-boot/u-boot.itb,addr=0x80200000 \
+          -blockdev driver=file,filename=./FreeBSD-14.1-RELEASE-riscv-riscv64.raw,node-name=hd0 \
+          -device virtio-blk-device,drive=hd0
+```

+ 499 - 0
prototyper/docs/booting-linux-kernel-in-qemu-using-uboot-and-opensbi.md

@@ -0,0 +1,499 @@
+# 使用OpenSBI & U-Boot在QEMU中启动Linux内核
+
+本教程给出了使用OpenSBI和U-Boot在QEMU中启动Linux内核的基本流程。高级用户可以在本教程中配置或构建各种内容时尝试不同的选项。
+
+请读者在其主机上安装必要的软件来尝试本教程的脚本。本教程是在Arch Linux上开发的。
+
+[环境配置](#环境配置)小节给出了本教程的环境配置方法,用户在使用本教程时需要先完成环境配置小节内容。
+
+[编译Linux Kernel](#编译linux-kernel)小节给出了Linux Kernel的编译流程,并使用编译好的Linux Kernel镜像制作启动盘。
+
+OpenSBI 有三种 Firmware:
+
+- `fw_payload`:下一引导阶段被作为 payload 打包进来,通常是 U-Boot 或 Linux。这是兼容 Linux 的 RISC-V 硬件所使用的默认 Firmware。
+- `fw_jump`:跳转到一个固定地址,该地址上需存有下一个加载器。QEMU 的早期版本曾经使用过它。
+- `fw_dynamic`:根据前一个阶段传入的信息动态加载下一个阶段。U-Boot SPL/Coreboot 使用 `fw_dynamic`。现在 QEMU 默认使用 `fw_dynamic`。
+
+[`fw_payload`](#fw_payload)小节本给出了使用OpnSBI `fw_payload`类型固件和U-Boot在QEMU上启动Linux Kernel的教程。
+
+[`fw_jump`](#fw_jump)小节本给出了使用OpnSBI `fw_jump`类型固件和U-Boot在QEMU上启动Linux Kernel的教程。
+
+[`fw_dynamic`](#fw_dynamic)小节本给出了使用OpnSBI `fw_dynamic`类型固件和U-Boot在QEMU上启动Linux Kernel的教程。
+
+本教程使用软件版本如下:
+
+|         软件          |  版本   |
+| :-------------------: | :-----: |
+| riscv64-linux-gnu-gcc | 14.1.0  |
+|  qemu-system-riscv64  |  9.0.1  |
+|        OpenSBI        |   1.4   |
+|        U-Boot         | 2024.04 |
+|     Linux Kernel      |   6.2   |
+|        busybox        | 1.36.0  |
+
+## 环境配置
+
+### 安装交叉编译器和QEMU
+
+For Arch Linux:
+
+``` shell
+$ sudo pacman -S git riscv64-linux-gnu-gcc qemu-system-riscv
+```
+
+For Ubuntu:
+
+``` shell
+$ sudo apt-get update && sudo apt-get upgrade
+$ sudo apt-get install git qemu-system-misc gcc-riscv64-linux-gnu 
+```
+
+#### 测试是否成功安装
+
+For riscv64-linux-gnu-gcc:
+
+``` shell
+$  riscv64-linux-gnu-gcc --version
+```
+
+它将输出以下版本信息
+
+``` 
+riscv64-linux-gnu-gcc (GCC) 14.1.0
+Copyright (C) 2024 Free Software Foundation, Inc.
+This is free software; see the source for copying conditions.  There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+```
+
+For QEMU:
+
+``` shell
+$ qemu-system-riscv64  --version
+```
+
+它将输出以下版本信息
+
+``` 
+QEMU emulator version 9.0.1
+Copyright (c) 2003-2024 Fabrice Bellard and the QEMU Project developers
+```
+
+### 准备OpenSBI, U-Boot , busybox和Linux Kernel源码
+
+创建工作目录并进入该目录
+
+``` shell
+$ mkdir workshop && cd workshop
+```
+
+Clone OpenSBI
+
+``` shell
+$ git clone https://github.com/riscv/opensbi.git && cd opensbi && git checkout v1.4 && cd ..
+```
+
+Clone U-Boot
+
+``` shell
+$ git clone https://github.com/u-boot/u-boot.git && cd u-boot && git checkout v2024.04 && cd ..
+```
+
+Clone busybox
+
+``` shell
+$ git clone https://github.com/mirror/busybox.git && cd busybox && git checkout 1_36_0 && cd ..
+```
+
+Clone Linux Kernel
+
+``` shell
+$ git clone https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git && cd linux && git checkout v6.2 && cd ..
+```
+
+## 编译Linux Kernel
+
+进入`linux`目录
+
+``` shell
+$ cd linux
+```
+
+导出环境变量
+
+``` shell
+$ export ARCH=riscv
+$ export CROSS_COMPILE=riscv64-linux-gnu-
+```
+
+生成`.config`文件
+
+``` shell
+$ make defconfig
+```
+
+验证`.config`文件是否存在RISC-V
+
+``` shell
+$ grep --color=always -ni 'riscv' .config
+```
+
+观察到RISC-V 配置选项已启用
+
+``` 
+CONFIG_RISCV=y
+```
+
+编译Linux Kernel
+
+``` shell
+$ make -j$(nproc)
+```
+
+生成的文件`Image` 和 `Image.gz` 可以在`arch/riscv/boot/`目录找到。 `Image.gz`是 `Image` 的压缩形式。
+
+### 创建根文件系统
+
+#### 编译busybox
+
+> busybox在Ubuntu 22.04和Arch Linux系统上编译时会报错,推荐在Ubuntu 20.04系统上编译。
+
+进入busybox目录
+
+``` shell
+$ cd busybox
+```
+
+导出环境变量
+
+``` shell
+$ export ARCH=riscv
+$ export CROSS_COMPILE=riscv64-linux-gnu-
+```
+
+编译busybox
+
+``` shell
+$ make defconfig
+$ make menuconfig
+# Enable the Build static binary (no shared libs) option in Settings-->Build Options
+$ make -j $(nproc)
+$ make install
+```
+
+#### 创建启动盘
+
+在`workshop`目录运行以下命令来创建一个1 GB的磁盘镜像
+
+``` shell
+# Create a 1 GB disk image
+$ qemu-img create linux-rootfs.img 1g
+```
+
+#### 创建分区
+
+将在磁盘映像`linux-rootfs.img`上创建1个分区,这个分区是可引导的。
+
+`parted`命令将用于在镜像`linux-rootfs.img`中创建分区。在镜像中创建分区表:
+
+``` shell
+$ sudo parted linux-rootfs.img mklabel gpt
+```
+
+现在`linux-rootfs.img`中有一个分区表。将`linux-rootfs.img`挂载为loop device,以便它可以用作块设备。将`linux-rootfs.img`挂载为块设备将允许在其中创建分区。
+
+``` shell
+# Attach linux-rootfs.img with the first available loop device
+$ sudo losetup --find --show linux-rootfs.img
+```
+
+> - `find`:查找第一个未使用的loop device
+> - `show`:显示`linux-rootfs.img`附加到的loop device的名称
+
+记下循环设备的完整路径。在本教程中它是`/dev/loop0`。对`/dev/loop0`的操作将会对`linux-rootfs.img`进行操作。
+
+对`/dev/loop0`分区
+
+``` shell
+# Create a couple of primary partitions
+$ sudo parted --align minimal /dev/loop0 mkpart primary ext4 0 100%
+
+$ sudo parted /dev/loop0 print
+```
+
+#### 格式化分区
+
+通过以下命令查看分区:
+
+``` shell
+$ ls -l /dev/loop0*
+```
+
+在本教程中,分区为`/dev/loop0p1`。
+
+格式化分区并创建`ext4`文件系统,同时将分区设置为可引导分区。
+
+``` shell
+$ sudo mkfs.ext4 /dev/loop0p1
+
+# Mark first partition as bootable
+$ sudo parted /dev/loop0 set 1 boot on
+```
+
+#### 将Linux Kernel和根文件系统拷贝进启动盘
+
+``` shell
+# Mount the 1st partition
+$ sudo mkdir rootfs
+$ sudo mount /dev/loop0p1 rootfs
+$ cd rootfs
+```
+拷贝Linux Kernel镜像
+``` shell
+$ sudo cp ../linux/arch/riscv/boot/Image .
+```
+
+拷贝根文件系统
+
+``` shell
+$ sudo cp -r ../busybox/_install/* .
+$ sudo mkdir proc sys dev etc etc/init.d
+$ cd etc/init.d/
+$ sudo cat > rcS << EOF
+  #!/bin/sh
+  mount -t proc none /proc
+  mount -t sysfs none /sys
+  /sbin/mdev -s
+  EOF
+$ sudo chmod +x rcS
+```
+
+卸载`rootfs`
+
+``` shell
+$ cd workshop
+$ sudo umount rootfs
+```
+
+将`/dev/loop0`分离
+
+``` shell
+$ sudo losetup -d /dev/loop0
+```
+
+## `fw_payload`
+
+本小节给出了使用OpnSBI `fw_payload`类型固件和U-Boot在QEMU上启动Linux Kernel的教程。
+
+### 编译U-Boot
+
+进入U-Boot目录
+
+``` shell
+$ cd u-boot
+```
+
+导出环境变量
+
+``` shell
+$ export ARCH=riscv
+$ export CROSS_COMPILE=riscv64-linux-gnu-
+```
+
+生成`.config`文件
+
+``` shell
+# To generate .config file out of board configuration file
+$ make qemu-riscv64_smode_defconfig
+# add bootcmd value
+$ make menuconfig
+```
+U-Boot 配置选项将加载到终端。导航到 `Boot options` $\rightarrow$ `bootcmd value` 并将以下内容写入 `bootcmd` 值:
+
+``` 
+ext4load virtio 0:1 84000000 Image; setenv bootargs root=/dev/vda1 rw console=ttyS0; booti 0x84000000 - ${fdtcontroladdr}
+```
+
+编译U-Boot
+
+``` shell
+# To build U-Boot
+$ make -j$(nproc)
+```
+
+U-Boot 二进制文件位于 `./u-boot.bin`。
+
+### 编译OpenSBI
+
+进入OpenSBI目录
+
+``` shell
+$ cd opensbi
+```
+
+导出环境变量
+
+``` shell
+$ export ARCH=riscv
+$ export CROSS_COMPILE=riscv64-linux-gnu-
+```
+
+编译OpenSBI
+
+``` shell
+$ make PLATFORM=generic FW_PAYLOAD_PATH=../u-boot/u-boot.bin -j$(nproc)
+```
+
+本小节将使用 QEMU 可以运行的输出文件 `build/platform/generic/firmware/fw_payload.elf`。由于`FW_PAYLOAD_PATH`指向 u-boot,因此 U-Boot 嵌入在输出中,OpenSBI 将自动启动 U-Boot。
+
+### 使用OpenSBI `fw_payload`固件启动Linux Kernel
+
+进入`workshop`目录
+
+``` shell
+$ cd workshop
+```
+
+运行下面命令
+
+``` shell
+$ qemu-system-riscv64 -M virt -smp 4 -m 256M -nographic \
+      -bios ./opensbi/build/platform/generic/firmware/fw_payload.elf \
+      -blockdev driver=file,filename=./linux-rootfs.img,node-name=hd0 \
+      -device virtio-blk-device,drive=hd0
+```
+
+
+
+## `fw_jump`
+
+本小节给出了使用OpnSBI `fw_jump`类型固件和U-Boot在QEMU上启动Linux Kernel的教程。
+
+### 编译U-Boot
+
+和[`fw_payload`](#fw_payload)小节一致
+
+### 编译OpenSBI
+
+进入OpenSBI目录
+
+``` shell
+$ cd opensbi
+```
+
+导出环境变量
+
+``` shell
+$ export ARCH=riscv
+$ export CROSS_COMPILE=riscv64-linux-gnu-
+```
+
+编译OpenSBI
+
+``` shell
+$ make all PLATFORM=generic PLATFORM_RISCV_XLEN=64 -j$(nproc)
+```
+
+本小节将使用 QEMU 可以运行的输出文件 `build/platform/generic/firmware/fw_jump.bin`。
+
+### 使用OpenSBI `fw_jump`固件启动Linux Kernel
+
+进入`workshop`目录
+
+``` shell
+$ cd workshop
+```
+
+运行下面命令
+
+``` shell
+$ qemu-system-riscv64 -M virt -smp 4 -m 256M -nographic \
+      -bios ./opensbi/build/platform/generic/firmware/fw_jump.elf \
+      -kernel ./u-boot/u-boot.bin  \
+      -blockdev driver=file,filename=./linux-rootfs.img,node-name=hd0 \
+      -device virtio-blk-device,drive=hd0
+```
+
+## `fw_dynamic`
+
+本小节给出了使用OpnSBI `fw_dynamic`类型固件和U-Boot在QEMU上启动Linux Kernel的教程。
+
+### 编译OpenSBI
+
+进入OpenSBI目录
+
+``` shell
+$ cd opensbi
+```
+
+导出环境变量
+
+``` shell
+$ export ARCH=riscv
+$ export CROSS_COMPILE=riscv64-linux-gnu-
+```
+
+编译OpenSBI
+
+``` shell
+$ make all PLATFORM=generic PLATFORM_RISCV_XLEN=64 -j$(nproc)
+```
+
+本小节将使用 QEMU 可以运行的输出文件 `build/platform/generic/firmware/fw_dynamic.bin`。
+
+### 编译U-Boot SPL
+
+进入U-Boot目录
+
+``` shell
+$ cd u-boot
+```
+
+导出环境变量
+
+``` shell
+$ export ARCH=riscv
+$ export CROSS_COMPILE=riscv64-linux-gnu-
+$ export OPENSBI=../opensbi/build/platform/generic/firmware/fw_dynamic.bin 
+```
+
+生成`.config`文件
+
+``` shell
+# To generate .config file out of board configuration file
+$ make qemu-riscv64_spl_defconfig
+# add bootcmd value
+$ make menuconfig
+```
+
+U-Boot 配置选项将加载到终端。导航到 `Boot options` $\rightarrow$ `bootcmd value` 并将以下内容写入 `bootcmd` 值:
+
+``` 
+ext4load virtio 0:1 84000000 Image; setenv bootargs root=/dev/vda1 rw console=ttyS0; booti 0x84000000 - ${fdtcontroladdr}
+```
+
+编译U-Boot
+
+``` shell
+# To build U-Boot
+$ make -j$(nproc)
+```
+
+本小节将使用二进制文件 `./spl/u-boot-spl`和`./u-boot.itb `。
+
+### 使用OpenSBI `fw_dynamic`固件启动Linux Kernel
+
+进入`workshop`目录
+
+``` shell
+$ cd workshop
+```
+
+运行下面命令
+
+``` shell
+$ qemu-system-riscv64 -M virt -smp 4 -m 256M -nographic \
+          -bios ./u-boot/spl/u-boot-spl \
+          -device loader,file=./u-boot/u-boot.itb,addr=0x80200000 \
+          -blockdev driver=file,filename=./linux-rootfs.img,node-name=hd0 \
+          -device virtio-blk-device,drive=hd0
+```
+

+ 343 - 0
prototyper/docs/booting-linux-kernel-in-qemu-using-uboot-and-rustsbi.md

@@ -0,0 +1,343 @@
+# 使用RustSBI & U-Boot在QEMU中启动Linux内核
+
+本教程给出了使用RustSBI和U-Boot在QEMU中启动Linux内核的基本流程。高级用户可以在本教程中配置或构建各种内容时尝试不同的选项。
+
+请读者在其主机上安装必要的软件来尝试本教程的脚本。本教程是在Arch Linux上开发的。
+
+[环境配置](#环境配置)小节给出了本教程的环境配置方法,用户在使用本教程时需要先完成环境配置小节内容。
+
+[编译Linux Kernel](#编译linux-kernel)小节给出了Linux Kernel的编译流程,并使用编译好的Linux Kernel镜像制作启动盘。
+
+RustSBI 原型系统提供动态固件,根据前一个阶段传入的信息动态加载下一个阶段。
+
+本教程使用软件版本如下:
+
+|         软件          |  版本   |
+| :-------------------: | :-----: |
+| riscv64-linux-gnu-gcc | 14.1.0  |
+|  qemu-system-riscv64  |  9.0.1  |
+|  RustSBI Prototyper   |  0.0.0  |
+|        U-Boot         | 2024.04 |
+|     Linux Kernel      |   6.2   |
+|        busybox        | 1.36.0  |
+
+## 环境配置
+
+### 安装交叉编译器和QEMU
+
+For Arch Linux:
+
+``` shell
+$ sudo pacman -S git riscv64-linux-gnu-gcc qemu-system-riscv
+```
+
+For Ubuntu:
+
+``` shell
+$ sudo apt-get update && sudo apt-get upgrade
+$ sudo apt-get install git qemu-system-misc gcc-riscv64-linux-gnu 
+```
+
+#### 测试是否成功安装
+
+For riscv64-linux-gnu-gcc:
+
+``` shell
+$ riscv64-linux-gnu-gcc --version
+```
+
+它将输出以下版本信息
+
+``` 
+riscv64-linux-gnu-gcc (GCC) 14.1.0
+Copyright (C) 2024 Free Software Foundation, Inc.
+This is free software; see the source for copying conditions.  There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+```
+
+For QEMU:
+
+``` shell
+$ qemu-system-riscv64 --version
+```
+
+它将输出以下版本信息
+
+``` 
+QEMU emulator version 9.0.1
+Copyright (c) 2003-2024 Fabrice Bellard and the QEMU Project developers
+```
+
+### 准备RustSBI Prototyper, U-Boot , busybox和Linux Kernel源码
+
+创建工作目录并进入该目录
+
+``` shell
+$ mkdir workshop && cd workshop
+```
+
+Clone RustSBI Prototyper
+
+``` shell
+$ git clone https://github.com/rustsbi/prototyper.git && cd prototyper && git checkout main && cd ..
+```
+
+Clone U-Boot
+
+``` shell
+$ git clone https://github.com/u-boot/u-boot.git && cd u-boot && git checkout v2024.04 && cd ..
+```
+
+Clone busybox
+
+``` shell
+$ git clone https://github.com/mirror/busybox.git && cd busybox && git checkout 1_36_0 && cd ..
+```
+
+Clone Linux Kernel
+
+``` shell
+$ git clone https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git && cd linux && git checkout v6.2 && cd ..
+```
+
+## 编译Linux Kernel
+
+进入`linux`目录
+
+``` shell
+$ cd linux
+```
+
+导出环境变量
+
+``` shell
+$ export ARCH=riscv
+$ export CROSS_COMPILE=riscv64-linux-gnu-
+```
+
+生成`.config`文件
+
+``` shell
+$ make defconfig
+```
+
+验证`.config`文件是否存在RISC-V
+
+``` shell
+$ grep --color=always -ni 'riscv' .config
+```
+
+观察到RISC-V 配置选项已启用
+
+``` 
+CONFIG_RISCV=y
+```
+
+编译Linux Kernel
+
+``` shell
+$ make -j$(nproc)
+```
+
+生成的文件`Image` 和 `Image.gz` 可以在`arch/riscv/boot/`目录找到。 `Image.gz`是 `Image` 的压缩形式。
+
+### 创建根文件系统
+
+#### 编译busybox
+
+> busybox在Ubuntu 22.04和Arch Linux系统上编译时会报错,推荐在Ubuntu 20.04系统上编译。
+
+进入busybox目录
+
+``` shell
+$ cd busybox
+```
+
+导出环境变量
+
+``` shell
+$ export ARCH=riscv
+$ export CROSS_COMPILE=riscv64-linux-gnu-
+```
+
+编译busybox
+
+``` shell
+$ make defconfig
+$ make menuconfig
+# Enable the Build static binary (no shared libs) option in Settings-->Build Options
+$ make -j $(nproc)
+$ make install
+```
+
+#### 创建启动盘
+
+在`workshop`目录运行以下命令来创建一个1 GB的磁盘镜像
+
+``` shell
+# Create a 1 GB disk image
+$ qemu-img create linux-rootfs.img 1g
+```
+
+#### 创建分区
+
+将在磁盘映像`linux-rootfs.img`上创建1个分区,这个分区是可引导的。
+
+`parted`命令将用于在镜像`linux-rootfs.img`中创建分区。在镜像中创建分区表:
+
+``` shell
+$ sudo parted linux-rootfs.img mklabel gpt
+```
+
+现在`linux-rootfs.img`中有一个分区表。将`linux-rootfs.img`挂载为loop device,以便它可以用作块设备。将`linux-rootfs.img`挂载为块设备将允许在其中创建分区。
+
+``` shell
+# Attach linux-rootfs.img with the first available loop device
+$ sudo losetup --find --show linux-rootfs.img
+```
+
+> - `find`:查找第一个未使用的loop device
+> - `show`:显示`linux-rootfs.img`附加到的loop device的名称
+
+记下循环设备的完整路径。在本教程中它是`/dev/loop0`。对`/dev/loop0`的操作将会对`linux-rootfs.img`进行操作。
+
+对`/dev/loop0`分区
+
+``` shell
+# Create a couple of primary partitions
+$ sudo parted --align minimal /dev/loop0 mkpart primary ext4 0 100%
+
+$ sudo parted /dev/loop0 print
+```
+
+#### 格式化分区
+
+通过以下命令查看分区:
+
+``` shell
+$ ls -l /dev/loop0*
+```
+
+在本教程中,分区为`/dev/loop0p1`。
+
+格式化分区并创建`ext4`文件系统,同时将分区设置为可引导分区。
+
+``` shell
+$ sudo mkfs.ext4 /dev/loop0p1
+
+# Mark first partition as bootable
+$ sudo parted /dev/loop0 set 1 boot on
+```
+
+#### 将Linux Kernel和根文件系统拷贝进启动盘
+
+``` shell
+# Mount the 1st partition
+$ sudo mkdir rootfs
+$ sudo mount /dev/loop0p1 rootfs
+$ cd rootfs
+```
+拷贝Linux Kernel镜像
+``` shell
+$ sudo cp ../linux/arch/riscv/boot/Image .
+```
+
+拷贝根文件系统
+
+``` shell
+$ sudo cp -r ../busybox/_install/* .
+$ sudo mkdir proc sys dev etc etc/init.d
+$ cd etc/init.d/
+$ sudo cat > rcS << EOF
+  #!/bin/sh
+  mount -t proc none /proc
+  mount -t sysfs none /sys
+  /sbin/mdev -s
+  EOF
+$ sudo chmod +x rcS
+```
+
+卸载`rootfs`
+
+``` shell
+$ cd workshop
+$ sudo umount rootfs
+```
+
+将`/dev/loop0`分离
+
+``` shell
+$ sudo losetup -d /dev/loop0
+```
+
+## 编译RustSBI  Prototyper
+
+进入prototyper目录
+
+``` shell
+$ cd prototyper
+```
+
+编译RustSBI  Prototyper
+
+``` shell
+$ cargo prototyper
+```
+
+## 编译U-Boot SPL
+
+进入U-Boot目录
+
+``` shell
+$ cd u-boot
+```
+
+导出环境变量
+
+``` shell
+$ export ARCH=riscv
+$ export CROSS_COMPILE=riscv64-linux-gnu-
+$ export OPENSBI=../prototyper/target/riscv64imac-unknown-none-elf/release/rustsbi-prototyper.bin 
+```
+
+生成`.config`文件
+
+``` shell
+# To generate .config file out of board configuration file
+$ make qemu-riscv64_spl_defconfig
+# add bootcmd value
+$ make menuconfig
+```
+
+U-Boot 配置选项将加载到终端。导航到 `Boot options` $\rightarrow$ `bootcmd value` 并将以下内容写入 `bootcmd` 值:
+
+``` 
+ext4load virtio 0:1 84000000 Image; setenv bootargs root=/dev/vda1 rw console=ttyS0; booti 0x84000000 - ${fdtcontroladdr}
+```
+
+编译U-Boot
+
+``` shell
+# To build U-Boot
+$ make -j$(nproc)
+```
+
+本小节将使用二进制文件 `./spl/u-boot-spl`和`./u-boot.itb `。
+
+## 使用RustSBI 原型系统和U-Boot启动Linux Kernel
+
+进入`workshop`目录
+
+``` shell
+$ cd workshop
+```
+
+运行下面命令
+
+``` shell
+$ qemu-system-riscv64 -M virt -smp 1 -m 256M -nographic \
+          -bios ./u-boot/spl/u-boot-spl \
+          -device loader,file=./u-boot/u-boot.itb,addr=0x80200000 \
+          -blockdev driver=file,filename=./linux-rootfs.img,node-name=hd0 \
+          -device virtio-blk-device,drive=hd0
+```
+

+ 107 - 0
prototyper/docs/booting-openEuler-23.09-in-qemu-using-uboot-and-rustsbi.md

@@ -0,0 +1,107 @@
+# 使用RustSBI & U-Boot在QEMU中启动openEuler 23.09
+
+本教程给出了使用RustSBI和U-Boot在QEMU中启动openEuler 23.09的基本流程。
+
+本教程使用软件版本如下:
+
+|         软件          |  版本   |
+| :-------------------: | :-----: |
+| riscv64-linux-gnu-gcc | 14.1.0  |
+|  qemu-system-riscv64  |  9.0.1  |
+|  RustSBI Prototyper   |  0.0.0  |
+|        U-Boot         | 2024.04 |
+
+## 准备RustSBI Prototyper, U-Boot ,openEuler 23.09
+创建工作目录并进入该目录
+
+``` shell
+$ mkdir workshop && cd workshop
+```
+
+### Clone RustSBI Prototyper
+
+``` shell
+$ git clone https://github.com/rustsbi/prototyper.git && cd prototyper && git checkout main && cd ..
+```
+
+### Clone U-Boot
+
+``` shell
+$ git clone https://github.com/u-boot/u-boot.git && cd u-boot && git checkout v2024.04 && cd ..
+```
+### 下载openEuler 23.09 Qemu磁盘镜像文件
+
+下载链接:[openEuler 23.09](https://mirror.iscas.ac.cn/openeuler-sig-riscv/openEuler-RISC-V/preview/openEuler-23.09-V1-riscv64/QEMU/openEuler-23.09-V1-base-qemu-preview.qcow2.zst)
+```shell
+ $ unzstd openEuler-23.09-V1-base-qemu-preview.qcow2.zst
+```
+- The password of user `root` is `openEuler12#$`.
+- The password of the default user `openeuler` is `openEuler12#$`.
+
+
+## 编译RustSBI  Prototyper
+
+进入prototyper目录
+
+``` shell
+$ cd prototyper
+```
+
+编译RustSBI  Prototyper
+
+``` shell
+$ cargo prototyper
+```
+
+## 编译U-Boot SPL
+
+进入U-Boot目录
+
+``` shell
+$ cd u-boot
+```
+
+导出环境变量
+
+``` shell
+$ export ARCH=riscv
+$ export CROSS_COMPILE=riscv64-linux-gnu-
+$ export OPENSBI=../prototyper/target/riscv64imac-unknown-none-elf/release/rustsbi-prototyper.bin 
+```
+
+生成`.config`文件,编译U-Boot
+
+``` shell
+# To generate .config file out of board configuration file
+$ make qemu-riscv64_spl_defconfig
+$ ./scripts/config -e CMD_BTRFS -e FS_BTRFS
+$ make olddefconfig
+$ sed -i.bak 's/# CONFIG_USE_BOOTARGS is not set/CONFIG_USE_BOOTARGS=y\nCONFIG_BOOTARGS="root=\/dev\/vda1 rw console=ttyS0 swiotlb=1 loglevel=7 systemd.default_timeout_start_sec=600 selinux=0 highres=off earlycon"/' .config
+$ make -j$(nproc)
+```
+
+## 使用RustSBI 原型系统和U-Boot启动openEuler 23.09
+
+进入`workshop`目录
+
+``` shell
+$ cd workshop
+```
+
+运行下面命令
+
+``` shell
+$ qemu-system-riscv64 \
+    -nographic -machine virt \
+    -smp 4 -m 8G \
+    -bios ./u-boot/spl/u-boot-spl  \
+    -device loader,file=./u-boot/u-boot.itb,addr=0x80200000 \
+    -drive file=openEuler-23.09-V1-base-qemu-preview.qcow2,format=qcow2,id=hd0 \
+    -object rng-random,filename=/dev/urandom,id=rng0 \
+    -device virtio-vga \
+    -device virtio-rng-device,rng=rng0 \
+    -device virtio-blk-device,drive=hd0 \
+    -device virtio-net-device,netdev=usernet \
+    -netdev user,id=usernet,hostfwd=tcp::12055-:22 \
+    -device qemu-xhci -usb -device usb-kbd -device usb-tablet
+```

+ 149 - 0
prototyper/docs/booting-openwrt-in-qemu-using-uboot-and-rustsbi.md

@@ -0,0 +1,149 @@
+# 使用RustSBI & U-Boot在QEMU中启动 Openwrt
+
+本教程给出了使用RustSBI和U-Boot在QEMU中启动Openwrt的基本流程。
+
+本教程使用软件版本如下:
+
+|         软件          |  版本   |
+| :-------------------: | :-----: |
+| riscv64-linux-gnu-gcc | 14.1.0  |
+|  qemu-system-riscv64  |  9.0.1  |
+|  RustSBI Prototyper   |  0.0.0  |
+|        U-Boot         | 2024.04 |
+
+## 准备RustSBI Prototyper, U-Boot ,Openwrt
+创建工作目录并进入该目录
+
+``` shell
+$ mkdir workshop && cd workshop
+```
+
+### Clone RustSBI Prototyper
+
+``` shell
+$ git clone https://github.com/rustsbi/prototyper.git && cd prototyper && git checkout main && cd ..
+```
+
+### Clone U-Boot
+
+``` shell
+$ git clone https://github.com/u-boot/u-boot.git && cd u-boot && git checkout v2024.04 && cd ..
+```
+
+### Clone & Patch Openwrt
+
+``` shell
+$ git clone https://git.openwrt.org/openwrt/openwrt.git 
+$ cd ./openwrt
+$ git checkout 603a3c6
+```
+
+应用本项目目录下的 `docs/openwrt-patch.patch`。
+
+```shell
+$ curl https://raw.githubusercontent.com/rustsbi/prototyper/refs/heads/main/docs/openwrt-patch.patch --output openwrt-patch.patch
+$ git apply openwrt-patch.patch
+```
+
+## 编译RustSBI  Prototyper
+
+进入prototyper目录
+
+``` shell
+$ cd prototyper
+```
+
+编译RustSBI  Prototyper
+
+``` shell
+$ cargo prototyper
+```
+
+## 编译U-Boot SPL
+
+进入U-Boot目录
+
+``` shell
+$ cd u-boot
+```
+
+导出环境变量
+
+``` shell
+$ export ARCH=riscv
+$ export CROSS_COMPILE=riscv64-linux-gnu-
+$ export OPENSBI=../prototyper/target/riscv64imac-unknown-none-elf/release/rustsbi-prototyper.bin 
+```
+
+生成`.config`文件,编译U-Boot
+
+``` shell
+# To generate .config file out of board configuration file
+$ make qemu-riscv64_spl_defconfig
+$ sed -i.bak 's/CONFIG_BOOTCOMMAND=*/CONFIG_BOOTCOMMAND="scsi scan; fatload scsi 0:3 84000000 Image; setenv bootargs root=\/dev\/sda4 rw earlycon console=\/dev\/ttyS0 rootwait; booti 0x84000000 - ${fdtcontroladdr};"/' .config
+$ make -j$(nproc)
+```
+
+## 编译 Openwrt
+
+首先,你应先按照 <https://openwrt.org/docs/guide-developer/toolchain/install-buildsystem> 配置自己的编译环境。
+
+(以下内容参照并修改自 <https://openwrt.org/docs/guide-developer/toolchain/use-buildsystem>)
+
+更新 Feeds:
+```shell
+$ cd openwrt
+# Update the feeds
+$ ./scripts/feeds update -a
+$ ./scripts/feeds install -a
+```
+
+修改配置:
+```shell
+$ make -j$(nproc) menuconfig
+```
+
+进入 `Target System`,选中 `$SiFive U-based RISC-V boards`。
+
+修改内核配置:
+```shell
+$ make -j$(nproc) kernel_menuconfig
+```
+
+进入后将   
+`Device Drivers` $\rightarrow$ `Serial ATA and Parallel ATA drivers (libata)` $\rightarrow$ `AHCI SATA support`  
+`Device Drivers` $\rightarrow$ `Network device support` $\rightarrow$ `Ethernet driver support` $\rightarrow$ `Intel devices` $\rightarrow$ `Intel(R) PRO/1000 Gigabit Ethernet support`  
+设为 `built-in`。
+
+编译镜像:
+```shell
+# Build the firmware image
+$ make -j$(nproc) defconfig download clean world
+```
+
+拷贝并解压镜像:
+```shell
+$ cd ..
+$ cp ./openwrt/bin/targets/sifiveu/generic/openwrt-sifiveu-generic-sifive_unleashed-ext4-sdcard.img.gz ./
+$ gzip -dk openwrt-sifiveu-generic-sifive_unleashed-ext4-sdcard.img.gz
+```
+
+## 使用RustSBI 原型系统和U-Boot启动 Openwrt
+
+进入`workshop`目录
+
+``` shell
+$ cd workshop
+```
+
+运行下面命令
+
+``` shell
+$ qemu-system-riscv64 \
+-machine virt -nographic -m 4096 -smp 1 \
+-bios ./u-boot/spl/u-boot-spl \
+-device virtio-rng-pci -device ahci,id=ahci -device ide-hd,bus=ahci.0,drive=mydrive \
+-drive file=./openwrt-sifiveu-generic-sifive_unleashed-ext4-sdcard.img,format=raw,if=none,id=mydrive \
+-device loader,file=./u-boot/u-boot.itb,addr=0x80200000 \
+-device e1000,netdev=n1 -netdev user,id=n1,hostfwd=tcp::12055-:22
+```

+ 156 - 0
prototyper/docs/booting-polyos-in-qemu-using-uboot-and-rustsbi.md

@@ -0,0 +1,156 @@
+# 使用RustSBI & U-Boot在QEMU中启动 PolyOS
+
+尽管 Openharmony 在 4.1 引入了 `device_qemu-riscv64-linux`,但是目前仍无法按照[文档](https://gitee.com/openharmony/device_qemu/tree/HEAD/riscv64_virt#)正常编译。
+
+本文于此介绍基于 OpenHarmony 的系统 -- PolyOS 使用 RustSBI 和 U-Boot 在 QEMU 中启动的方法。
+
+下令 `$workdir` 表示工作目录。
+
+### Clone & Compile RustSBI Prototyper
+
+``` shell
+$ cd $workdir
+$ git clone https://github.com/rustsbi/prototyper.git && cd prototyper && git checkout main && cd ..
+```
+
+``` shell
+$ cd prototyper
+```
+
+编译RustSBI  Prototyper
+
+``` shell
+$ cargo prototyper
+```
+
+### Clone & Compile U-Boot
+
+``` shell
+$ cd $workdir
+$ git clone https://github.com/u-boot/u-boot.git && cd u-boot && git checkout v2024.04 && cd ..
+```
+
+进入U-Boot目录
+
+``` shell
+$ cd u-boot
+```
+
+导出环境变量
+
+``` shell
+$ export ARCH=riscv
+$ export CROSS_COMPILE=riscv64-linux-gnu-
+$ export OPENSBI=../prototyper/target/riscv64imac-unknown-none-elf/release/rustsbi-prototyper.bin 
+```
+
+生成`.config`文件,编译U-Boot
+
+``` shell
+# To generate .config file out of board configuration file
+$ make qemu-riscv64_spl_defconfig
+$ make -j$(nproc)
+```
+
+
+### Download & Configure PolyOS
+
+下载 PolyOS Mobile 镜像:<https://polyos.iscas.ac.cn/downloads/polyos-mobile-latest.img.tar.xz>。
+
+```shell
+$ cd $workdir
+$ wget https://polyos.iscas.ac.cn/downloads/polyos-mobile-latest.img.tar.xz
+$ tar xvf polyos-mobile-latest.img.tar.xz
+```
+
+创建一个带分区表的镜像,并创建一个分区。
+
+```shell
+$ cd ./image
+$ qemu-img create boot.img 1g
+$ fdisk boot.img
+# 创建 GPT 分区表
+> g
+# 新建一个分区
+> n
+# 保存
+> w
+```
+
+( fdisk 的提示全选是和默认项即可。)
+
+挂载本地回环设备:
+
+```shell
+$ sudo losetup --find --show -P ./boot.img
+```
+
+以下假设挂载的本地回环设备为 `/dev/loop1`。
+
+将给定的 boot.ext4 写入该分区:
+
+```shell
+$ dd if=./boot.ext4 of=/dev/loop1p1
+```
+
+挂载该分区:
+
+```shell
+$ mkdir boot
+$ mount /dev/loop1p1 ./boot
+```
+
+创建 `./boot/extlinux/extlinux.conf`,并写入以下内容:
+
+```shell
+default polyOS-RISC-V
+label   polyOS-RISC-V
+    kernel /Image
+    initrd /ramdisk.img
+    append 'loglevel=1 ip=192.168.137.2:192.168.137.1:192.168.137.1:255.255.255.0::eth0:off sn=0023456789 console=tty0,115200 console=ttyS0,115200 init=/bin/init ohos.boot.hardware=virt root=/dev/ram0 rw ohos.required_mount.system=/dev/block/vdb@/usr@ext4@ro,barrier=1@wait,required ohos.required_mount.vendor=/dev/block/vdc@/vendor@ext4@ro,barrier=1@wait,required ohos.required_mount.sys_prod=/dev/block/vde@/sys_prod@ext4@ro,barrier=1@wait,required ohos.required_mount.chip_prod=/dev/block/vdf@/chip_prod@ext4@ro,barrier=1@wait,required ohos.required_mount.data=/dev/block/vdd@/data@ext4@nosuid,nodev,noatime,barrier=1,data=ordered,noauto_da_alloc@wait,reservedsize=1073741824 ohos.required_mount.misc=/dev/block/vda@/misc@none@none=@wait,required'
+```
+
+卸载相关分区和本地回环设备:
+
+```shell
+$ umount ./boot
+$ losetup -d /dev/loop1
+```
+
+### USE Qemu to bootup
+
+使用 qemu 启动:
+```shell
+$ cd $workdir/image
+image_path=`pwd`
+qemu-system-riscv64 \
+    -name PolyOS-Mobile \
+    -machine virt \
+    -m 4096\
+    -smp 4 \
+    -no-reboot \
+	-bios ../u-boot/spl/u-boot-spl \
+	-device loader,file=../u-boot/u-boot.itb,addr=0x80200000 \
+    -drive if=none,file=${image_path}/boot.img,format=raw,id=boot,index=6 \
+	-device ahci,id=ahci -device ide-hd,bus=ahci.0,drive=boot \
+    -drive if=none,file=${image_path}/updater.img,format=raw,id=updater,index=5 \
+    -device virtio-blk-device,drive=updater \
+    -drive if=none,file=${image_path}/system.img,format=raw,id=system,index=4 \
+    -device virtio-blk-device,drive=system \
+    -drive if=none,file=${image_path}/vendor.img,format=raw,id=vendor,index=3 \
+    -device virtio-blk-device,drive=vendor \
+    -drive if=none,file=${image_path}/userdata.img,format=raw,id=userdata,index=2 \
+    -device virtio-blk-device,drive=userdata \
+    -drive if=none,file=${image_path}/sys_prod.img,format=raw,id=sys-prod,index=1 \
+    -device virtio-blk-device,drive=sys-prod \
+    -drive if=none,file=${image_path}/chip_prod.img,format=raw,id=chip-prod,index=0 \
+    -device virtio-blk-device,drive=chip-prod \
+    -nographic \
+    -device virtio-gpu-pci,xres=486,yres=864,max_outputs=1,addr=08.0 \
+    -monitor telnet:127.0.0.1:55555,server,nowait \
+    -device virtio-mouse-pci \
+    -device virtio-keyboard-pci \
+    -device es1370 \
+    -k en-us \
+    -display sdl,gl=off
+```

+ 319 - 0
prototyper/docs/booting-test-kernel-in-qemu-using-uboot-and-rustsbi.md

@@ -0,0 +1,319 @@
+# 使用RustSBI & U-Boot SPL在QEMU中启动Test Kernel
+
+本教程给出了使用RustSBI和U-Boot在QEMU中启动Test Kernel的基本流程。
+
+其中启动流程分为两种类型:
+1. 只使用U-Boot SPL的启动流程
+2. 同时使用U-Boot SPL和U-Boot的启动流程。
+
+请读者在其主机上安装必要的软件来尝试本教程的脚本。本教程是在Arch Linux上开发的。
+
+[环境配置](#环境配置)小节给出了本教程的环境配置方法,用户在使用本教程时需要先完成环境配置小节内容。
+
+[使用U-Boot SPL启动Test Kernel](#使用U-Boot-SPL启动Test-Kernel)小节给出了只使用U-Boot SPL的启动流程。
+
+[使用U-Boot SPL和U-Boot启动Test Kernel](#使用U-Boot-SPL和U-Boot启动Test-Kernel)小节给出了同时使用U-Boot SPL和U-Boot的启动流程。
+
+本教程使用软件版本如下:
+
+|         软件          |  版本   |
+| :-------------------: | :-----: |
+| riscv64-linux-gnu-gcc | 14.1.0  |
+|  qemu-system-riscv64  |  9.0.1  |
+|  RustSBI Prototyper   |  0.0.0  |
+|        U-Boot         | 2024.04 |
+|     Linux Kernel      |   6.2   |
+|        busybox        | 1.36.0  |
+
+## 环境配置
+
+### 安装交叉编译器、QEMU和相关依赖
+
+For Arch Linux:
+
+``` shell
+$ sudo pacman -S git riscv64-linux-gnu-gcc qemu-system-riscv uboot-tools
+```
+
+For Ubuntu:
+
+``` shell
+$ sudo apt-get update && sudo apt-get upgrade
+$ sudo apt-get install git qemu-system-misc gcc-riscv64-linux-gnu u-boot-tools
+```
+
+#### 测试是否成功安装
+
+For riscv64-linux-gnu-gcc:
+
+``` shell
+$ riscv64-linux-gnu-gcc --version
+```
+
+它将输出以下版本信息
+
+```
+riscv64-linux-gnu-gcc (GCC) 14.1.0
+Copyright (C) 2024 Free Software Foundation, Inc.
+This is free software; see the source for copying conditions.  There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+```
+
+For QEMU:
+
+``` shell
+$ qemu-system-riscv64 --version
+```
+
+它将输出以下版本信息
+
+```
+QEMU emulator version 9.0.1
+Copyright (c) 2003-2024 Fabrice Bellard and the QEMU Project developers
+```
+
+### 准备RustSBI Prototyper, Test Kernel, U-Boot源码
+
+创建工作目录并进入该目录
+
+``` shell
+$ mkdir workshop && cd workshop
+```
+
+Clone RustSBI Prototyper
+
+``` shell
+$ git clone https://github.com/rustsbi/prototyper.git && cd prototyper && git checkout main && cd ..
+```
+
+Clone U-Boot
+
+``` shell
+$ git clone https://github.com/u-boot/u-boot.git && cd u-boot && git checkout v2024.04 && cd ..
+```
+
+## 使用U-Boot SPL启动Test Kernel
+### 编译RustSBI  Prototyper和Test Kernel
+
+进入prototyper目录
+
+``` shell
+$ cd prototyper
+```
+
+编译RustSBI Prototyper和Test Kernel
+
+``` shell
+$ cargo prototyper
+$ cargo test-kernel --pack
+```
+
+本小节将使用二进制文件 `./target/riscv64imac-unknown-none-elf/release/rustsbi-test-kernel.itb`。
+
+### 编译U-Boot SPL
+
+进入U-Boot目录
+
+``` shell
+$ cd u-boot
+```
+
+导出环境变量
+
+``` shell
+$ export ARCH=riscv
+$ export CROSS_COMPILE=riscv64-linux-gnu-
+$ export OPENSBI=../prototyper/target/riscv64imac-unknown-none-elf/release/rustsbi-prototyper.bin
+```
+
+生成`.config`文件
+
+``` shell
+# To generate .config file out of board configuration file
+$ make qemu-riscv64_spl_defconfig
+# add bootcmd value
+$ make menuconfig
+```
+
+编译U-Boot
+
+``` shell
+# To build U-Boot
+$ make -j$(nproc)
+```
+
+本小节将使用二进制文件 `./spl/u-boot-spl`。
+
+### 使用RustSBI原型系统和U-Boot启动Linux Kernel
+
+进入`workshop`目录
+
+``` shell
+$ cd workshop
+```
+
+运行下面命令
+
+``` shell
+$ qemu-system-riscv64 -M virt -smp 1 -m 256M -nographic \
+          -bios ./u-boot/spl/u-boot-spl \
+          -device loader,file=./prototyper/target/riscv64imac-unknown-none-elf/release/rustsbi-test-kernel.itb,addr=0x80200000
+```
+
+## 使用U-Boot SPL和U-Boot启动Test Kernel
+### 编译RustSBI  Prototyper和Test Kernel
+
+进入prototyper目录
+
+``` shell
+$ cd prototyper
+```
+
+编译RustSBI Prototyper和Test Kernel
+
+``` shell
+$ cargo make prototyper
+$ cargo make test-kernel
+```
+本小节将使用二进制文件 `./target/riscv64imac-unknown-none-elf/release/rustsbi-prototyper.bin`和`./target/riscv64imac-unknown-none-elf/release/rustsbi-test-kernel.bin`。
+
+### 编译U-Boot SPL
+
+进入U-Boot目录
+
+``` shell
+$ cd u-boot
+```
+
+导出环境变量
+
+``` shell
+$ export ARCH=riscv
+$ export CROSS_COMPILE=riscv64-linux-gnu-
+$ export OPENSBI=../prototyper/target/riscv64imac-unknown-none-elf/release/rustsbi-prototyper.bin
+```
+
+生成`.config`文件
+
+``` shell
+# To generate .config file out of board configuration file
+$ make qemu-riscv64_spl_defconfig
+# add bootcmd value
+$ make menuconfig
+```
+
+U-Boot 配置选项将加载到终端。导航到 `Boot options` $\rightarrow$ `bootcmd value` 并将以下内容写入 `bootcmd` 值:
+
+```
+ext4load virtio 0:1 84000000 rustsbi-test-kernel.bin; booti 0x84000000 - ${fdtcontroladdr}
+```
+
+编译U-Boot
+
+``` shell
+# To build U-Boot
+$ make -j$(nproc)
+```
+
+本小节将使用二进制文件 `./spl/u-boot-spl`和`./u-boot.itb `。
+
+### 创建启动盘
+在`workshop`目录运行以下命令来创建一个256 MB的磁盘镜像
+
+``` shell
+# Create a 256 MB disk image
+$ qemu-img create test-kernel.img 256m
+```
+
+#### 创建分区
+
+将在磁盘映像`test-kernel.img`上创建1个分区,这个分区是可引导的。
+
+`parted`命令将用于在镜像`test-kernel.img`中创建分区。在镜像中创建分区表:
+
+``` shell
+$ sudo parted test-kernel.img mklabel gpt
+```
+
+现在`test-kernel.img`中有一个分区表。将`test-kernel.img`挂载为loop device,以便它可以用作块设备。将`test-kernel.img`挂载为块设备将允许在其中创建分区。
+
+``` shell
+# Attach test-kernel.img with the first available loop device
+$ sudo losetup --find --show test-kernel.img
+```
+
+> - `find`:查找第一个未使用的loop device
+> - `show`:显示`test-kernel.img`附加到的loop device的名称
+
+记下循环设备的完整路径。在本教程中它是`/dev/loop0`。对`/dev/loop0`的操作将会对`test-kernel.img`进行操作。
+
+对`/dev/loop0`分区
+
+``` shell
+# Create a couple of primary partitions
+$ sudo parted --align minimal /dev/loop0 mkpart primary ext4 0 100%
+
+$ sudo parted /dev/loop0 print
+```
+
+#### 格式化分区
+
+通过以下命令查看分区:
+
+``` shell
+$ ls -l /dev/loop0*
+```
+
+在本教程中,分区为`/dev/loop0p1`。
+
+格式化分区并创建`ext4`文件系统,同时将分区设置为可引导分区。
+
+``` shell
+$ sudo mkfs.ext4 /dev/loop0p1
+
+# Mark first partition as bootable
+$ sudo parted /dev/loop0 set 1 boot on
+```
+
+#### 将Linux Kernel和根文件系统拷贝进启动盘
+
+``` shell
+# Mount the 1st partition
+$ sudo mkdir test-kernel
+$ sudo mount /dev/loop0p1 test-kernel
+$ cd test-kernel
+```
+拷贝Linux Kernel镜像
+``` shell
+$ sudo cp ../prototyper/target/riscv64imac-unknown-none-elf/release/rustsbi-test-kernel.bin .
+```
+
+卸载`test-kernel`
+
+``` shell
+$ cd workshop
+$ sudo umount test-kernel
+```
+
+将`/dev/loop0`分离
+
+``` shell
+$ sudo losetup -d /dev/loop0
+```
+
+### 使用RustSBI 原型系统和U-Boot启动Linux Kernel
+
+进入`workshop`目录
+
+``` shell
+$ cd workshop
+```
+
+运行下面命令
+
+``` shell
+$ qemu-system-riscv64 -M virt -smp 1 -m 256M -nographic \
+          -bios ./u-boot/spl/u-boot-spl \
+          -device loader,file=./u-boot/u-boot.itb,addr=0x80200000 \
+          -blockdev driver=file,filename=./test-kernel.img,node-name=hd0 \
+          -device virtio-blk-device,drive=hd0
+```

+ 101 - 0
prototyper/docs/booting-ubuntu-24.04.1-in-qemu-using-edk2-and-opensbi.md

@@ -0,0 +1,101 @@
+# 使用 OpenSBI & EDK2 在 QEMU 中启动 Ubuntu 24.04.1
+
+本教程给出了使用 OpenSBI 和 EDK II 在 QEMU 中启动 Ubuntu 24.04.1 的基本流程。
+
+请读者在其主机上安装必要的软件来尝试本教程。本教程是在 Arch Linux 上开发的,建议读者使用 x86_64 平台上的 Linux 环境按照本教程进行尝试。
+
+本教程使用软件版本如下:
+
+|         软件          |     版本     |
+| :-------------------: | :----------: |
+| riscv64-linux-gnu-gcc |    14.2.0    |
+|  qemu-system-riscv64  |     9.2.0    |
+|       OpenSBI         |      1.6     |
+|        EDK II         | stable202411 |
+
+## 准备 Opensbi,EDK II,Ubuntu 24.04.1
+创建工作目录并进入该目录
+
+``` shell
+$ mkdir workshop && cd workshop
+```
+
+### Clone Opensbi
+
+``` shell
+$ git clone -b v1.6 https://github.com/riscv/opensbi.git
+```
+
+### Clone EDK II
+
+``` shell
+$ git clone -b edk2-stable202411 --recurse-submodule [email protected]:tianocore/edk2.git
+```
+
+### 下载 Ubuntu 24.04.1 镜像文件
+
+下载链接:[Ubuntu 24.04.1](https://cdimage.ubuntu.com/releases/24.04.1/release/ubuntu-24.04.1-preinstalled-server-riscv64.img.xz)
+``` shell
+$ wget https://cdimage.ubuntu.com/releases/24.04.1/release/ubuntu-24.04.1-preinstalled-server-riscv64.img.xz
+$ xz -d ubuntu-24.04.1-preinstalled-server-riscv64.img.xz
+```
+
+- The password of the default user `ubuntu` is `ubuntu`.
+- 登录后应会被要求更改登录密码。
+- 可以通过 `sudo` 更改 root 密码。
+
+## 编译 EDK II
+
+设置环境变量
+
+``` shell
+$ export WORKSPACE=`pwd`
+$ export GCC5_RISCV64_PREFIX=riscv64-linux-gnu-
+$ export PACKAGES_PATH=$WORKSPACE/edk2
+$ export EDK_TOOLS_PATH=$WORKSPACE/edk2/BaseTools
+$ source edk2/edksetup.sh --reconfig
+```
+
+编译 BaseTools
+
+``` shell
+$ make -C edk2/BaseTools
+```
+
+编译 RiscVVirtQemu
+
+``` shell
+$ source edk2/edksetup.sh BaseTools
+$ build -a RISCV64 --buildtarget RELEASE -p OvmfPkg/RiscVVirt/RiscVVirtQemu.dsc -t GCC5
+```
+
+## 编译 OpenSBI
+
+``` shell
+$ make -C opensbi \
+    -j $(nproc) \
+    CROSS_COMPILE=riscv64-linux-gnu- \
+    PLATFORM=generic
+```
+
+## 使用 OpenSBI 和 EDK II 启动 Ubuntu 24.04.1
+
+将 RISCV_VIRT_CODE.fd 和 RISCV_VIRT_VARS.fd 填充至 32M,以适应 RISC-V QEMU pflash devices 的需求
+
+``` shell
+$ truncate -s 32M Build/RiscVVirtQemu/RELEASE_GCC5/FV/RISCV_VIRT_CODE.fd
+$ truncate -s 32M Build/RiscVVirtQemu/RELEASE_GCC5/FV/RISCV_VIRT_VARS.fd
+```
+
+启动 qemu-system-riscv64
+
+``` shell
+$ qemu-system-riscv64  \
+    -M virt,pflash0=pflash0,pflash1=pflash1,acpi=off \
+    -m 4096 -smp 2  -nographic \
+    -bios opensbi/build/platform/generic/firmware/fw_dynamic.bin \
+    -blockdev node-name=pflash0,driver=file,read-only=on,filename=Build/RiscVVirtQemu/RELEASE_GCC5/FV/RISCV_VIRT_CODE.fd  \
+    -blockdev node-name=pflash1,driver=file,filename=Build/RiscVVirtQemu/RELEASE_GCC5/FV/RISCV_VIRT_VARS.fd \
+    -device virtio-blk-device,drive=hd0  \
+    -drive file=ubuntu-24.04.1-preinstalled-server-riscv64.img,format=raw,id=hd0,if=none
+```

+ 107 - 0
prototyper/docs/booting-ubuntu-24.04.1-in-qemu-using-uboot-and-rustsbi.md

@@ -0,0 +1,107 @@
+# 使用RustSBI & U-Boot在QEMU中启动 Ubuntu 24.04.1
+
+本教程给出了使用RustSBI和U-Boot在QEMU中启动Ubuntu 24.04.1的基本流程。
+
+本教程使用软件版本如下:
+
+|         软件          |  版本   |
+| :-------------------: | :-----: |
+| riscv64-linux-gnu-gcc | 14.1.0  |
+|  qemu-system-riscv64  |  9.0.1  |
+|  RustSBI Prototyper   |  0.0.0  |
+|        U-Boot         | 2024.04 |
+
+## 准备RustSBI Prototyper, U-Boot ,Ubuntu 24.04.1
+创建工作目录并进入该目录
+
+``` shell
+$ mkdir workshop && cd workshop
+```
+
+### Clone RustSBI Prototyper
+
+``` shell
+$ git clone https://github.com/rustsbi/prototyper.git && cd prototyper && git checkout main && cd ..
+```
+
+### Clone U-Boot
+
+``` shell
+$ git clone https://github.com/u-boot/u-boot.git && cd u-boot && git checkout v2024.04 && cd ..
+```
+### 下载并扩容 Ubuntu 24.04.1 磁盘镜像文件
+
+下载链接:[Ubuntu 24.04.1](https://cdimage.ubuntu.com/releases/noble/release/ubuntu-24.04.1-preinstalled-server-riscv64.img.xz)
+```shell
+ $ unar ubuntu-24.04.1-preinstalled-server-riscv64.img.xz
+ $ qemu-img resize -f raw ubuntu-24.04.1-preinstalled-server-riscv64.img +5G
+```
+
+- The password of the default user `ubuntu` is `ubuntu`.
+- 登录后应会被要求更改登录密码。
+- 可以通过 `sudo` 更改 root 密码。
+
+
+## 编译RustSBI  Prototyper
+
+进入prototyper目录
+
+``` shell
+$ cd prototyper
+```
+
+编译RustSBI  Prototyper
+
+``` shell
+$ cargo prototyper
+```
+
+## 编译U-Boot SPL
+
+进入U-Boot目录
+
+``` shell
+$ cd u-boot
+```
+
+导出环境变量
+
+``` shell
+$ export ARCH=riscv
+$ export CROSS_COMPILE=riscv64-linux-gnu-
+$ export OPENSBI=../prototyper/target/riscv64imac-unknown-none-elf/release/rustsbi-prototyper.bin 
+```
+
+生成`.config`文件,编译U-Boot
+
+``` shell
+# To generate .config file out of board configuration file
+$ make qemu-riscv64_spl_defconfig
+$ make -j$(nproc)
+```
+
+## 使用RustSBI 原型系统和U-Boot启动 Ubuntu 24.04.1
+
+进入`workshop`目录
+
+``` shell
+$ cd workshop
+```
+
+运行下面命令
+
+``` shell
+$ qemu-system-riscv64 \
+    -nographic -machine virt \
+    -smp 4 -m 8G \
+    -bios ./u-boot/spl/u-boot-spl  \
+    -device loader,file=./u-boot/u-boot.itb,addr=0x80200000 \
+    -drive file=ubuntu-24.04.1-preinstalled-server-riscv64.img,format=raw,if=none,id=hd0 \
+    -object rng-random,filename=/dev/urandom,id=rng0 \
+    -device virtio-vga \
+    -device virtio-rng-device,rng=rng0 \
+    -device virtio-blk-device,drive=hd0 \
+    -device virtio-net-device,netdev=usernet \
+    -netdev user,id=usernet,hostfwd=tcp::12055-:22 \
+    -device qemu-xhci -usb -device usb-kbd -device usb-tablet
+```

+ 27 - 0
prototyper/docs/openwrt-patch.patch

@@ -0,0 +1,27 @@
+diff --git a/package/boot/uboot-sifiveu/patches/200-invalid-version.patch b/package/boot/uboot-sifiveu/patches/200-invalid-version.patch
+new file mode 100644
+index 0000000000..dd52b479f8
+--- /dev/null
++++ b/package/boot/uboot-sifiveu/patches/200-invalid-version.patch
+@@ -0,0 +1,11 @@
++--- a/scripts/dtc/pylibfdt/Makefile
+++++ b/scripts/dtc/pylibfdt/Makefile
++@@ -17,7 +17,7 @@
++       cmd_pymod = unset CROSS_COMPILE; unset CFLAGS; \
++ 		CC="$(HOSTCC)" LDSHARED="$(HOSTCC) -shared " \
++ 		LDFLAGS="$(HOSTLDFLAGS)" \
++-		VERSION="u-boot-$(UBOOTVERSION)" \
+++		VERSION="$(UBOOTVERSION)" \
++ 		CPPFLAGS="$(HOSTCFLAGS) -I$(LIBFDT_srcdir)" OBJDIR=$(obj) \
++ 		SOURCES="$(PYLIBFDT_srcs)" \
++ 		SWIG_OPTS="-I$(LIBFDT_srcdir) -I$(LIBFDT_srcdir)/.." \
+diff --git a/target/linux/sifiveu/base-files/etc/inittab b/target/linux/sifiveu/base-files/etc/inittab
+index 69f97c47c8..0d8ead1d91 100644
+--- a/target/linux/sifiveu/base-files/etc/inittab
++++ b/target/linux/sifiveu/base-files/etc/inittab
+@@ -1,4 +1,5 @@
+ ::sysinit:/etc/init.d/rcS S boot
+ ::shutdown:/etc/init.d/rcS K shutdown
+ ttySIF0::askfirst:/usr/libexec/login.sh
++ttyS0::askfirst:/usr/libexec/login.sh
+ tty1::askfirst:/usr/libexec/login.sh

+ 40 - 0
prototyper/prototyper/Cargo.toml

@@ -0,0 +1,40 @@
+cargo-features = ["per-package-target"]
+
+[package]
+name = "rustsbi-prototyper"
+version = "0.0.0"
+edition.workspace = true
+license.workspace = true
+repository.workspace = true
+forced-target = "riscv64imac-unknown-none-elf"
+
+[dependencies]
+aclint = "=0.1.0"
+log = "0.4"
+panic-halt = "1.0.0"
+riscv = "0.12.1"
+sifive-test-device = "0.0.0"
+spin = "0.9.8"
+uart16550 = "0.0.1"
+riscv-decode = "0.2.1"
+cfg-if = "1.0.0"
+buddy_system_allocator = "0.11.0"
+rustsbi = { version = "0.4.0", features = ["machine"] }
+sbi-spec = { version = "0.0.8", features = ["legacy"] }
+serde = { version = "1.0.202", default-features = false, features = ["derive"] }
+fast-trap = { version = "0.1.0",  features = ["riscv-m"] }
+serde-device-tree = { git = "https://github.com/rustsbi/serde-device-tree", default-features = false }
+uart_xilinx = { git = "https://github.com/duskmoon314/uart-rs/" }
+xuantie-riscv = { git= "https://github.com/rustsbi/xuantie" }
+bouffalo-hal = { git = "https://github.com/rustsbi/bouffalo-hal", rev = "968b949", features = ["bl808"] }
+
+[[bin]]
+name = "rustsbi-prototyper"
+test = false
+bench = false
+
+[features]
+nemu = []
+payload = []
+jump = []
+fdt = []

+ 82 - 0
prototyper/prototyper/build.rs

@@ -0,0 +1,82 @@
+use std::{env, path::PathBuf};
+
+fn main() {
+    let out = PathBuf::from(env::var_os("OUT_DIR").unwrap());
+    let ld = &out.join("rustsbi-prototyper.ld");
+
+    std::fs::write(ld, LINKER_SCRIPT).unwrap();
+
+    println!("cargo:rerun-if-env-changed=RUST_LOG,PROTOTYPER_FDT,PROTOTYPER_IMAGE");
+    println!("cargo:rustc-link-arg=-T{}", ld.display());
+    println!("cargo:rustc-link-search={}", out.display());
+}
+
+const LINKER_SCRIPT: &[u8] = b"OUTPUT_ARCH(riscv)
+ENTRY(_start) 
+SECTIONS {
+    . = 0x80000000;
+
+    . = ALIGN(0x1000); /* Need this to create proper sections */
+
+    sbi_start = .;
+    .text : ALIGN(0x1000) { 
+        *(.text.entry)
+        *(.text .text.*)
+    }
+
+    .rodata : ALIGN(0x1000) { 
+        sbi_rodata_start = .;
+        *(.rodata .rodata.*)
+        *(.srodata .srodata.*)
+        . = ALIGN(0x1000);  
+    } 
+
+    .dynsym : ALIGN(8) {
+        *(.dynsym)
+    }
+
+    .rela.dyn : ALIGN(8) {
+        __rel_dyn_start = .;
+        *(.rela*)
+        __rel_dyn_end = .;
+    }
+
+    sbi_rodata_end = .;
+
+	/*
+	 * PMP regions must be to be power-of-2. RX/RW will have separate
+	 * regions, so ensure that the split is power-of-2.
+	 */
+	. = ALIGN(1 << LOG2CEIL((SIZEOF(.rodata) + SIZEOF(.text)
+				+ SIZEOF(.dynsym) + SIZEOF(.rela.dyn))));
+
+    .data : ALIGN(0x1000) { 
+        sbi_data_start = .;
+        *(.data .data.*)
+        *(.sdata .sdata.*)
+        . = ALIGN(0x1000); 
+        sbi_data_end = .;
+    }
+    sidata = LOADADDR(.data);
+
+    .bss (NOLOAD) : ALIGN(0x1000) {  
+        *(.bss.stack)
+        sbi_heap_start = .;
+        *(.bss.heap)
+        sbi_heap_end = .;
+        sbi_bss_start = .;
+        *(.bss .bss.*)
+        *(.sbss .sbss.*)
+        sbi_bss_end = .;
+    } 
+    /DISCARD/ : {
+        *(.eh_frame)
+    }
+
+	. = ALIGN(0x1000); /* Need this to create proper sections */
+    sbi_end = .;
+
+    .text 0x80200000 : ALIGN(0x1000) {
+        *(.payload)
+    }
+}";

+ 16 - 0
prototyper/prototyper/src/cfg.rs

@@ -0,0 +1,16 @@
+/// The address where the SBI link start.
+pub const SBI_LINK_START_ADDRESS: usize = 0x80000000;
+/// Maximum number of supported harts.
+pub const NUM_HART_MAX: usize = 8;
+/// Stack size per hart (hardware thread) in bytes.
+pub const LEN_STACK_PER_HART: usize = 16 * 1024;
+/// Heap Size of SBI firmware.
+pub const HEAP_SIZE: usize = 32 * 1024;
+/// Platform page size.
+pub const PAGE_SIZE: usize = 4096;
+/// TLB_FLUSH_LIMIT defines the TLB refresh range limit.
+/// If the TLB refresh range is greater than TLB_FLUSH_LIMIT, the entire TLB is refreshed.
+pub const TLB_FLUSH_LIMIT: usize = 4 * PAGE_SIZE;
+
+#[cfg(feature = "jump")]
+pub const JUMP_ADDRESS: usize = 0x50000000;

+ 92 - 0
prototyper/prototyper/src/devicetree.rs

@@ -0,0 +1,92 @@
+use serde::Deserialize;
+use serde_device_tree::{
+    Dtb, DtbPtr,
+    buildin::{Node, NodeSeq, Reg, StrSeq},
+};
+
+use core::ops::Range;
+
+/// Root device tree structure containing system information.
+#[derive(Deserialize)]
+pub struct Tree<'a> {
+    /// Optional model name string.
+    pub model: Option<StrSeq<'a>>,
+    /// Memory information.
+    pub memory: NodeSeq<'a>,
+    /// CPU information.
+    pub cpus: Cpus<'a>,
+}
+
+/// CPU information container.
+#[derive(Deserialize)]
+#[serde(rename_all = "kebab-case")]
+pub struct Cpus<'a> {
+    /// Sequence of CPU nodes.
+    pub cpu: NodeSeq<'a>,
+}
+
+/// Individual CPU node information.
+#[derive(Deserialize, Debug)]
+pub struct Cpu<'a> {
+    /// RISC-V ISA extensions supported by this CPU.
+    #[serde(rename = "riscv,isa-extensions")]
+    pub isa_extensions: Option<StrSeq<'a>>,
+    #[serde(rename = "riscv,isa")]
+    pub isa: Option<StrSeq<'a>>,
+    /// CPU register information.
+    pub reg: Reg<'a>,
+}
+
+/// Generic device node information.
+#[allow(unused)]
+#[derive(Deserialize, Debug)]
+pub struct Device<'a> {
+    /// Device register information.
+    pub reg: Reg<'a>,
+}
+
+/// Memory range.
+#[derive(Deserialize)]
+#[serde(rename_all = "kebab-case")]
+pub struct Memory<'a> {
+    pub reg: Reg<'a>,
+}
+
+/// Errors that can occur during device tree parsing.
+pub enum ParseDeviceTreeError {
+    /// Invalid device tree format.
+    Format,
+}
+
+pub fn parse_device_tree(opaque: usize) -> Result<Dtb, ParseDeviceTreeError> {
+    let Ok(ptr) = DtbPtr::from_raw(opaque as *mut _) else {
+        return Err(ParseDeviceTreeError::Format);
+    };
+    let dtb = Dtb::from(ptr);
+    Ok(dtb)
+}
+
+pub fn get_compatible_and_range<'de>(node: &Node) -> Option<(StrSeq<'de>, Range<usize>)> {
+    let compatible = node
+        .get_prop("compatible")
+        .map(|prop_item| prop_item.deserialize::<StrSeq<'de>>());
+    let regs = node
+        .get_prop("reg")
+        .map(|prop_item| {
+            let reg = prop_item.deserialize::<serde_device_tree::buildin::Reg>();
+            if let Some(range) = reg.iter().next() {
+                return Some(range);
+            }
+            None
+        })
+        .map_or_else(|| None, |v| v);
+    if let Some(compatible) = compatible {
+        if let Some(regs) = regs {
+            Some((compatible, regs.0))
+        } else {
+            None
+        }
+    } else {
+        None
+    }
+}

+ 125 - 0
prototyper/prototyper/src/fail.rs

@@ -0,0 +1,125 @@
+use crate::riscv::current_hartid;
+use serde_device_tree::Dtb;
+
+use crate::devicetree;
+
+use riscv::interrupt::machine::{Exception, Interrupt};
+use riscv::register::{mcause::Trap, mepc, mtval};
+
+#[cfg(all(feature = "payload", feature = "jump"))]
+compile_error!("feature \"payload\" and feature \"jump\" cannot be enabled at the same time");
+
+#[panic_handler]
+fn panic(info: &core::panic::PanicInfo) -> ! {
+    use ::riscv::register::*;
+    error!("Hart {} {info}", current_hartid());
+    error!("-----------------------------");
+    error!("mcause:  {:?}", mcause::read().cause());
+    error!("mepc:    {:#018x}", mepc::read());
+    error!("mtval:   {:#018x}", mtval::read());
+    error!("-----------------------------");
+    error!("System shutdown scheduled due to RustSBI panic");
+    loop {}
+}
+
+pub fn unsupported_trap(trap: Option<Trap<Interrupt, Exception>>) -> ! {
+    error!("-----------------------------");
+    error!("trap:    {trap:?}");
+    error!("mepc:    {:#018x}", mepc::read());
+    error!("mtval:   {:#018x}", mtval::read());
+    error!("-----------------------------");
+    panic!("Stopped with unsupported trap")
+}
+
+/// Handles device tree format parsing errors by logging and resetting.
+#[cold]
+pub fn device_tree_format(_err: devicetree::ParseDeviceTreeError) -> Dtb {
+    loop {
+        core::hint::spin_loop()
+    }
+}
+
+#[cold]
+pub fn device_tree_deserialize_root<'a>(
+    _err: serde_device_tree::error::Error,
+) -> serde_device_tree::buildin::Node<'a> {
+    loop {
+        core::hint::spin_loop()
+    }
+}
+
+cfg_if::cfg_if! {
+    if #[cfg(feature = "payload")] {
+    } else if #[cfg(feature = "jump")] {
+    } else {
+        use crate::firmware::dynamic;
+        use crate::sbi::reset;
+        use riscv::register::mstatus;
+        /// Handles invalid dynamic information data by logging details and resetting.
+        #[cold]
+        pub fn invalid_dynamic_data(err: dynamic::DynamicError) -> (mstatus::MPP, usize) {
+            error!("Invalid data in dynamic information:");
+            if err.invalid_mpp {
+                error!("* dynamic information contains invalid privilege mode");
+            }
+            if err.invalid_next_addr {
+                error!("* dynamic information contains invalid next jump address");
+            }
+            let explain_next_mode = match err.bad_info.next_mode {
+                3 => "Machine",
+                1 => "Supervisor",
+                0 => "User",
+                _ => "Invalid",
+            };
+            error!(
+                "@ help: dynamic information contains magic value 0x{:x}, version {}, next jump address 0x{:x}, next privilege mode {} ({}), options {:x}, boot hart ID {}",
+                err.bad_info.magic, err.bad_info.version, err.bad_info.next_addr, err.bad_info.next_mode, explain_next_mode, err.bad_info.options, err.bad_info.boot_hart
+            );
+            reset::fail()
+        }
+
+        /// Handles case where dynamic information is not available by logging details and resetting.
+        #[cold]
+        pub fn no_dynamic_info_available(err: dynamic::DynamicReadError) -> dynamic::DynamicInfo {
+            if let Some(bad_paddr) = err.bad_paddr {
+                error!(
+                    "No dynamic information available at address 0x{:x}",
+                    bad_paddr
+                );
+            } else {
+                error!("No valid dynamic information available:");
+                if let Some(bad_magic) = err.bad_magic {
+                    error!(
+                        "* tried to identify dynamic information, but found invalid magic number 0x{:x}",
+                        bad_magic
+                    );
+                }
+                if let Some(bad_version) = err.bad_version {
+                    error!("* tries to identify version of dynamic information, but the version number {} is not supported", bad_version);
+                }
+                if err.bad_magic.is_none() {
+                    error!("@ help: magic number is valid")
+                }
+                if err.bad_version.is_none() {
+                    error!("@ help: dynamic information version is valid")
+                }
+            }
+            reset::fail()
+        }
+
+        /// Fallback function that returns default dynamic info with boot_hart set to MAX.
+        ///
+        /// Used when dynamic info read fails but execution should continue.
+        #[cold]
+        pub fn use_lottery(_err: dynamic::DynamicReadError) -> dynamic::DynamicInfo {
+            dynamic::DynamicInfo {
+                magic: 0,
+                version: 0,
+                next_addr: 0,
+                next_mode: 0,
+                options: 0,
+                boot_hart: usize::MAX,
+            }
+        }
+    }
+}

+ 147 - 0
prototyper/prototyper/src/firmware/dynamic.rs

@@ -0,0 +1,147 @@
+//! Frequently used first boot stage dynamic information on RISC-V.
+
+use core::ops::Range;
+use core::sync::atomic::{AtomicBool, Ordering};
+
+use super::BootInfo;
+use crate::fail;
+use crate::riscv::current_hartid;
+
+use riscv::register::mstatus;
+
+/// Determine whether the current hart is boot hart.
+///
+/// Return true if the current hart is boot hart.
+pub fn is_boot_hart(nonstandard_a2: usize) -> bool {
+    // Track whether this is the first hart to boot
+    static GENESIS: AtomicBool = AtomicBool::new(true);
+
+    let info = read_paddr(nonstandard_a2).unwrap_or_else(fail::use_lottery);
+
+    // Determine if this is the boot hart based on hart ID
+    if info.boot_hart == usize::MAX {
+        // If boot_hart is MAX, use atomic bool to determine first hart
+        GENESIS.swap(false, Ordering::AcqRel)
+    } else {
+        // Otherwise check if current hart matches designated boot hart
+        current_hartid() == info.boot_hart
+    }
+}
+
+/// Gets boot information from nonstandard_a2 parameter.
+///
+/// Returns BootInfo containing next stage address and privilege mode.
+pub fn get_boot_info(nonstandard_a2: usize) -> BootInfo {
+    let dynamic_info = read_paddr(nonstandard_a2).unwrap_or_else(fail::no_dynamic_info_available);
+    let (mpp, next_addr) = mpp_next_addr(&dynamic_info).unwrap_or_else(fail::invalid_dynamic_data);
+    BootInfo {
+        next_address: next_addr,
+        mpp,
+    }
+}
+
+/// M-mode firmware dynamic information.
+#[derive(Clone, Copy)]
+#[repr(C)]
+pub struct DynamicInfo {
+    /// Dynamic information magic value.
+    pub magic: usize,
+    /// Version of dynamic information.
+    pub version: usize,
+    /// Address of the next boot-loading stage.
+    pub next_addr: usize,
+    /// RISC-V privilege mode of the next boot-loading stage.
+    pub next_mode: usize,
+    /// M-mode firmware options; its definition varies between SBI implementations.
+    pub options: usize,
+    /// Boot hart ID of current environment.
+    pub boot_hart: usize,
+}
+
+// Definition of `boot_hart` can be found at:
+// https://github.com/riscv-software-src/opensbi/blob/019a8e69a1dc0c0f011fabd0372e1ba80e40dd7c/include/sbi/fw_dynamic.h#L75
+
+const DYNAMIC_INFO_INVALID_ADDRESSES: usize = 0x00000000;
+const NEXT_ADDR_VALID_ADDRESSES: Range<usize> = 0x80000000..0x90000000;
+pub(crate) const MAGIC: usize = 0x4942534f;
+const SUPPORTED_VERSION: Range<usize> = 0..3;
+
+/// Error type for dynamic info read failures.
+pub struct DynamicReadError {
+    pub bad_paddr: Option<usize>,
+    pub bad_magic: Option<usize>,
+    pub bad_version: Option<usize>,
+}
+
+// TODO: unconstrained lifetime
+/// Reads dynamic info from physical address.
+///
+/// Returns Result containing DynamicInfo or error details.
+pub fn read_paddr(paddr: usize) -> Result<DynamicInfo, DynamicReadError> {
+    let mut error = DynamicReadError {
+        bad_paddr: None,
+        bad_magic: None,
+        bad_version: None,
+    };
+    // check pointer before dereference.
+    if DYNAMIC_INFO_INVALID_ADDRESSES == paddr {
+        error.bad_paddr = Some(paddr);
+        return Err(error);
+    }
+    let ans = unsafe { *(paddr as *const DynamicInfo) };
+
+    // Validate magic number and version.
+    if ans.magic != MAGIC {
+        error.bad_magic = Some(ans.magic);
+    }
+    if !SUPPORTED_VERSION.contains(&ans.version) {
+        error.bad_version = Some(ans.version);
+    }
+    if error.bad_magic.is_some() || error.bad_version.is_some() {
+        return Err(error);
+    }
+    Ok(ans)
+}
+
+/// Error type for dynamic info validation failures.
+pub struct DynamicError<'a> {
+    pub invalid_mpp: bool,
+    pub invalid_next_addr: bool,
+    pub bad_info: &'a DynamicInfo,
+}
+
+/// Validates and extracts privilege mode and next address from dynamic info.
+///
+/// Returns Result containing tuple of (MPP, next_addr) or error details.
+pub fn mpp_next_addr(info: &DynamicInfo) -> Result<(mstatus::MPP, usize), DynamicError> {
+    let mut error = DynamicError {
+        invalid_mpp: false,
+        invalid_next_addr: false,
+        bad_info: info,
+    };
+
+    // fail safe, errors will be aggregated after whole checking process.
+    let next_addr_valid = NEXT_ADDR_VALID_ADDRESSES.contains(&info.next_addr);
+    let mpp_valid = matches!(info.next_mode, 0 | 1 | 3);
+
+    if !next_addr_valid {
+        error.invalid_next_addr = true;
+    }
+    if !mpp_valid {
+        error.invalid_mpp = true;
+    }
+
+    if !next_addr_valid || !mpp_valid {
+        return Err(error);
+    }
+
+    let mpp = match info.next_mode {
+        3 => mstatus::MPP::Machine,
+        1 => mstatus::MPP::Supervisor,
+        // pattern `_` avoids `unreachable!`` which introduces panic handler.
+        // pattern 0 and _
+        _ => mstatus::MPP::User,
+    };
+
+    Ok((mpp, info.next_addr))
+}

+ 20 - 0
prototyper/prototyper/src/firmware/jump.rs

@@ -0,0 +1,20 @@
+use core::sync::atomic::{AtomicBool, Ordering};
+use riscv::register::mstatus;
+
+use super::BootInfo;
+use crate::cfg::JUMP_ADDRESS;
+
+/// Determine whether the current hart is boot hart.
+///
+/// Return true if the current hart is boot hart.
+pub fn is_boot_hart(_nonstandard_a2: usize) -> bool {
+    static GENESIS: AtomicBool = AtomicBool::new(true);
+    GENESIS.swap(false, Ordering::AcqRel)
+}
+
+pub fn get_boot_info(_nonstandard_a2: usize) -> BootInfo {
+    BootInfo {
+        next_address: JUMP_ADDRESS,
+        mpp: mstatus::MPP::Supervisor,
+    }
+}

+ 133 - 0
prototyper/prototyper/src/firmware/mod.rs

@@ -0,0 +1,133 @@
+cfg_if::cfg_if! {
+    if #[cfg(feature = "payload")] {
+        pub mod payload;
+        pub use payload::{get_boot_info, is_boot_hart};
+    } else if #[cfg(feature = "jump")] {
+        pub mod jump;
+        pub use jump::{get_boot_info, is_boot_hart};
+    } else {
+        pub mod dynamic;
+        pub use dynamic::{get_boot_info, is_boot_hart};
+    }
+}
+
+#[allow(unused)]
+use core::arch::{asm, naked_asm};
+use core::ops::Range;
+use riscv::register::mstatus;
+
+pub struct BootInfo {
+    pub next_address: usize,
+    pub mpp: mstatus::MPP,
+}
+
+pub struct BootHart {
+    pub fdt_address: usize,
+    pub is_boot_hart: bool,
+}
+
+#[naked]
+#[unsafe(link_section = ".rodata.fdt")]
+#[repr(align(16))]
+#[cfg(feature = "fdt")]
+pub extern "C" fn raw_fdt() {
+    unsafe { naked_asm!(concat!(".incbin \"", env!("PROTOTYPER_FDT_PATH"), "\""),) }
+}
+
+#[inline]
+#[cfg(feature = "fdt")]
+fn get_fdt_address() -> usize {
+    raw_fdt as usize
+}
+
+/// Gets boot hart information based on opaque and nonstandard_a2 parameters.
+///
+/// Returns a BootHart struct containing FDT address and whether this is the boot hart.
+#[allow(unused_mut, unused_assignments)]
+pub fn get_boot_hart(opaque: usize, nonstandard_a2: usize) -> BootHart {
+    let is_boot_hart = is_boot_hart(nonstandard_a2);
+
+    let mut fdt_address = opaque;
+
+    #[cfg(feature = "fdt")]
+    {
+        fdt_address = get_fdt_address();
+    }
+
+    BootHart {
+        fdt_address,
+        is_boot_hart,
+    }
+}
+
+static mut SBI_START_ADDRESS: usize = 0;
+static mut SBI_END_ADDRESS: usize = 0;
+static mut RODATA_START_ADDRESS: usize = 0;
+static mut RODATA_END_ADDRESS: usize = 0;
+
+pub fn set_pmp(memory_range: &Range<usize>) {
+    unsafe {
+        // [0..memory_range.start] RW
+        // [memory_range.start..sbi_start] RWX
+        // [sbi_start..sbi_rodata_start] NONE
+        // [sbi_rodata_start..sbi_rodata_end] NONE
+        // [sbi_rodata_end..sbi_end] NONE
+        // [sbi_end..memory_range.end] RWX
+        // [memory_range.end..INF] RW
+        use riscv::register::*;
+
+        asm!("la {}, sbi_start", out(reg) SBI_START_ADDRESS, options(nomem));
+        asm!("la {}, sbi_end", out(reg) SBI_END_ADDRESS, options(nomem));
+        asm!("la {}, sbi_rodata_start", out(reg) RODATA_START_ADDRESS, options(nomem));
+        asm!("la {}, sbi_rodata_end", out(reg) RODATA_END_ADDRESS, options(nomem));
+
+        pmpcfg0::set_pmp(0, Range::OFF, Permission::NONE, false);
+        pmpaddr0::write(0);
+        pmpcfg0::set_pmp(1, Range::TOR, Permission::RW, false);
+        pmpaddr1::write(memory_range.start >> 2);
+        pmpcfg0::set_pmp(2, Range::TOR, Permission::RWX, false);
+        pmpaddr2::write(SBI_START_ADDRESS >> 2);
+        pmpcfg0::set_pmp(3, Range::TOR, Permission::NONE, false);
+        pmpaddr3::write(RODATA_START_ADDRESS >> 2);
+        pmpcfg0::set_pmp(4, Range::TOR, Permission::RW, false);
+        pmpaddr4::write(RODATA_END_ADDRESS >> 2);
+        pmpcfg0::set_pmp(5, Range::TOR, Permission::NONE, false);
+        pmpaddr5::write(SBI_END_ADDRESS >> 2);
+        pmpcfg0::set_pmp(6, Range::TOR, Permission::RWX, false);
+        pmpaddr6::write(memory_range.end >> 2);
+        pmpcfg0::set_pmp(7, Range::TOR, Permission::RW, false);
+        pmpaddr7::write(usize::MAX >> 2);
+    }
+}
+
+pub fn log_pmp_cfg(memory_range: &Range<usize>) {
+    unsafe {
+        info!("PMP Configuration");
+
+        info!(
+            "{:<10} {:<10} {:<15} {:<30}",
+            "PMP", "Range", "Permission", "Address"
+        );
+
+        info!("{:<10} {:<10} {:<15} 0x{:08x}", "PMP 0:", "OFF", "NONE", 0);
+        info!(
+            "{:<10} {:<10} {:<15} 0x{:08x} - 0x{:08x}",
+            "PMP 1-2:", "TOR", "RW/RWX", memory_range.start, SBI_START_ADDRESS
+        );
+        info!(
+            "{:<10} {:<10} {:<15} 0x{:08x} - 0x{:08x} - 0x{:08x}",
+            "PMP 3-5:", "TOR", "NONE/RW", RODATA_START_ADDRESS, RODATA_END_ADDRESS, SBI_END_ADDRESS
+        );
+        info!(
+            "{:<10} {:<10} {:<15} 0x{:08x}",
+            "PMP 6:", "TOR", "RWX", memory_range.end
+        );
+        info!(
+            "{:<10} {:<10} {:<15} 0x{:08x}",
+            "PMP 7:",
+            "TOR",
+            "RW",
+            usize::MAX
+        );
+    }
+}

+ 31 - 0
prototyper/prototyper/src/firmware/payload.rs

@@ -0,0 +1,31 @@
+use core::arch::naked_asm;
+use core::sync::atomic::{AtomicBool, Ordering};
+use riscv::register::mstatus;
+
+use super::BootInfo;
+
+/// Determine whether the current hart is boot hart.
+///
+/// Return true if the current hart is boot hart.
+pub fn is_boot_hart(_nonstandard_a2: usize) -> bool {
+    static GENESIS: AtomicBool = AtomicBool::new(true);
+    GENESIS.swap(false, Ordering::AcqRel)
+}
+
+pub fn get_boot_info(_nonstandard_a2: usize) -> BootInfo {
+    BootInfo {
+        next_address: get_image_address(),
+        mpp: mstatus::MPP::Supervisor,
+    }
+}
+
+#[naked]
+#[unsafe(link_section = ".payload")]
+pub extern "C" fn payload_image() {
+    unsafe { naked_asm!(concat!(".incbin \"", env!("PROTOTYPER_PAYLOAD_PATH"), "\""),) }
+}
+
+#[inline]
+fn get_image_address() -> usize {
+    payload_image as usize
+}

+ 49 - 0
prototyper/prototyper/src/macros.rs

@@ -0,0 +1,49 @@
+#[allow(unused)]
+macro_rules! print {
+    ($($arg:tt)*) => {
+        use core::fmt::Write;
+        if unsafe {$crate::platform::PLATFORM.have_console()} {
+            let console = unsafe { $crate::platform::PLATFORM.sbi.console.as_mut().unwrap() };
+            console.write_fmt(core::format_args!($($arg)*)).unwrap();
+            drop(console);
+        }
+    }
+}
+
+#[allow(unused)]
+macro_rules! println {
+    () => ($crate::print!("\n\r"));
+    ($($arg:tt)*) => {{
+        use core::fmt::Write;
+        if unsafe {$crate::platform::PLATFORM.have_console()} {
+            let console = unsafe { $crate::platform::PLATFORM.sbi.console.as_mut().unwrap() };
+            console.write_fmt(core::format_args!($($arg)*)).unwrap();
+            console.write_str("\n\r").unwrap();
+        }
+    }}
+}
+
+#[allow(unused)]
+macro_rules! has_csr {
+    ($($x: expr)*) => {{
+            use core::arch::asm;
+            use riscv::register::mtvec;
+            use crate::sbi::early_trap::expected_trap;
+            let res: usize;
+            unsafe {
+                // Backup old mtvec
+                let mtvec = mtvec::read().bits();
+                // Write expected_trap
+                mtvec::write(expected_trap as _, mtvec::TrapMode::Direct);
+                asm!("addi a0, zero, 0",
+                    "addi a1, zero, 0",
+                    "csrr a2, {}",
+                    "mv {}, a0",
+                    const $($x)*,
+                    out(reg) res,
+                    options(nomem));
+                asm!("csrw mtvec, {}", in(reg) mtvec);
+            }
+            res == 0
+    }};
+}

+ 210 - 0
prototyper/prototyper/src/main.rs

@@ -0,0 +1,210 @@
+#![feature(alloc_error_handler)]
+#![feature(naked_functions)]
+#![feature(fn_align)]
+#![no_std]
+#![no_main]
+#![allow(static_mut_refs)]
+
+extern crate alloc;
+#[macro_use]
+extern crate log;
+#[macro_use]
+mod macros;
+
+mod cfg;
+mod devicetree;
+mod fail;
+mod firmware;
+mod platform;
+mod riscv;
+mod sbi;
+
+use core::arch::{asm, naked_asm};
+
+use crate::platform::PLATFORM;
+use crate::riscv::csr::menvcfg;
+use crate::riscv::current_hartid;
+use crate::sbi::extensions::{
+    Extension, PrivilegedVersion, hart_extension_probe, hart_privileged_version,
+    privileged_version_detection,
+};
+use crate::sbi::hart_context::NextStage;
+use crate::sbi::heap::sbi_heap_init;
+use crate::sbi::hsm::local_remote_hsm;
+use crate::sbi::ipi;
+use crate::sbi::trap;
+use crate::sbi::trap_stack;
+
+pub const R_RISCV_RELATIVE: usize = 3;
+
+#[unsafe(no_mangle)]
+extern "C" fn rust_main(_hart_id: usize, opaque: usize, nonstandard_a2: usize) {
+    // Track whether SBI is initialized and ready.
+
+    let boot_hart_info = firmware::get_boot_hart(opaque, nonstandard_a2);
+    // boot hart task entry.
+    if boot_hart_info.is_boot_hart {
+        // Initialize the sbi heap
+        sbi_heap_init();
+
+        // parse the device tree
+        let fdt_address = boot_hart_info.fdt_address;
+
+        unsafe {
+            PLATFORM.init(fdt_address);
+            PLATFORM.print_board_info();
+        }
+
+        firmware::set_pmp(unsafe { PLATFORM.info.memory_range.as_ref().unwrap() });
+        firmware::log_pmp_cfg(unsafe { PLATFORM.info.memory_range.as_ref().unwrap() });
+
+        // Get boot information and prepare for kernel entry.
+        let boot_info = firmware::get_boot_info(nonstandard_a2);
+        let (mpp, next_addr) = (boot_info.mpp, boot_info.next_address);
+
+        // Log boot hart ID and PMP information
+        let hart_id = current_hartid();
+        info!("{:<30}: {}", "Boot HART ID", hart_id);
+
+        // Detection Priv Version
+        privileged_version_detection();
+        let priv_version = hart_privileged_version(hart_id);
+        info!("{:<30}: {:?}", "Boot HART Privileged Version", priv_version);
+
+        // Start kernel.
+        local_remote_hsm().start(NextStage {
+            start_addr: next_addr,
+            next_mode: mpp,
+            opaque: fdt_address,
+        });
+
+        info!(
+            "Redirecting hart {} to 0x{:0>16x} in {:?} mode.",
+            current_hartid(),
+            next_addr,
+            mpp
+        );
+    } else {
+        // Other harts task entry.
+        trap_stack::prepare_for_trap();
+
+        // Wait for boot hart to complete SBI initialization.
+        while !unsafe { PLATFORM.ready() } {
+            core::hint::spin_loop()
+        }
+
+        firmware::set_pmp(unsafe { PLATFORM.info.memory_range.as_ref().unwrap() });
+        // Detection Priv Version
+        privileged_version_detection();
+    }
+    // Clear all pending IPIs.
+    ipi::clear_all();
+
+    // Configure CSRs and trap handling.
+    unsafe {
+        // Delegate all interrupts and exceptions to supervisor mode.
+        asm!("csrw mideleg,    {}", in(reg) !0);
+        asm!("csrw medeleg,    {}", in(reg) !0);
+        asm!("csrw mcounteren, {}", in(reg) !0);
+        asm!("csrw scounteren, {}", in(reg) !0);
+        use ::riscv::register::{medeleg, mtvec};
+        // Keep supervisor environment calls and illegal instructions in M-mode.
+        medeleg::clear_supervisor_env_call();
+        medeleg::clear_illegal_instruction();
+        if hart_privileged_version(current_hartid()) >= PrivilegedVersion::Version1_12 {
+            // Configure environment features based on available extensions.
+            if hart_extension_probe(current_hartid(), Extension::Sstc) {
+                menvcfg::set_bits(
+                    menvcfg::STCE | menvcfg::CBIE_INVALIDATE | menvcfg::CBCFE | menvcfg::CBZE,
+                );
+            } else {
+                menvcfg::set_bits(menvcfg::CBIE_INVALIDATE | menvcfg::CBCFE | menvcfg::CBZE);
+            }
+        }
+        // Set up trap handling.
+        mtvec::write(fast_trap::trap_entry as _, mtvec::TrapMode::Direct);
+    }
+}
+
+#[naked]
+#[unsafe(link_section = ".text.entry")]
+#[unsafe(export_name = "_start")]
+unsafe extern "C" fn start() -> ! {
+    unsafe {
+        naked_asm!(
+            ".option arch, +a",
+            // 1. Turn off interrupt.
+            "   csrw    mie, zero",
+            // 2. Initialize programming language runtime.
+            // only clear bss if hartid matches preferred boot hart id.
+            "   csrr    t0, mhartid",
+            "   bne     t0, zero, 4f",
+            "   call    {relocation_update}",
+            "1:",
+            // 3. Hart 0 clear bss segment.
+            "   lla     t0, sbi_bss_start
+            lla     t1, sbi_bss_end
+         2: bgeu    t0, t1, 3f
+            sd      zero, 0(t0)
+            addi    t0, t0, 8
+            j       2b",
+            "3: ", // Hart 0 set bss ready signal.
+            "   lla     t0, 6f
+            li      t1, 1
+            amoadd.w t0, t1, 0(t0)
+            j       5f",
+            "4:", // Other harts are waiting for bss ready signal.
+            "   li      t1, 1
+            lla     t0, 6f
+            lw      t0, 0(t0)
+            bne     t0, t1, 4b",
+            "5:",
+             // 4. Prepare stack for each hart.
+            "   call    {locate_stack}",
+            "   call    {main}",
+            "   csrw    mscratch, sp",
+            "   j       {hart_boot}",
+            "  .balign  4",
+            "6:",  // bss ready signal.
+            "  .word    0",
+            relocation_update = sym relocation_update,
+            locate_stack = sym trap_stack::locate,
+            main         = sym rust_main,
+            hart_boot    = sym trap::boot::boot,
+        )
+    }
+}
+
+// Handle relocations for position-independent code
+#[naked]
+unsafe extern "C" fn relocation_update() {
+    unsafe {
+        naked_asm!(
+            // Get load offset.
+            "   li t0, {START_ADDRESS}",
+            "   lla t1, sbi_start",
+            "   sub t2, t1, t0",
+
+            // Foreach rela.dyn and update relocation.
+            "   lla t0, __rel_dyn_start",
+            "   lla t1, __rel_dyn_end",
+            "   li  t3, {R_RISCV_RELATIVE}",
+            "1:",
+            "   ld  t4, 8(t0)",
+            "   bne t4, t3, 2f",
+            "   ld t4, 0(t0)", // Get offset
+            "   ld t5, 16(t0)", // Get append
+            "   add t4, t4, t2", // Add load offset to offset add append
+            "   add t5, t5, t2",
+            "   sd t5, 0(t4)", // Update address
+            "   addi t0, t0, 24", // Get next rela item
+            "2:",
+            "   blt t0, t1, 1b",
+
+            // Return
+            "   ret",
+            R_RISCV_RELATIVE = const R_RISCV_RELATIVE,
+            START_ADDRESS = const cfg::SBI_LINK_START_ADDRESS,
+        )
+    }
+}

+ 122 - 0
prototyper/prototyper/src/platform/clint.rs

@@ -0,0 +1,122 @@
+use aclint::SifiveClint;
+use core::arch::asm;
+use xuantie_riscv::peripheral::clint::THeadClint;
+
+use crate::sbi::ipi::IpiDevice;
+pub(crate) const SIFIVE_CLINT_COMPATIBLE: [&str; 1] = ["riscv,clint0"];
+pub(crate) const THEAD_CLINT_COMPATIBLE: [&str; 1] = ["thead,c900-clint"];
+
+#[doc(hidden)]
+#[allow(unused)]
+#[derive(Clone, Copy, Debug)]
+pub enum MachineClintType {
+    SiFiveClint,
+    TheadClint,
+}
+
+/// For SiFive Clint
+pub struct SifiveClintWrap {
+    inner: *const SifiveClint,
+}
+
+impl SifiveClintWrap {
+    pub fn new(base: usize) -> Self {
+        Self {
+            inner: base as *const SifiveClint,
+        }
+    }
+}
+
+impl IpiDevice for SifiveClintWrap {
+    #[inline(always)]
+    fn read_mtime(&self) -> u64 {
+        unsafe { (*self.inner).read_mtime() }
+    }
+
+    #[inline(always)]
+    fn write_mtime(&self, val: u64) {
+        unsafe { (*self.inner).write_mtime(val) }
+    }
+
+    #[inline(always)]
+    fn read_mtimecmp(&self, hart_idx: usize) -> u64 {
+        unsafe { (*self.inner).read_mtimecmp(hart_idx) }
+    }
+
+    #[inline(always)]
+    fn write_mtimecmp(&self, hart_idx: usize, val: u64) {
+        unsafe { (*self.inner).write_mtimecmp(hart_idx, val) }
+    }
+
+    #[inline(always)]
+    fn read_msip(&self, hart_idx: usize) -> bool {
+        unsafe { (*self.inner).read_msip(hart_idx) }
+    }
+
+    #[inline(always)]
+    fn set_msip(&self, hart_idx: usize) {
+        unsafe { (*self.inner).set_msip(hart_idx) }
+    }
+
+    #[inline(always)]
+    fn clear_msip(&self, hart_idx: usize) {
+        unsafe { (*self.inner).clear_msip(hart_idx) }
+    }
+}
+
+/// For T-Head Clint
+pub struct THeadClintWrap {
+    inner: *const THeadClint,
+}
+
+impl THeadClintWrap {
+    pub fn new(base: usize) -> Self {
+        Self {
+            inner: base as *const THeadClint,
+        }
+    }
+}
+
+impl IpiDevice for THeadClintWrap {
+    #[inline(always)]
+    fn read_mtime(&self) -> u64 {
+        unsafe {
+            let mut mtime: u64 = 0;
+            asm!(
+                "rdtime {}",
+                inout(reg) mtime,
+            );
+            mtime
+        }
+    }
+
+    #[inline(always)]
+    fn write_mtime(&self, _val: u64) {
+        unimplemented!()
+    }
+
+    #[inline(always)]
+    fn read_mtimecmp(&self, hart_idx: usize) -> u64 {
+        unsafe { (*self.inner).read_mtimecmp(hart_idx) }
+    }
+
+    #[inline(always)]
+    fn write_mtimecmp(&self, hart_idx: usize, val: u64) {
+        unsafe { (*self.inner).write_mtimecmp(hart_idx, val) }
+    }
+
+    #[inline(always)]
+    fn read_msip(&self, hart_idx: usize) -> bool {
+        unsafe { (*self.inner).read_msip(hart_idx) }
+    }
+
+    #[inline(always)]
+    fn set_msip(&self, hart_idx: usize) {
+        unsafe { (*self.inner).set_msip(hart_idx) }
+    }
+
+    #[inline(always)]
+    fn clear_msip(&self, hart_idx: usize) {
+        unsafe { (*self.inner).clear_msip(hart_idx) }
+    }
+}

+ 98 - 0
prototyper/prototyper/src/platform/console.rs

@@ -0,0 +1,98 @@
+use bouffalo_hal::uart::RegisterBlock as BflbUartRegisterBlock;
+use uart_xilinx::MmioUartAxiLite;
+use uart16550::{Register, Uart16550};
+
+use crate::sbi::console::ConsoleDevice;
+pub(crate) const UART16650U8_COMPATIBLE: [&str; 1] = ["ns16550a"];
+pub(crate) const UART16650U32_COMPATIBLE: [&str; 1] = ["snps,dw-apb-uart"];
+pub(crate) const UARTAXILITE_COMPATIBLE: [&str; 1] = ["xlnx,xps-uartlite-1.00.a"];
+pub(crate) const UARTBFLB_COMPATIBLE: [&str; 1] = ["bflb,bl808-uart"];
+
+#[doc(hidden)]
+#[allow(unused)]
+#[derive(Clone, Copy, Debug)]
+pub enum MachineConsoleType {
+    Uart16550U8,
+    Uart16550U32,
+    UartAxiLite,
+    UartBflb,
+}
+
+/// For Uart 16550
+pub struct Uart16550Wrap<R: Register> {
+    inner: *const Uart16550<R>,
+}
+
+impl<R: Register> Uart16550Wrap<R> {
+    pub fn new(base: usize) -> Self {
+        Self {
+            inner: base as *const Uart16550<R>,
+        }
+    }
+}
+
+impl<R: Register> ConsoleDevice for Uart16550Wrap<R> {
+    fn read(&self, buf: &mut [u8]) -> usize {
+        unsafe { (*self.inner).read(buf) }
+    }
+
+    fn write(&self, buf: &[u8]) -> usize {
+        unsafe { (*self.inner).write(buf) }
+    }
+}
+
+/// For Uart AxiLite
+impl ConsoleDevice for MmioUartAxiLite {
+    fn read(&self, buf: &mut [u8]) -> usize {
+        self.read(buf)
+    }
+
+    fn write(&self, buf: &[u8]) -> usize {
+        self.write(buf)
+    }
+}
+
+/// For Uart BFLB
+pub struct UartBflbWrap {
+    inner: *const BflbUartRegisterBlock,
+}
+
+impl UartBflbWrap {
+    pub fn new(base: usize) -> Self {
+        Self {
+            inner: base as *const BflbUartRegisterBlock,
+        }
+    }
+}
+
+impl ConsoleDevice for UartBflbWrap {
+    fn read(&self, buf: &mut [u8]) -> usize {
+        let uart = unsafe { &(*self.inner) };
+        while uart.fifo_config_1.read().receive_available_bytes() == 0 {
+            core::hint::spin_loop();
+        }
+        let len = core::cmp::min(
+            uart.fifo_config_1.read().receive_available_bytes() as usize,
+            buf.len(),
+        );
+        buf.iter_mut()
+            .take(len)
+            .for_each(|slot| *slot = uart.fifo_read.read());
+        len
+    }
+
+    fn write(&self, buf: &[u8]) -> usize {
+        let uart = unsafe { &(*self.inner) };
+        let mut count = 0;
+        for current in buf {
+            if uart.fifo_config_1.read().transmit_available_bytes() == 0 {
+                break;
+            }
+            count += 1;
+            unsafe {
+                uart.fifo_write.write(*current);
+            }
+        }
+        count
+    }
+}

+ 434 - 0
prototyper/prototyper/src/platform/mod.rs

@@ -0,0 +1,434 @@
+use alloc::string::String;
+use alloc::{boxed::Box, string::ToString};
+use clint::{SifiveClintWrap, THeadClintWrap};
+use core::{
+    ops::Range,
+    sync::atomic::{AtomicBool, Ordering},
+};
+use reset::SifiveTestDeviceWrap;
+use spin::Mutex;
+use uart_xilinx::MmioUartAxiLite;
+
+use crate::cfg::NUM_HART_MAX;
+use crate::devicetree::*;
+use crate::fail;
+use crate::platform::clint::{MachineClintType, SIFIVE_CLINT_COMPATIBLE, THEAD_CLINT_COMPATIBLE};
+use crate::platform::console::Uart16550Wrap;
+use crate::platform::console::UartBflbWrap;
+use crate::platform::console::{
+    MachineConsoleType, UART16650U8_COMPATIBLE, UART16650U32_COMPATIBLE, UARTAXILITE_COMPATIBLE,
+    UARTBFLB_COMPATIBLE,
+};
+use crate::platform::reset::SIFIVETEST_COMPATIBLE;
+use crate::sbi::SBI;
+use crate::sbi::console::SbiConsole;
+use crate::sbi::extensions;
+use crate::sbi::hsm::SbiHsm;
+use crate::sbi::ipi::SbiIpi;
+use crate::sbi::logger;
+use crate::sbi::reset::SbiReset;
+use crate::sbi::rfence::SbiRFence;
+use crate::sbi::trap_stack;
+
+mod clint;
+mod console;
+mod reset;
+
+type BaseAddress = usize;
+
+type CpuEnableList = [bool; NUM_HART_MAX];
+
+pub struct BoardInfo {
+    pub memory_range: Option<Range<usize>>,
+    pub console: Option<(BaseAddress, MachineConsoleType)>,
+    pub reset: Option<BaseAddress>,
+    pub ipi: Option<(BaseAddress, MachineClintType)>,
+    pub cpu_num: Option<usize>,
+    pub cpu_enabled: Option<CpuEnableList>,
+    pub model: String,
+}
+
+impl BoardInfo {
+    pub const fn new() -> Self {
+        BoardInfo {
+            memory_range: None,
+            console: None,
+            reset: None,
+            ipi: None,
+            cpu_enabled: None,
+            cpu_num: None,
+            model: String::new(),
+        }
+    }
+}
+
+pub struct Platform {
+    pub info: BoardInfo,
+    pub sbi: SBI,
+    pub ready: AtomicBool,
+}
+
+impl Platform {
+    pub const fn new() -> Self {
+        Platform {
+            info: BoardInfo::new(),
+            sbi: SBI::new(),
+            ready: AtomicBool::new(false),
+        }
+    }
+
+    pub fn init(&mut self, fdt_address: usize) {
+        self.info_init(fdt_address);
+        self.sbi_init();
+        trap_stack::prepare_for_trap();
+        self.ready.swap(true, Ordering::Release);
+    }
+
+    fn info_init(&mut self, fdt_address: usize) {
+        let dtb = parse_device_tree(fdt_address).unwrap_or_else(fail::device_tree_format);
+        let dtb = dtb.share();
+
+        let root: serde_device_tree::buildin::Node = serde_device_tree::from_raw_mut(&dtb)
+            .unwrap_or_else(fail::device_tree_deserialize_root);
+        let tree: Tree = root.deserialize();
+
+        // Get console device, init sbi console and logger
+        self.sbi_find_and_init_console(&root);
+
+        // Get ipi and reset device info
+        let mut find_device = |node: &serde_device_tree::buildin::Node| {
+            let info = get_compatible_and_range(node);
+            if let Some(info) = info {
+                let (compatible, regs) = info;
+                let base_address = regs.start;
+                for device_id in compatible.iter() {
+                    // Initialize clint device.
+                    if SIFIVE_CLINT_COMPATIBLE.contains(&device_id) {
+                        if node.get_prop("clint,has-no-64bit-mmio").is_some() {
+                            self.info.ipi = Some((base_address, MachineClintType::TheadClint));
+                        } else {
+                            self.info.ipi = Some((base_address, MachineClintType::SiFiveClint));
+                        }
+                    } else if THEAD_CLINT_COMPATIBLE.contains(&device_id) {
+                        self.info.ipi = Some((base_address, MachineClintType::TheadClint));
+                    }
+                    // Initialize reset device.
+                    if SIFIVETEST_COMPATIBLE.contains(&device_id) {
+                        self.info.reset = Some(base_address);
+                    }
+                }
+            }
+        };
+        root.search(&mut find_device);
+
+        // Get memory info
+        // TODO: More than one memory node or range?
+        let memory_reg = tree
+            .memory
+            .iter()
+            .next()
+            .unwrap()
+            .deserialize::<Memory>()
+            .reg;
+        let memory_range = memory_reg.iter().next().unwrap().0;
+        self.info.memory_range = Some(memory_range);
+
+        // Get cpu number info
+        self.info.cpu_num = Some(tree.cpus.cpu.len());
+
+        // Get model info
+        if let Some(model) = tree.model {
+            let model = model.iter().next().unwrap_or("<unspecified>");
+            self.info.model = model.to_string();
+        } else {
+            let model = "<unspecified>";
+            self.info.model = model.to_string();
+        }
+
+        // TODO: Need a better extension initialization method
+        extensions::init(&tree.cpus.cpu);
+
+        // Find which hart is enabled by fdt
+        let mut cpu_list: CpuEnableList = [false; NUM_HART_MAX];
+        for cpu_iter in tree.cpus.cpu.iter() {
+            let cpu = cpu_iter.deserialize::<Cpu>();
+            let hart_id = cpu.reg.iter().next().unwrap().0.start;
+            if let Some(x) = cpu_list.get_mut(hart_id) {
+                *x = true;
+            }
+        }
+        self.info.cpu_enabled = Some(cpu_list);
+    }
+
+    fn sbi_init(&mut self) {
+        self.sbi_ipi_init();
+        self.sbi_hsm_init();
+        self.sbi_reset_init();
+        self.sbi_rfence_init();
+    }
+
+    fn sbi_find_and_init_console(&mut self, root: &serde_device_tree::buildin::Node) {
+        //  Get console device info
+        if let Some(stdout_path) = root.chosen_stdout_path() {
+            if let Some(node) = root.find(stdout_path) {
+                let info = get_compatible_and_range(&node);
+                if let Some((compatible, regs)) = info {
+                    for device_id in compatible.iter() {
+                        if UART16650U8_COMPATIBLE.contains(&device_id) {
+                            self.info.console = Some((regs.start, MachineConsoleType::Uart16550U8));
+                        }
+                        if UART16650U32_COMPATIBLE.contains(&device_id) {
+                            self.info.console =
+                                Some((regs.start, MachineConsoleType::Uart16550U32));
+                        }
+                        if UARTAXILITE_COMPATIBLE.contains(&device_id) {
+                            self.info.console = Some((regs.start, MachineConsoleType::UartAxiLite));
+                        }
+                        if UARTBFLB_COMPATIBLE.contains(&device_id) {
+                            self.info.console = Some((regs.start, MachineConsoleType::UartBflb));
+                        }
+                    }
+                }
+            }
+        }
+
+        // init console and logger
+        self.sbi_console_init();
+        logger::Logger::init().unwrap();
+        info!("Hello RustSBI!");
+    }
+
+    fn sbi_console_init(&mut self) {
+        if let Some((base, console_type)) = self.info.console {
+            self.sbi.console = match console_type {
+                MachineConsoleType::Uart16550U8 => Some(SbiConsole::new(Mutex::new(Box::new(
+                    Uart16550Wrap::<u8>::new(base),
+                )))),
+                MachineConsoleType::Uart16550U32 => Some(SbiConsole::new(Mutex::new(Box::new(
+                    Uart16550Wrap::<u32>::new(base),
+                )))),
+                MachineConsoleType::UartAxiLite => Some(SbiConsole::new(Mutex::new(Box::new(
+                    MmioUartAxiLite::new(base),
+                )))),
+                MachineConsoleType::UartBflb => Some(SbiConsole::new(Mutex::new(Box::new(
+                    UartBflbWrap::new(base),
+                )))),
+            };
+        } else {
+            self.sbi.console = None;
+        }
+    }
+
+    fn sbi_reset_init(&mut self) {
+        if let Some(base) = self.info.reset {
+            self.sbi.reset = Some(SbiReset::new(Mutex::new(Box::new(
+                SifiveTestDeviceWrap::new(base),
+            ))));
+        } else {
+            self.sbi.reset = None;
+        }
+    }
+
+    fn sbi_ipi_init(&mut self) {
+        if let Some((base, clint_type)) = self.info.ipi {
+            self.sbi.ipi = match clint_type {
+                MachineClintType::SiFiveClint => Some(SbiIpi::new(
+                    Mutex::new(Box::new(SifiveClintWrap::new(base))),
+                    self.info.cpu_num.unwrap_or(NUM_HART_MAX),
+                )),
+                MachineClintType::TheadClint => Some(SbiIpi::new(
+                    Mutex::new(Box::new(THeadClintWrap::new(base))),
+                    self.info.cpu_num.unwrap_or(NUM_HART_MAX),
+                )),
+            };
+        } else {
+            self.sbi.ipi = None;
+        }
+    }
+
+    fn sbi_hsm_init(&mut self) {
+        // TODO: Can HSM work properly when there is no ipi device?
+        if self.info.ipi.is_some() {
+            self.sbi.hsm = Some(SbiHsm);
+        } else {
+            self.sbi.hsm = None;
+        }
+    }
+
+    fn sbi_rfence_init(&mut self) {
+        // TODO: Can rfence work properly when there is no ipi device?
+        if self.info.ipi.is_some() {
+            self.sbi.rfence = Some(SbiRFence);
+        } else {
+            self.sbi.rfence = None;
+        }
+    }
+
+    pub fn print_board_info(&self) {
+        info!("RustSBI version {}", rustsbi::VERSION);
+        rustsbi::LOGO.lines().for_each(|line| info!("{}", line));
+        info!("Initializing RustSBI machine-mode environment.");
+
+        self.print_platform_info();
+        self.print_cpu_info();
+        self.print_device_info();
+        self.print_memory_info();
+        self.print_additional_info();
+    }
+
+    #[inline]
+    fn print_platform_info(&self) {
+        info!("{:<30}: {}", "Platform Name", self.info.model);
+    }
+
+    fn print_cpu_info(&self) {
+        info!(
+            "{:<30}: {:?}",
+            "Platform HART Count",
+            self.info.cpu_num.unwrap_or(0)
+        );
+
+        if let Some(cpu_enabled) = &self.info.cpu_enabled {
+            let mut enabled_harts = [0; NUM_HART_MAX];
+            let mut count = 0;
+            for (i, &enabled) in cpu_enabled.iter().enumerate() {
+                if enabled {
+                    enabled_harts[count] = i;
+                    count += 1;
+                }
+            }
+            info!("{:<30}: {:?}", "Enabled HARTs", &enabled_harts[..count]);
+        } else {
+            warn!("{:<30}: Not Available", "Enabled HARTs");
+        }
+    }
+
+    #[inline]
+    fn print_device_info(&self) {
+        self.print_clint_info();
+        self.print_console_info();
+        self.print_reset_info();
+        self.print_hsm_info();
+        self.print_rfence_info();
+    }
+
+    #[inline]
+    fn print_clint_info(&self) {
+        match self.info.ipi {
+            Some((base, device)) => {
+                info!(
+                    "{:<30}: {:?} (Base Address: 0x{:x})",
+                    "Platform IPI Device", device, base
+                );
+            }
+            None => warn!("{:<30}: Not Available", "Platform IPI Device"),
+        }
+    }
+
+    #[inline]
+    fn print_console_info(&self) {
+        match self.info.console {
+            Some((base, device)) => {
+                info!(
+                    "{:<30}: {:?} (Base Address: 0x{:x})",
+                    "Platform Console Device", device, base
+                );
+            }
+            None => warn!("{:<30}: Not Available", "Platform Console Device"),
+        }
+    }
+
+    #[inline]
+    fn print_reset_info(&self) {
+        if let Some(base) = self.info.reset {
+            info!(
+                "{:<30}: Available (Base Address: 0x{:x})",
+                "Platform Reset Device", base
+            );
+        } else {
+            warn!("{:<30}: Not Available", "Platform Reset Device");
+        }
+    }
+
+    #[inline]
+    fn print_memory_info(&self) {
+        if let Some(memory_range) = &self.info.memory_range {
+            info!(
+                "{:<30}: 0x{:x} - 0x{:x}",
+                "Memory range", memory_range.start, memory_range.end
+            );
+        } else {
+            warn!("{:<30}: Not Available", "Memory range");
+        }
+    }
+
+    #[inline]
+    fn print_hsm_info(&self) {
+        info!(
+            "{:<30}: {}",
+            "Platform HSM Device",
+            if self.have_hsm() {
+                "Available"
+            } else {
+                "Not Available"
+            }
+        );
+    }
+
+    #[inline]
+    fn print_rfence_info(&self) {
+        info!(
+            "{:<30}: {}",
+            "Platform RFence Device",
+            if self.have_rfence() {
+                "Available"
+            } else {
+                "Not Available"
+            }
+        );
+    }
+
+    #[inline]
+    fn print_additional_info(&self) {
+        if !self.ready.load(Ordering::Acquire) {
+            warn!(
+                "{:<30}: Platform initialization is not complete.",
+                "Platform Status"
+            );
+        } else {
+            info!(
+                "{:<30}: Platform initialization complete and ready.",
+                "Platform Status"
+            );
+        }
+    }
+}
+
+#[allow(unused)]
+impl Platform {
+    pub fn have_console(&self) -> bool {
+        self.sbi.console.is_some()
+    }
+
+    pub fn have_reset(&self) -> bool {
+        self.sbi.reset.is_some()
+    }
+
+    pub fn have_ipi(&self) -> bool {
+        self.sbi.ipi.is_some()
+    }
+
+    pub fn have_hsm(&self) -> bool {
+        self.sbi.hsm.is_some()
+    }
+
+    pub fn have_rfence(&self) -> bool {
+        self.sbi.rfence.is_some()
+    }
+
+    pub fn ready(&self) -> bool {
+        self.ready.load(Ordering::Acquire)
+    }
+}
+
+pub(crate) static mut PLATFORM: Platform = Platform::new();

+ 34 - 0
prototyper/prototyper/src/platform/reset.rs

@@ -0,0 +1,34 @@
+use sifive_test_device::SifiveTestDevice;
+
+use crate::sbi::reset::ResetDevice;
+pub(crate) const SIFIVETEST_COMPATIBLE: [&str; 1] = ["sifive,test0"];
+
+pub struct SifiveTestDeviceWrap {
+    inner: *const SifiveTestDevice,
+}
+
+impl SifiveTestDeviceWrap {
+    pub fn new(base: usize) -> Self {
+        Self {
+            inner: base as *const SifiveTestDevice,
+        }
+    }
+}
+
+/// Reset Device: SifiveTestDevice
+impl ResetDevice for SifiveTestDeviceWrap {
+    #[inline]
+    fn fail(&self, code: u16) -> ! {
+        unsafe { (*self.inner).fail(code) }
+    }
+
+    #[inline]
+    fn pass(&self) -> ! {
+        unsafe { (*self.inner).pass() }
+    }
+
+    #[inline]
+    fn reset(&self) -> ! {
+        unsafe { (*self.inner).reset() }
+    }
+}

+ 63 - 0
prototyper/prototyper/src/riscv/csr.rs

@@ -0,0 +1,63 @@
+#![allow(unused)]
+
+/// CSR addresses for timer registers.
+///
+/// Time value (lower 32 bits).
+pub const CSR_TIME: u32 = 0xc01;
+/// Time value (upper 32 bits).
+pub const CSR_TIMEH: u32 = 0xc81;
+/// Supervisor timer compare value.
+pub const CSR_STIMECMP: u32 = 0x14D;
+
+/// Machine environment configuration register (menvcfg) bit fields.
+pub mod menvcfg {
+    use core::arch::asm;
+
+    /// Fence of I/O implies memory.
+    pub const FIOM: usize = 0x1 << 0;
+    /// Cache block invalidate - flush.
+    pub const CBIE_FLUSH: usize = 0x01 << 4;
+    /// Cache block invalidate - invalidate.
+    pub const CBIE_INVALIDATE: usize = 0x11 << 4;
+    /// Cache block clean for enclave.
+    pub const CBCFE: usize = 0x1 << 6;
+    /// Cache block zero for enclave.
+    pub const CBZE: usize = 0x1 << 7;
+    /// Page-based memory types enable.
+    pub const PBMTE: usize = 0x1 << 62;
+    /// Supervisor timer counter enable.
+    pub const STCE: usize = 0x1 << 63;
+
+    /// Sets the STCE bit to enable supervisor timer counter.
+    #[inline(always)]
+    pub fn set_stce() {
+        set_bits(STCE);
+    }
+
+    /// Sets specified bits in menvcfg register.
+    pub fn set_bits(option: usize) {
+        let mut bits: usize;
+        unsafe {
+            // Read current `menvcfg` value.
+            asm!("csrr {}, menvcfg", out(reg) bits, options(nomem));
+        }
+        // Set requested bits
+        bits |= option;
+        unsafe {
+            // Write back updated value
+            asm!("csrw menvcfg, {}", in(reg) bits, options(nomem));
+        }
+    }
+}
+
+/// Supervisor timer compare register operations.
+pub mod stimecmp {
+    use core::arch::asm;
+
+    /// Sets the supervisor timer compare value.
+    pub fn set(value: u64) {
+        unsafe {
+            asm!("csrrw zero, stimecmp, {}", in(reg) value, options(nomem));
+        }
+    }
+}

+ 7 - 0
prototyper/prototyper/src/riscv/mod.rs

@@ -0,0 +1,7 @@
+pub mod csr;
+
+/// Returns the current hart (hardware thread) ID.
+#[inline]
+pub fn current_hartid() -> usize {
+    riscv::register::mhartid::read()
+}

+ 126 - 0
prototyper/prototyper/src/sbi/console.rs

@@ -0,0 +1,126 @@
+use alloc::boxed::Box;
+use core::fmt::{self, Write};
+use rustsbi::{Console, Physical, SbiRet};
+use spin::Mutex;
+
+use crate::platform::PLATFORM;
+
+/// A trait that must be implemented by console devices to provide basic I/O functionality.
+pub trait ConsoleDevice {
+    /// Reads bytes from the console into the provided buffer.
+    ///
+    /// # Returns
+    /// The number of bytes that were successfully read.
+    fn read(&self, buf: &mut [u8]) -> usize;
+
+    /// Writes bytes from the provided buffer to the console.
+    ///
+    /// # Returns
+    /// The number of bytes that were successfully written.
+    fn write(&self, buf: &[u8]) -> usize;
+}
+
+/// An implementation of the SBI console interface that wraps a console device.
+///
+/// This provides a safe interface for interacting with console hardware through the
+/// SBI specification.
+pub struct SbiConsole {
+    inner: Mutex<Box<dyn ConsoleDevice>>,
+}
+
+impl SbiConsole {
+    /// Creates a new SBI console that wraps the provided locked console device.
+    ///
+    /// # Arguments
+    /// * `inner` - A mutex containing the console device implementation
+    #[inline]
+    pub fn new(inner: Mutex<Box<dyn ConsoleDevice>>) -> Self {
+        Self { inner }
+    }
+
+    /// Writes a single character to the console.
+    ///
+    /// # Arguments
+    /// * `c` - The character to write, as a usize
+    ///
+    /// # Returns
+    /// Always returns 0 to indicate success
+    #[inline]
+    pub fn putchar(&mut self, c: usize) -> usize {
+        self.write_char(c as u8 as char).unwrap();
+        0
+    }
+
+    /// Reads a single character from the console.
+    ///
+    /// This method will block until a character is available to be read.
+    ///
+    /// # Returns
+    /// The read character as a usize
+    #[inline]
+    pub fn getchar(&self) -> usize {
+        let mut c = 0u8;
+        let console = self.inner.lock();
+        // Block until we successfully read 1 byte
+        while console.read(core::slice::from_mut(&mut c)) != 1 {
+            core::hint::spin_loop();
+        }
+        c as usize
+    }
+}
+
+impl Console for SbiConsole {
+    /// Write a physical memory buffer to the console.
+    #[inline]
+    fn write(&self, bytes: Physical<&[u8]>) -> SbiRet {
+        // TODO: verify valid memory range for a `Physical` slice.
+        let start = bytes.phys_addr_lo();
+        let buf = unsafe { core::slice::from_raw_parts(start as *const u8, bytes.num_bytes()) };
+        let bytes_written = self.inner.lock().write(buf);
+        SbiRet::success(bytes_written)
+    }
+
+    /// Read from console into a physical memory buffer.
+    #[inline]
+    fn read(&self, bytes: Physical<&mut [u8]>) -> SbiRet {
+        // TODO: verify valid memory range for a `Physical` slice.
+        let start = bytes.phys_addr_lo();
+        let buf = unsafe { core::slice::from_raw_parts_mut(start as *mut u8, bytes.num_bytes()) };
+        let bytes_read = self.inner.lock().read(buf);
+        SbiRet::success(bytes_read)
+    }
+
+    /// Write a single byte to the console.
+    #[inline]
+    fn write_byte(&self, byte: u8) -> SbiRet {
+        self.inner.lock().write(&[byte]);
+        SbiRet::success(0)
+    }
+}
+
+impl fmt::Write for SbiConsole {
+    /// Implement Write trait for string formatting.
+    #[inline]
+    fn write_str(&mut self, s: &str) -> fmt::Result {
+        let mut bytes = s.as_bytes();
+        let console = self.inner.lock();
+        // Write all bytes in chunks
+        while !bytes.is_empty() {
+            let count = console.write(bytes);
+            bytes = &bytes[count..];
+        }
+        Ok(())
+    }
+}
+
+/// Global function to write a character to the console.
+#[inline]
+pub fn putchar(c: usize) -> usize {
+    unsafe { PLATFORM.sbi.console.as_mut().unwrap().putchar(c) }
+}
+
+/// Global function to read a character from the console.
+#[inline]
+pub fn getchar() -> usize {
+    unsafe { PLATFORM.sbi.console.as_mut().unwrap().getchar() }
+}

+ 21 - 0
prototyper/prototyper/src/sbi/early_trap.rs

@@ -0,0 +1,21 @@
+use core::arch::naked_asm;
+
+/// When you expected some insts will cause trap, use this.
+/// If trap happened, a0 will set to 1, otherwise will be 0.
+///
+/// This function will change a0 and a1 and will NOT change them back.
+#[naked]
+#[repr(align(16))]
+pub(crate) unsafe extern "C" fn expected_trap() {
+    unsafe {
+        naked_asm!(
+            "add a0, zero, zero",
+            "add a1, zero, zero",
+            "csrr a1, mepc",
+            "addi a1, a1, 4",
+            "csrw mepc, a1",
+            "addi a0, zero, 1",
+            "mret",
+        )
+    }
+}

+ 129 - 0
prototyper/prototyper/src/sbi/extensions.rs

@@ -0,0 +1,129 @@
+use serde_device_tree::buildin::NodeSeq;
+
+use crate::riscv::current_hartid;
+use crate::sbi::trap_stack::ROOT_STACK;
+
+pub struct HartFeatures {
+    extension: [bool; Extension::COUNT],
+    privileged_version: PrivilegedVersion,
+}
+
+#[derive(Copy, Clone)]
+pub enum Extension {
+    Sstc = 0,
+}
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub enum PrivilegedVersion {
+    Unknown = 0,
+    Version1_10 = 1,
+    Version1_11 = 2,
+    Version1_12 = 3,
+}
+
+impl Extension {
+    const COUNT: usize = 1;
+    const ITER: [Self; Extension::COUNT] = [Extension::Sstc];
+
+    pub fn as_str(&self) -> &'static str {
+        match self {
+            Extension::Sstc => "sstc",
+        }
+    }
+
+    #[inline]
+    pub fn index(&self) -> usize {
+        *self as usize
+    }
+}
+
+pub fn hart_extension_probe(hart_id: usize, ext: Extension) -> bool {
+    unsafe {
+        ROOT_STACK
+            .get_mut(hart_id)
+            .map(|x| x.hart_context().features.extension[ext.index()])
+            .unwrap()
+    }
+}
+
+pub fn hart_privileged_version(hart_id: usize) -> PrivilegedVersion {
+    unsafe {
+        ROOT_STACK
+            .get_mut(hart_id)
+            .map(|x| x.hart_context().features.privileged_version)
+            .unwrap()
+    }
+}
+
+#[cfg(not(feature = "nemu"))]
+pub fn init(cpus: &NodeSeq) {
+    use crate::devicetree::Cpu;
+    for cpu_iter in cpus.iter() {
+        let cpu = cpu_iter.deserialize::<Cpu>();
+        let hart_id = cpu.reg.iter().next().unwrap().0.start;
+        let mut hart_exts = [false; Extension::COUNT];
+        if cpu.isa_extensions.is_some() {
+            let isa = cpu.isa_extensions.unwrap();
+            Extension::ITER.iter().for_each(|ext| {
+                hart_exts[ext.index()] = isa.iter().any(|e| e == ext.as_str());
+            });
+        } else if cpu.isa.is_some() {
+            let isa_iter = cpu.isa.unwrap();
+            let isa = isa_iter.iter().next().unwrap_or_default();
+            Extension::ITER.iter().for_each(|ext| {
+                hart_exts[ext.index()] = isa.contains(ext.as_str());
+            })
+        }
+
+        unsafe {
+            ROOT_STACK
+                .get_mut(hart_id)
+                .map(|stack| stack.hart_context().features.extension = hart_exts)
+                .unwrap()
+        }
+    }
+}
+
+pub fn privileged_version_detection() {
+    let mut current_priv_ver = PrivilegedVersion::Unknown;
+    {
+        const CSR_MCOUNTEREN: u64 = 0x306;
+        const CSR_MCOUNTINHIBIT: u64 = 0x320;
+        const CSR_MENVCFG: u64 = 0x30a;
+
+        if has_csr!(CSR_MCOUNTEREN) {
+            current_priv_ver = PrivilegedVersion::Version1_10;
+            if has_csr!(CSR_MCOUNTINHIBIT) {
+                current_priv_ver = PrivilegedVersion::Version1_11;
+                if has_csr!(CSR_MENVCFG) {
+                    current_priv_ver = PrivilegedVersion::Version1_12;
+                }
+            }
+        }
+    }
+    unsafe {
+        ROOT_STACK
+            .get_mut(current_hartid())
+            .map(|stack| stack.hart_context().features.privileged_version = current_priv_ver)
+            .unwrap()
+    }
+}
+
+#[cfg(feature = "nemu")]
+pub fn init(cpus: &NodeSeq) {
+    for hart_id in 0..cpus.len() {
+        let mut hart_exts = [false; Extension::COUNT];
+        hart_exts[Extension::Sstc.index()] = true;
+        unsafe {
+            ROOT_STACK
+                .get_mut(hart_id)
+                .map(|stack| {
+                    stack.hart_context().features = HartFeatures {
+                        extension: hart_exts,
+                        privileged_version: PrivilegedVersion::Version1_12,
+                    }
+                })
+                .unwrap()
+        }
+    }
+}

+ 70 - 0
prototyper/prototyper/src/sbi/fifo.rs

@@ -0,0 +1,70 @@
+use core::mem::MaybeUninit;
+
+/// Size of the FIFO buffer.
+const FIFO_SIZE: usize = 16;
+
+#[derive(Debug)]
+pub enum FifoError {
+    Empty,
+    Full,
+}
+
+/// A fixed-size FIFO (First In First Out) queue implementation.
+pub struct Fifo<T: Copy + Clone> {
+    data: [MaybeUninit<T>; FIFO_SIZE],
+    head: usize,
+    tail: usize,
+    count: usize,
+}
+
+impl<T: Copy + Clone> Fifo<T> {
+    #[inline]
+    pub fn new() -> Self {
+        // Initialize array with uninitialized values
+        let data = [MaybeUninit::uninit(); FIFO_SIZE];
+        Self {
+            data,
+            head: 0,
+            tail: 0,
+            count: 0,
+        }
+    }
+
+    #[inline]
+    pub fn is_full(&self) -> bool {
+        self.count == FIFO_SIZE
+    }
+
+    #[inline]
+    pub fn is_empty(&self) -> bool {
+        self.count == 0
+    }
+
+    pub fn push(&mut self, element: T) -> Result<(), FifoError> {
+        if self.is_full() {
+            return Err(FifoError::Full);
+        }
+
+        // Write element and update tail position
+        self.data[self.tail].write(element);
+        self.tail = (self.tail + 1) % FIFO_SIZE;
+        self.count += 1;
+
+        Ok(())
+    }
+
+    pub fn pop(&mut self) -> Result<T, FifoError> {
+        if self.is_empty() {
+            return Err(FifoError::Empty);
+        }
+
+        // unsafe: Take ownership of element at head
+        let element = unsafe { self.data[self.head].assume_init_read() };
+
+        // Update head position
+        self.head = (self.head + 1) % FIFO_SIZE;
+        self.count -= 1;
+
+        Ok(element)
+    }
+}

+ 47 - 0
prototyper/prototyper/src/sbi/hart_context.rs

@@ -0,0 +1,47 @@
+use crate::sbi::extensions::HartFeatures;
+use crate::sbi::hsm::HsmCell;
+use crate::sbi::rfence::RFenceCell;
+use core::ptr::NonNull;
+use core::sync::atomic::AtomicU8;
+use fast_trap::FlowContext;
+use riscv::register::mstatus;
+
+/// Context for managing hart (hardware thread) state and operations.
+pub(crate) struct HartContext {
+    /// Trap context for handling exceptions and interrupts.
+    trap: FlowContext,
+    /// Hart state management cell containing next stage boot info.
+    pub hsm: HsmCell<NextStage>,
+    /// Remote fence synchronization cell.
+    pub rfence: RFenceCell,
+    /// Type of inter-processor interrupt pending.
+    pub ipi_type: AtomicU8,
+    /// Supported hart features.
+    pub features: HartFeatures,
+}
+
+impl HartContext {
+    /// Initialize the hart context by creating new HSM and RFence cells
+    #[inline]
+    pub fn init(&mut self) {
+        self.hsm = HsmCell::new();
+        self.rfence = RFenceCell::new();
+    }
+
+    /// Get a non-null pointer to the trap context.
+    #[inline]
+    pub fn context_ptr(&mut self) -> NonNull<FlowContext> {
+        unsafe { NonNull::new_unchecked(&mut self.trap) }
+    }
+}
+
+/// Information needed to boot into the next execution stage.
+#[derive(Debug)]
+pub struct NextStage {
+    /// Starting address to jump to.
+    pub start_addr: usize,
+    /// Opaque value passed to next stage.
+    pub opaque: usize,
+    /// Privilege mode for next stage.
+    pub next_mode: mstatus::MPP,
+}

+ 21 - 0
prototyper/prototyper/src/sbi/heap.rs

@@ -0,0 +1,21 @@
+use crate::cfg::HEAP_SIZE;
+use buddy_system_allocator::LockedHeap;
+
+#[unsafe(link_section = ".bss.heap")]
+static mut HEAP: [u8; HEAP_SIZE] = [0; HEAP_SIZE];
+
+#[global_allocator]
+static HEAP_ALLOCATOR: LockedHeap<15> = LockedHeap::<15>::empty();
+
+pub fn sbi_heap_init() {
+    unsafe {
+        HEAP_ALLOCATOR
+            .lock()
+            .init(HEAP.as_ptr() as usize, HEAP_SIZE);
+    }
+}
+
+#[alloc_error_handler]
+pub fn handle_alloc_error(layout: core::alloc::Layout) -> ! {
+    panic!("Heap allocation error, layout = {:?}", layout);
+}

+ 254 - 0
prototyper/prototyper/src/sbi/hsm.rs

@@ -0,0 +1,254 @@
+use core::{
+    cell::UnsafeCell,
+    hint::spin_loop,
+    sync::atomic::{AtomicUsize, Ordering},
+};
+use riscv::register::mstatus::MPP;
+use rustsbi::{SbiRet, spec::hsm::hart_state};
+
+use crate::platform::PLATFORM;
+use crate::riscv::current_hartid;
+use crate::sbi::hart_context::NextStage;
+use crate::sbi::trap_stack::ROOT_STACK;
+
+/// Special state indicating a hart is in the process of starting.
+const HART_STATE_START_PENDING_EXT: usize = usize::MAX;
+
+type HsmState = AtomicUsize;
+
+/// Cell for managing hart state and shared data between harts.
+pub(crate) struct HsmCell<T> {
+    status: HsmState,
+    inner: UnsafeCell<Option<T>>,
+}
+
+impl<T> HsmCell<T> {
+    /// Creates a new HsmCell with STOPPED state and no inner data.
+    pub const fn new() -> Self {
+        Self {
+            status: HsmState::new(hart_state::STOPPED),
+            inner: UnsafeCell::new(None),
+        }
+    }
+
+    /// Gets a local view of this cell for the current hart.
+    ///
+    /// # Safety
+    ///
+    /// Caller must ensure this cell belongs to the current hart.
+    #[inline]
+    pub unsafe fn local(&self) -> LocalHsmCell<'_, T> {
+        LocalHsmCell(self)
+    }
+
+    /// Gets a remote view of this cell for accessing from other harts.
+    #[inline]
+    pub fn remote(&self) -> RemoteHsmCell<'_, T> {
+        RemoteHsmCell(self)
+    }
+}
+
+/// View of HsmCell for operations on the current hart.
+pub struct LocalHsmCell<'a, T>(&'a HsmCell<T>);
+
+/// View of HsmCell for operations from other harts.
+pub struct RemoteHsmCell<'a, T>(&'a HsmCell<T>);
+
+// Mark HsmCell as safe to share between threads
+unsafe impl<T: Send> Sync for HsmCell<T> {}
+unsafe impl<T: Send> Send for HsmCell<T> {}
+
+impl<T> LocalHsmCell<'_, T> {
+    /// Attempts to transition hart from START_PENDING to STARTED state.
+    ///
+    /// Returns inner data if successful, otherwise returns current state.
+    #[inline]
+    pub fn start(&self) -> Result<T, usize> {
+        loop {
+            match self.0.status.compare_exchange(
+                hart_state::START_PENDING,
+                hart_state::STARTED,
+                Ordering::AcqRel,
+                Ordering::Relaxed,
+            ) {
+                Ok(_) => break Ok(unsafe { (*self.0.inner.get()).take().unwrap() }),
+                Err(HART_STATE_START_PENDING_EXT) => spin_loop(),
+                Err(s) => break Err(s),
+            }
+        }
+    }
+
+    /// Transitions hart to STOPPED state.
+    #[allow(unused)]
+    #[inline]
+    pub fn stop(&self) {
+        self.0.status.store(hart_state::STOPPED, Ordering::Release)
+    }
+
+    /// Transitions hart to SUSPENDED state.
+    #[allow(unused)]
+    #[inline]
+    pub fn suspend(&self) {
+        self.0
+            .status
+            .store(hart_state::SUSPENDED, Ordering::Relaxed)
+    }
+
+    /// Transitions hart to STARTED state.
+    #[allow(unused)]
+    #[inline]
+    pub fn resume(&self) {
+        self.0.status.store(hart_state::STARTED, Ordering::Relaxed)
+    }
+}
+
+impl<T: core::fmt::Debug> RemoteHsmCell<'_, T> {
+    /// Attempts to start a stopped hart by providing startup data.
+    ///
+    /// Returns true if successful, false if hart was not in STOPPED state.
+    #[inline]
+    pub fn start(&self, t: T) -> bool {
+        if self
+            .0
+            .status
+            .compare_exchange(
+                hart_state::STOPPED,
+                HART_STATE_START_PENDING_EXT,
+                Ordering::Acquire,
+                Ordering::Relaxed,
+            )
+            .is_ok()
+        {
+            unsafe { *self.0.inner.get() = Some(t) };
+            self.0
+                .status
+                .store(hart_state::START_PENDING, Ordering::Release);
+            true
+        } else {
+            false
+        }
+    }
+
+    /// Gets the current state of the hart.
+    #[allow(unused)]
+    #[inline]
+    pub fn sbi_get_status(&self) -> usize {
+        match self.0.status.load(Ordering::Relaxed) {
+            HART_STATE_START_PENDING_EXT => hart_state::START_PENDING,
+            normal => normal,
+        }
+    }
+
+    /// Checks if hart can receive IPIs (must be STARTED or SUSPENDED).
+    #[allow(unused)]
+    #[inline]
+    pub fn allow_ipi(&self) -> bool {
+        matches!(
+            self.0.status.load(Ordering::Relaxed),
+            hart_state::STARTED | hart_state::SUSPENDED
+        )
+    }
+}
+
+/// Gets the local HSM cell for the current hart.
+pub(crate) fn local_hsm() -> LocalHsmCell<'static, NextStage> {
+    unsafe {
+        ROOT_STACK
+            .get_unchecked_mut(current_hartid())
+            .hart_context()
+            .hsm
+            .local()
+    }
+}
+
+/// Gets a remote view of the current hart's HSM cell.
+pub(crate) fn local_remote_hsm() -> RemoteHsmCell<'static, NextStage> {
+    unsafe {
+        ROOT_STACK
+            .get_unchecked_mut(current_hartid())
+            .hart_context()
+            .hsm
+            .remote()
+    }
+}
+
+/// Gets a remote view of any hart's HSM cell.
+#[allow(unused)]
+pub(crate) fn remote_hsm(hart_id: usize) -> Option<RemoteHsmCell<'static, NextStage>> {
+    unsafe {
+        ROOT_STACK
+            .get_mut(hart_id)
+            .map(|x| x.hart_context().hsm.remote())
+    }
+}
+
+/// Implementation of SBI HSM (Hart State Management) extension.
+pub(crate) struct SbiHsm;
+
+impl rustsbi::Hsm for SbiHsm {
+    /// Starts execution on a stopped hart.
+    fn hart_start(&self, hartid: usize, start_addr: usize, opaque: usize) -> SbiRet {
+        match remote_hsm(hartid) {
+            Some(remote) => {
+                if remote.start(NextStage {
+                    start_addr,
+                    opaque,
+                    next_mode: MPP::Supervisor,
+                }) {
+                    unsafe {
+                        PLATFORM.sbi.ipi.as_ref().unwrap().set_msip(hartid);
+                    }
+                    SbiRet::success(0)
+                } else {
+                    SbiRet::already_available()
+                }
+            }
+            None => SbiRet::invalid_param(),
+        }
+    }
+
+    /// Stops execution on the current hart.
+    #[inline]
+    fn hart_stop(&self) -> SbiRet {
+        local_hsm().stop();
+        unsafe {
+            riscv::register::mie::clear_msoft();
+        }
+        riscv::asm::wfi();
+        SbiRet::success(0)
+    }
+
+    /// Gets the current state of a hart.
+    #[inline]
+    fn hart_get_status(&self, hartid: usize) -> SbiRet {
+        match remote_hsm(hartid) {
+            Some(remote) => SbiRet::success(remote.sbi_get_status()),
+            None => SbiRet::invalid_param(),
+        }
+    }
+
+    /// Suspends execution on the current hart.
+    fn hart_suspend(&self, suspend_type: u32, _resume_addr: usize, _opaque: usize) -> SbiRet {
+        use rustsbi::spec::hsm::suspend_type::{NON_RETENTIVE, RETENTIVE};
+        if matches!(suspend_type, NON_RETENTIVE | RETENTIVE) {
+            unsafe {
+                PLATFORM
+                    .sbi
+                    .ipi
+                    .as_ref()
+                    .unwrap()
+                    .clear_msip(current_hartid());
+            }
+            unsafe {
+                riscv::register::mie::set_msoft();
+            }
+            local_hsm().suspend();
+            riscv::asm::wfi();
+            crate::sbi::trap::handler::msoft_ipi_handler();
+            local_hsm().resume();
+            SbiRet::success(0)
+        } else {
+            SbiRet::not_supported()
+        }
+    }
+}

+ 292 - 0
prototyper/prototyper/src/sbi/ipi.rs

@@ -0,0 +1,292 @@
+use crate::platform::PLATFORM;
+use crate::riscv::csr::stimecmp;
+use crate::riscv::current_hartid;
+use crate::sbi::extensions::{Extension, hart_extension_probe};
+use crate::sbi::hsm::remote_hsm;
+use crate::sbi::rfence;
+use crate::sbi::trap_stack::ROOT_STACK;
+use alloc::boxed::Box;
+use core::sync::atomic::Ordering::Relaxed;
+use rustsbi::{HartMask, SbiRet};
+use spin::Mutex;
+
+/// IPI type for supervisor software interrupt.
+pub(crate) const IPI_TYPE_SSOFT: u8 = 1 << 0;
+/// IPI type for memory fence operations.
+pub(crate) const IPI_TYPE_FENCE: u8 = 1 << 1;
+
+/// Trait defining interface for inter-processor interrupt device
+#[allow(unused)]
+pub trait IpiDevice {
+    /// Read machine time value.
+    fn read_mtime(&self) -> u64;
+    /// Write machine time value.
+    fn write_mtime(&self, val: u64);
+    /// Read machine timer compare value for given hart.
+    fn read_mtimecmp(&self, hart_idx: usize) -> u64;
+    /// Write machine timer compare value for given hart.
+    fn write_mtimecmp(&self, hart_idx: usize, val: u64);
+    /// Read machine software interrupt pending bit for given hart.
+    fn read_msip(&self, hart_idx: usize) -> bool;
+    /// Set machine software interrupt pending bit for given hart.
+    fn set_msip(&self, hart_idx: usize);
+    /// Clear machine software interrupt pending bit for given hart.
+    fn clear_msip(&self, hart_idx: usize);
+}
+
+/// SBI IPI implementation.
+pub struct SbiIpi {
+    /// Reference to atomic pointer to IPI device.
+    pub ipi_dev: Mutex<Box<dyn IpiDevice>>,
+    /// Maximum hart ID in the system
+    pub max_hart_id: usize,
+}
+
+impl rustsbi::Timer for SbiIpi {
+    /// Set timer value for current hart.
+    #[inline]
+    fn set_timer(&self, stime_value: u64) {
+        let hart_id = current_hartid();
+        let uses_sstc = hart_extension_probe(hart_id, Extension::Sstc);
+
+        // Set timer value based on extension support.
+        if uses_sstc {
+            stimecmp::set(stime_value);
+        } else {
+            self.write_mtimecmp(hart_id, stime_value);
+            unsafe {
+                riscv::register::mip::clear_stimer();
+            }
+        }
+        // Enable machine timer interrupt.
+        unsafe {
+            riscv::register::mie::set_mtimer();
+        }
+    }
+}
+
+impl rustsbi::Ipi for SbiIpi {
+    /// Send IPI to specified harts.
+    #[inline]
+    fn send_ipi(&self, hart_mask: rustsbi::HartMask) -> SbiRet {
+        let mut hart_mask = hart_mask;
+
+        for hart_id in 0..=self.max_hart_id {
+            if !hart_mask.has_bit(hart_id) {
+                continue;
+            }
+
+            // There are 2 situation to return invalid_param:
+            // 1. We can not get hsm, which usually means this hart_id is bigger than MAX_HART_ID.
+            // 2. BOARD hasn't init or this hart_id is not enabled by device tree.
+            // In the next loop, we'll assume that all of above situation will not happened and
+            // directly send ipi.
+            let Some(hsm) = remote_hsm(hart_id) else {
+                return SbiRet::invalid_param();
+            };
+
+            if unsafe {
+                PLATFORM
+                    .info
+                    .cpu_enabled
+                    .is_none_or(|list| list.get(hart_id).is_none_or(|res| !(*res)))
+            } {
+                return SbiRet::invalid_param();
+            }
+
+            if !hsm.allow_ipi() {
+                hart_mask = hart_mask_clear(hart_mask, hart_id);
+            }
+        }
+        for hart_id in 0..=self.max_hart_id {
+            if !hart_mask.has_bit(hart_id) {
+                continue;
+            }
+
+            if set_ipi_type(hart_id, IPI_TYPE_SSOFT) == 0 {
+                self.set_msip(hart_id);
+            }
+        }
+
+        SbiRet::success(0)
+    }
+}
+
+impl SbiIpi {
+    /// Create new SBI IPI instance.
+    #[inline]
+    pub fn new(ipi_dev: Mutex<Box<dyn IpiDevice>>, max_hart_id: usize) -> Self {
+        Self {
+            ipi_dev,
+            max_hart_id,
+        }
+    }
+
+    /// Send IPI for remote fence operation.
+    pub fn send_ipi_by_fence(
+        &self,
+        hart_mask: rustsbi::HartMask,
+        ctx: rfence::RFenceContext,
+    ) -> SbiRet {
+        let current_hart = current_hartid();
+        let mut hart_mask = hart_mask;
+
+        for hart_id in 0..=self.max_hart_id {
+            if !hart_mask.has_bit(hart_id) {
+                continue;
+            }
+
+            // There are 2 situation to return invalid_param:
+            // 1. We can not get hsm, which usually means this hart_id is bigger than MAX_HART_ID.
+            // 2. BOARD hasn't init or this hart_id is not enabled by device tree.
+            // In the next loop, we'll assume that all of above situation will not happened and
+            // directly send ipi.
+            let Some(hsm) = remote_hsm(hart_id) else {
+                return SbiRet::invalid_param();
+            };
+
+            if unsafe {
+                PLATFORM
+                    .info
+                    .cpu_enabled
+                    .is_none_or(|list| list.get(hart_id).is_none_or(|res| !(*res)))
+            } {
+                return SbiRet::invalid_param();
+            }
+
+            if !hsm.allow_ipi() {
+                hart_mask = hart_mask_clear(hart_mask, hart_id);
+            }
+        }
+
+        // Send fence operations to target harts
+        for hart_id in 0..=self.max_hart_id {
+            if !hart_mask.has_bit(hart_id) {
+                continue;
+            }
+
+            if let Some(remote) = rfence::remote_rfence(hart_id) {
+                if let Some(local) = rfence::local_rfence() {
+                    local.add();
+                }
+                remote.set(ctx);
+                if hart_id != current_hart {
+                    let old_ipi_type = set_ipi_type(hart_id, IPI_TYPE_FENCE);
+                    if old_ipi_type == 0 {
+                        self.set_msip(hart_id);
+                    }
+                }
+            }
+        }
+
+        // Wait for all fence operations to complete
+        while !rfence::local_rfence().unwrap().is_sync() {
+            rfence::rfence_single_handler();
+        }
+
+        SbiRet::success(0)
+    }
+
+    /// Get lower 32 bits of machine time.
+    #[inline]
+    pub fn get_time(&self) -> usize {
+        self.ipi_dev.lock().read_mtime() as usize
+    }
+
+    /// Get upper 32 bits of machine time.
+    #[inline]
+    pub fn get_timeh(&self) -> usize {
+        (self.ipi_dev.lock().read_mtime() >> 32) as usize
+    }
+
+    /// Set machine software interrupt pending for hart.
+    #[inline]
+    pub fn set_msip(&self, hart_idx: usize) {
+        self.ipi_dev.lock().set_msip(hart_idx);
+    }
+
+    /// Clear machine software interrupt pending for hart.
+    #[inline]
+    pub fn clear_msip(&self, hart_idx: usize) {
+        self.ipi_dev.lock().clear_msip(hart_idx);
+    }
+
+    /// Write machine timer compare value for hart.
+    #[inline]
+    pub fn write_mtimecmp(&self, hart_idx: usize, val: u64) {
+        self.ipi_dev.lock().write_mtimecmp(hart_idx, val);
+    }
+
+    /// Clear all pending interrupts for current hart.
+    #[inline]
+    pub fn clear(&self) {
+        let hart_id = current_hartid();
+        // Load ipi_dev once instead of twice
+        let ipi_dev = self.ipi_dev.lock();
+        ipi_dev.clear_msip(hart_id);
+        ipi_dev.write_mtimecmp(hart_id, u64::MAX);
+    }
+}
+
+/// Set IPI type for specified hart.
+pub fn set_ipi_type(hart_id: usize, event_id: u8) -> u8 {
+    unsafe {
+        ROOT_STACK
+            .get_unchecked_mut(hart_id)
+            .hart_context()
+            .ipi_type
+            .fetch_or(event_id, Relaxed)
+    }
+}
+
+/// Get and reset IPI type for current hart.
+pub fn get_and_reset_ipi_type() -> u8 {
+    unsafe {
+        ROOT_STACK
+            .get_unchecked_mut(current_hartid())
+            .hart_context()
+            .ipi_type
+            .swap(0, Relaxed)
+    }
+}
+
+/// Clear machine software interrupt pending for current hart.
+#[inline]
+pub fn clear_msip() {
+    match unsafe { PLATFORM.sbi.ipi.as_ref() } {
+        Some(ipi) => ipi.clear_msip(current_hartid()),
+        None => error!("SBI or IPI device not initialized"),
+    }
+}
+
+/// Clear machine timer interrupt for current hart.
+#[inline]
+pub fn clear_mtime() {
+    match unsafe { PLATFORM.sbi.ipi.as_ref() } {
+        Some(ipi) => ipi.write_mtimecmp(current_hartid(), u64::MAX),
+        None => error!("SBI or IPI device not initialized"),
+    }
+}
+
+/// Clear all pending interrupts for current hart.
+#[inline]
+pub fn clear_all() {
+    match unsafe { PLATFORM.sbi.ipi.as_ref() } {
+        Some(ipi) => ipi.clear(),
+        None => error!("SBI or IPI device not initialized"),
+    }
+}
+
+pub fn hart_mask_clear(hart_mask: HartMask, hart_id: usize) -> HartMask {
+    let (mask, mask_base) = hart_mask.into_inner();
+    if mask_base == usize::MAX {
+        return HartMask::from_mask_base(mask & (!(1 << hart_id)), 0);
+    }
+    let Some(idx) = hart_id.checked_sub(mask_base) else {
+        return hart_mask;
+    };
+    if idx >= usize::BITS as usize {
+        return hart_mask;
+    }
+    HartMask::from_mask_base(mask & (!(1 << hart_id)), mask_base)
+}

+ 55 - 0
prototyper/prototyper/src/sbi/logger.rs

@@ -0,0 +1,55 @@
+use core::str::FromStr;
+use log::{Level, LevelFilter};
+
+/// Simple logger implementation for RustSBI that supports colored output.
+pub struct Logger;
+
+impl Logger {
+    /// Initialize the logger with log level from RUST_LOG env var or default to Info.
+    pub fn init() -> Result<(), log::SetLoggerError> {
+        // Set max log level from RUST_LOG env var if present, otherwise use Info
+        let max_level = option_env!("RUST_LOG")
+            .and_then(|s| LevelFilter::from_str(s).ok())
+            .unwrap_or(LevelFilter::Info);
+
+        log::set_max_level(max_level);
+        log::set_logger(&Logger)
+    }
+}
+
+impl log::Log for Logger {
+    // Always enable logging for all log levels
+    #[inline]
+    fn enabled(&self, _metadata: &log::Metadata) -> bool {
+        true
+    }
+
+    // Log messages with color-coded levels
+    #[inline]
+    fn log(&self, record: &log::Record) {
+        // ANSI color codes for different log levels
+        const ERROR_COLOR: u8 = 31; // Red
+        const WARN_COLOR: u8 = 93; // Bright yellow
+        const INFO_COLOR: u8 = 32; // Green
+        const DEBUG_COLOR: u8 = 36; // Cyan
+        const TRACE_COLOR: u8 = 90; // Bright black
+
+        let color_code = match record.level() {
+            Level::Error => ERROR_COLOR,
+            Level::Warn => WARN_COLOR,
+            Level::Info => INFO_COLOR,
+            Level::Debug => DEBUG_COLOR,
+            Level::Trace => TRACE_COLOR,
+        };
+
+        println!(
+            "\x1b[1;37m[RustSBI] \x1b[1;{color_code}m{:^5}\x1b[0m - {}",
+            record.level(),
+            record.args(),
+        );
+    }
+
+    // No-op flush since we use println! which is already line-buffered
+    #[inline]
+    fn flush(&self) {}
+}

+ 50 - 0
prototyper/prototyper/src/sbi/mod.rs

@@ -0,0 +1,50 @@
+use rustsbi::RustSBI;
+
+pub mod console;
+pub mod hsm;
+pub mod ipi;
+pub mod reset;
+pub mod rfence;
+
+pub mod early_trap;
+pub mod extensions;
+pub mod fifo;
+pub mod hart_context;
+pub mod heap;
+pub mod logger;
+pub mod trap;
+pub mod trap_stack;
+
+use console::SbiConsole;
+use hsm::SbiHsm;
+use ipi::SbiIpi;
+use reset::SbiReset;
+use rfence::SbiRFence;
+
+#[derive(RustSBI, Default)]
+#[rustsbi(dynamic)]
+#[allow(clippy::upper_case_acronyms)]
+pub struct SBI {
+    #[rustsbi(console)]
+    pub console: Option<SbiConsole>,
+    #[rustsbi(ipi, timer)]
+    pub ipi: Option<SbiIpi>,
+    #[rustsbi(hsm)]
+    pub hsm: Option<SbiHsm>,
+    #[rustsbi(reset)]
+    pub reset: Option<SbiReset>,
+    #[rustsbi(fence)]
+    pub rfence: Option<SbiRFence>,
+}
+
+impl SBI {
+    pub const fn new() -> Self {
+        SBI {
+            console: None,
+            ipi: None,
+            hsm: None,
+            reset: None,
+            rfence: None,
+        }
+    }
+}

+ 60 - 0
prototyper/prototyper/src/sbi/reset.rs

@@ -0,0 +1,60 @@
+use alloc::boxed::Box;
+use rustsbi::SbiRet;
+use spin::Mutex;
+
+use crate::platform::PLATFORM;
+
+pub trait ResetDevice {
+    fn fail(&self, code: u16) -> !;
+    fn pass(&self) -> !;
+    fn reset(&self) -> !;
+}
+
+pub struct SbiReset {
+    pub reset_dev: Mutex<Box<dyn ResetDevice>>,
+}
+
+impl SbiReset {
+    pub fn new(reset_dev: Mutex<Box<dyn ResetDevice>>) -> Self {
+        Self { reset_dev }
+    }
+
+    #[allow(unused)]
+    pub fn fail(&self) -> ! {
+        trace!("Test fail, invoke process exit procedure on Reset device");
+        self.reset_dev.lock().fail(0);
+    }
+}
+
+impl rustsbi::Reset for SbiReset {
+    #[inline]
+    fn system_reset(&self, reset_type: u32, reset_reason: u32) -> SbiRet {
+        use rustsbi::spec::srst::{
+            RESET_REASON_NO_REASON, RESET_REASON_SYSTEM_FAILURE, RESET_TYPE_COLD_REBOOT,
+            RESET_TYPE_SHUTDOWN, RESET_TYPE_WARM_REBOOT,
+        };
+        match reset_type {
+            RESET_TYPE_SHUTDOWN => match reset_reason {
+                RESET_REASON_NO_REASON => self.reset_dev.lock().pass(),
+                RESET_REASON_SYSTEM_FAILURE => self.reset_dev.lock().fail(u16::MAX),
+                value => self.reset_dev.lock().fail(value as _),
+            },
+            RESET_TYPE_COLD_REBOOT | RESET_TYPE_WARM_REBOOT => self.reset_dev.lock().reset(),
+
+            _ => SbiRet::invalid_param(),
+        }
+    }
+}
+
+#[allow(unused)]
+pub fn fail() -> ! {
+    match unsafe { PLATFORM.sbi.reset.as_ref() } {
+        Some(reset) => reset.fail(),
+        None => {
+            trace!("test fail, begin dead loop");
+            loop {
+                core::hint::spin_loop()
+            }
+        }
+    }
+}

+ 323 - 0
prototyper/prototyper/src/sbi/rfence.rs

@@ -0,0 +1,323 @@
+use rustsbi::{HartMask, SbiRet};
+use spin::Mutex;
+
+use crate::cfg::{PAGE_SIZE, TLB_FLUSH_LIMIT};
+use crate::platform::PLATFORM;
+use crate::riscv::current_hartid;
+use crate::sbi::fifo::{Fifo, FifoError};
+use crate::sbi::trap_stack::ROOT_STACK;
+use core::arch::asm;
+
+use core::sync::atomic::{AtomicU32, Ordering};
+
+/// Cell for managing remote fence operations between harts.
+pub(crate) struct RFenceCell {
+    // Queue of fence operations with source hart ID
+    queue: Mutex<Fifo<(RFenceContext, usize)>>,
+    // Counter for tracking pending synchronization operations
+    wait_sync_count: AtomicU32,
+}
+
+/// Context information for a remote fence operation.
+#[repr(C)]
+#[derive(Clone, Copy, Debug)]
+pub struct RFenceContext {
+    /// Start address of memory region to fence.
+    pub start_addr: usize,
+    /// Size of memory region to fence.
+    pub size: usize,
+    /// Address space ID.
+    pub asid: usize,
+    /// Virtual machine ID.
+    pub vmid: usize,
+    /// Type of fence operation.
+    pub op: RFenceType,
+}
+
+/// Types of remote fence operations supported.
+#[allow(unused)]
+#[derive(Clone, Copy, Debug)]
+pub enum RFenceType {
+    /// Instruction fence.
+    FenceI,
+    /// Supervisor fence for virtual memory.
+    SFenceVma,
+    /// Supervisor fence for virtual memory with ASID.
+    SFenceVmaAsid,
+    /// Hypervisor fence for guest virtual memory with VMID.
+    HFenceGvmaVmid,
+    /// Hypervisor fence for guest virtual memory.
+    HFenceGvma,
+    /// Hypervisor fence for virtual machine virtual memory with ASID.
+    HFenceVvmaAsid,
+    /// Hypervisor fence for virtual machine virtual memory.
+    HFenceVvma,
+}
+
+impl RFenceCell {
+    /// Creates a new RFenceCell with empty queue and zero sync count.
+    pub fn new() -> Self {
+        Self {
+            queue: Mutex::new(Fifo::new()),
+            wait_sync_count: AtomicU32::new(0),
+        }
+    }
+
+    /// Gets a local view of this fence cell for the current hart.
+    #[inline]
+    pub fn local(&self) -> LocalRFenceCell<'_> {
+        LocalRFenceCell(self)
+    }
+
+    /// Gets a remote view of this fence cell for accessing from other harts.
+    #[inline]
+    pub fn remote(&self) -> RemoteRFenceCell<'_> {
+        RemoteRFenceCell(self)
+    }
+}
+
+// Mark RFenceCell as safe to share between threads
+unsafe impl Sync for RFenceCell {}
+unsafe impl Send for RFenceCell {}
+
+/// View of RFenceCell for operations on the current hart.
+pub struct LocalRFenceCell<'a>(&'a RFenceCell);
+
+/// View of RFenceCell for operations from other harts.
+pub struct RemoteRFenceCell<'a>(&'a RFenceCell);
+
+/// Gets the local fence context for the current hart.
+pub(crate) fn local_rfence() -> Option<LocalRFenceCell<'static>> {
+    unsafe {
+        ROOT_STACK
+            .get_mut(current_hartid())
+            .map(|x| x.hart_context().rfence.local())
+    }
+}
+
+/// Gets the remote fence context for a specific hart.
+pub(crate) fn remote_rfence(hart_id: usize) -> Option<RemoteRFenceCell<'static>> {
+    unsafe {
+        ROOT_STACK
+            .get_mut(hart_id)
+            .map(|x| x.hart_context().rfence.remote())
+    }
+}
+
+#[allow(unused)]
+impl LocalRFenceCell<'_> {
+    /// Checks if all synchronization operations are complete.
+    pub fn is_sync(&self) -> bool {
+        self.0.wait_sync_count.load(Ordering::Relaxed) == 0
+    }
+
+    /// Increments the synchronization counter.
+    pub fn add(&self) {
+        self.0.wait_sync_count.fetch_add(1, Ordering::Relaxed);
+    }
+
+    /// Checks if the operation queue is empty.
+    pub fn is_empty(&self) -> bool {
+        self.0.queue.lock().is_empty()
+    }
+
+    /// Gets the next fence operation from the queue.
+    pub fn get(&self) -> Option<(RFenceContext, usize)> {
+        self.0.queue.lock().pop().ok()
+    }
+
+    /// Adds a fence operation to the queue, retrying if full.
+    pub fn set(&self, ctx: RFenceContext) {
+        let hart_id = current_hartid();
+        loop {
+            let mut queue = self.0.queue.lock();
+            match queue.push((ctx, hart_id)) {
+                Ok(_) => break,
+                Err(FifoError::Full) => {
+                    drop(queue);
+                    rfence_single_handler();
+                }
+                Err(_) => panic!("Unable to push fence ops to fifo"),
+            }
+        }
+    }
+}
+
+#[allow(unused)]
+impl RemoteRFenceCell<'_> {
+    /// Adds a fence operation to the queue from a remote hart.
+    pub fn set(&self, ctx: RFenceContext) {
+        let hart_id = current_hartid();
+        loop {
+            let mut queue = self.0.queue.lock();
+            match queue.push((ctx, hart_id)) {
+                Ok(_) => return,
+                Err(FifoError::Full) => {
+                    drop(queue);
+                    rfence_single_handler();
+                }
+                Err(_) => panic!("Unable to push fence ops to fifo"),
+            }
+        }
+    }
+
+    /// Decrements the synchronization counter.
+    pub fn sub(&self) {
+        self.0.wait_sync_count.fetch_sub(1, Ordering::Relaxed);
+    }
+}
+
+/// Implementation of RISC-V remote fence operations.
+pub(crate) struct SbiRFence;
+
+/// Validates address range for fence operations
+#[inline(always)]
+fn validate_address_range(start_addr: usize, size: usize) -> Result<usize, SbiRet> {
+    // Check page alignment using bitwise AND instead of modulo
+    if start_addr & 0xFFF != 0 {
+        return Err(SbiRet::invalid_address());
+    }
+
+    // Avoid checked_add by checking for overflow directly
+    if size > usize::MAX - start_addr {
+        return Err(SbiRet::invalid_address());
+    }
+
+    Ok(size)
+}
+
+/// Processes a remote fence operation by sending IPI to target harts.
+fn remote_fence_process(rfence_ctx: RFenceContext, hart_mask: HartMask) -> SbiRet {
+    let sbi_ret = unsafe { PLATFORM.sbi.ipi.as_ref() }
+        .unwrap()
+        .send_ipi_by_fence(hart_mask, rfence_ctx);
+
+    sbi_ret
+}
+
+impl rustsbi::Fence for SbiRFence {
+    /// Remote instruction fence for specified harts.
+    fn remote_fence_i(&self, hart_mask: HartMask) -> SbiRet {
+        remote_fence_process(
+            RFenceContext {
+                start_addr: 0,
+                size: 0,
+                asid: 0,
+                vmid: 0,
+                op: RFenceType::FenceI,
+            },
+            hart_mask,
+        )
+    }
+
+    /// Remote supervisor fence for virtual memory on specified harts.
+    fn remote_sfence_vma(&self, hart_mask: HartMask, start_addr: usize, size: usize) -> SbiRet {
+        let flush_size = match validate_address_range(start_addr, size) {
+            Ok(size) => size,
+            Err(e) => return e,
+        };
+
+        remote_fence_process(
+            RFenceContext {
+                start_addr,
+                size: flush_size,
+                asid: 0,
+                vmid: 0,
+                op: RFenceType::SFenceVma,
+            },
+            hart_mask,
+        )
+    }
+
+    /// Remote supervisor fence for virtual memory with ASID on specified harts.
+    fn remote_sfence_vma_asid(
+        &self,
+        hart_mask: HartMask,
+        start_addr: usize,
+        size: usize,
+        asid: usize,
+    ) -> SbiRet {
+        let flush_size = match validate_address_range(start_addr, size) {
+            Ok(size) => size,
+            Err(e) => return e,
+        };
+
+        remote_fence_process(
+            RFenceContext {
+                start_addr,
+                size: flush_size,
+                asid,
+                vmid: 0,
+                op: RFenceType::SFenceVmaAsid,
+            },
+            hart_mask,
+        )
+    }
+}
+
+/// Handles a single remote fence operation.
+#[inline]
+pub fn rfence_single_handler() {
+    let rfence_context = local_rfence().unwrap().get();
+    if let Some((ctx, id)) = rfence_context {
+        match ctx.op {
+            // Handle instruction fence
+            RFenceType::FenceI => unsafe {
+                asm!("fence.i");
+                remote_rfence(id).unwrap().sub();
+            },
+            // Handle virtual memory address fence
+            RFenceType::SFenceVma => {
+                // If the flush size is greater than the maximum limit then simply flush all
+                if (ctx.start_addr == 0 && ctx.size == 0)
+                    || (ctx.size == usize::MAX)
+                    || (ctx.size > TLB_FLUSH_LIMIT)
+                {
+                    unsafe {
+                        asm!("sfence.vma");
+                    }
+                } else {
+                    for offset in (0..ctx.size).step_by(PAGE_SIZE) {
+                        let addr = ctx.start_addr + offset;
+                        unsafe {
+                            asm!("sfence.vma {}", in(reg) addr);
+                        }
+                    }
+                }
+                remote_rfence(id).unwrap().sub();
+            }
+            // Handle virtual memory address fence with ASID
+            RFenceType::SFenceVmaAsid => {
+                let asid = ctx.asid;
+                // If the flush size is greater than the maximum limit then simply flush all
+                if (ctx.start_addr == 0 && ctx.size == 0)
+                    || (ctx.size == usize::MAX)
+                    || (ctx.size > TLB_FLUSH_LIMIT)
+                {
+                    unsafe {
+                        asm!("sfence.vma {}, {}", in(reg) 0, in(reg) asid);
+                    }
+                } else {
+                    for offset in (0..ctx.size).step_by(PAGE_SIZE) {
+                        let addr = ctx.start_addr + offset;
+                        unsafe {
+                            asm!("sfence.vma {}, {}", in(reg) addr, in(reg) asid);
+                        }
+                    }
+                }
+                remote_rfence(id).unwrap().sub();
+            }
+            rfencetype => {
+                error!("Unsupported RFence Type: {:?}!", rfencetype);
+            }
+        }
+    }
+}
+
+/// Process all pending remote fence operations.
+#[inline]
+pub fn rfence_handler() {
+    while !local_rfence().unwrap().is_empty() {
+        rfence_single_handler();
+    }
+}

+ 86 - 0
prototyper/prototyper/src/sbi/trap/boot.rs

@@ -0,0 +1,86 @@
+use crate::riscv::current_hartid;
+use crate::sbi::hsm::local_hsm;
+use crate::sbi::ipi;
+use core::arch::naked_asm;
+use riscv::register::{mie, mstatus, satp, sstatus};
+
+/// Boot Function.
+/// After boot, this flow will never back again,
+/// so we can store a0, a1 and mepc only.
+#[naked]
+pub unsafe extern "C" fn boot() -> ! {
+    unsafe {
+        naked_asm!(
+            ".align 2",
+            // Switch stacks
+            "csrrw  sp, mscratch, sp",
+            // Allocate stack space
+            "addi   sp, sp, -3*8",
+            // Call handler with context pointer
+            "mv     a0, sp",
+            "call   {boot_handler}",
+            // Restore mepc
+            "ld     t0, 0*8(sp)
+            csrw    mepc, t0",
+            // Restore registers
+            "
+        ld      a0, 1*8(sp)
+        ld      a1, 2*8(sp)",
+            // Restore stack pointer
+            "addi   sp, sp, 3*8",
+            // Switch stacks back
+            "csrrw  sp, mscratch, sp",
+            // Return from machine mode
+            "mret",
+            boot_handler = sym boot_handler,
+        );
+    }
+}
+
+/// Boot Handler.
+pub extern "C" fn boot_handler(ctx: &mut BootContext) {
+    #[inline(always)]
+    fn boot(ctx: &mut BootContext, start_addr: usize, opaque: usize) {
+        unsafe {
+            sstatus::clear_sie();
+            satp::write(0);
+        }
+        ctx.a0 = current_hartid();
+        ctx.a1 = opaque;
+        ctx.mepc = start_addr;
+    }
+
+    match local_hsm().start() {
+        // Handle HSM Start
+        Ok(next_stage) => {
+            ipi::clear_msip();
+            unsafe {
+                mstatus::set_mpie();
+                mstatus::set_mpp(next_stage.next_mode);
+                mie::set_msoft();
+                mie::set_mtimer();
+            }
+            boot(ctx, next_stage.start_addr, next_stage.opaque);
+        }
+        // Handle HSM Stop
+        Err(rustsbi::spec::hsm::HART_STOP) => {
+            ipi::clear_msip();
+            unsafe {
+                mie::set_msoft();
+            }
+            riscv::asm::wfi();
+        }
+        _ => {
+            unreachable!("Boot stage hsm should be start or stop.");
+        }
+    }
+}
+
+/// Boot context structure containing saved register state.
+#[derive(Debug)]
+#[repr(C)]
+pub struct BootContext {
+    pub mepc: usize, // 0
+    pub a0: usize,
+    pub a1: usize, // 2
+}

+ 182 - 0
prototyper/prototyper/src/sbi/trap/handler.rs

@@ -0,0 +1,182 @@
+use fast_trap::{FastContext, FastResult};
+use riscv::register::{mepc, mie, mstatus, satp, sstatus};
+use rustsbi::RustSBI;
+
+use crate::platform::PLATFORM;
+use crate::riscv::csr::{CSR_TIME, CSR_TIMEH};
+use crate::riscv::current_hartid;
+use crate::sbi::console;
+use crate::sbi::hsm::local_hsm;
+use crate::sbi::ipi;
+use crate::sbi::rfence;
+
+#[inline]
+pub fn switch(mut ctx: FastContext, start_addr: usize, opaque: usize) -> FastResult {
+    unsafe {
+        sstatus::clear_sie();
+        satp::write(0);
+    }
+
+    ctx.regs().a[0] = current_hartid();
+    ctx.regs().a[1] = opaque;
+    ctx.regs().pc = start_addr;
+    ctx.call(2)
+}
+
+/// Handle machine software inter-processor interrupts.
+#[inline]
+pub fn msoft_ipi_handler() {
+    use ipi::get_and_reset_ipi_type;
+    ipi::clear_msip();
+    let ipi_type = get_and_reset_ipi_type();
+    // Handle supervisor software interrupt
+    if (ipi_type & ipi::IPI_TYPE_SSOFT) != 0 {
+        unsafe {
+            riscv::register::mip::set_ssoft();
+        }
+    }
+    // Handle fence operation
+    if (ipi_type & ipi::IPI_TYPE_FENCE) != 0 {
+        rfence::rfence_handler();
+    }
+}
+
+#[inline]
+pub fn msoft_handler(ctx: FastContext) -> FastResult {
+    match local_hsm().start() {
+        // Handle HSM Start
+        Ok(next_stage) => {
+            ipi::clear_msip();
+            unsafe {
+                mstatus::set_mpie();
+                mstatus::set_mpp(next_stage.next_mode);
+                mie::set_msoft();
+                mie::set_mtimer();
+            }
+            switch(ctx, next_stage.start_addr, next_stage.opaque)
+        }
+        // Handle HSM Stop
+        Err(rustsbi::spec::hsm::HART_STOP) => {
+            ipi::clear_msip();
+            unsafe {
+                mie::set_msoft();
+            }
+            riscv::asm::wfi();
+            ctx.restore()
+        }
+        // Handle RFence
+        _ => {
+            msoft_ipi_handler();
+            ctx.restore()
+        }
+    }
+}
+
+#[inline]
+#[allow(clippy::too_many_arguments)]
+pub fn sbi_call_handler(
+    mut ctx: FastContext,
+    a1: usize,
+    a2: usize,
+    a3: usize,
+    a4: usize,
+    a5: usize,
+    a6: usize,
+    a7: usize,
+) -> FastResult {
+    use sbi_spec::{base, hsm, legacy};
+    let mut ret = unsafe {
+        PLATFORM
+            .sbi
+            .handle_ecall(a7, a6, [ctx.a0(), a1, a2, a3, a4, a5])
+    };
+    if ret.is_ok() {
+        match (a7, a6) {
+            // Handle non-retentive suspend
+            (hsm::EID_HSM, hsm::HART_SUSPEND)
+                if matches!(ctx.a0() as u32, hsm::suspend_type::NON_RETENTIVE) =>
+            {
+                return switch(ctx, a1, a2);
+            }
+            // Handle legacy console probe
+            (base::EID_BASE, base::PROBE_EXTENSION)
+                if matches!(
+                    ctx.a0(),
+                    legacy::LEGACY_CONSOLE_PUTCHAR | legacy::LEGACY_CONSOLE_GETCHAR
+                ) =>
+            {
+                ret.value = 1;
+            }
+            _ => {}
+        }
+    } else {
+        match a7 {
+            legacy::LEGACY_CONSOLE_PUTCHAR => {
+                ret.error = console::putchar(ctx.a0());
+                ret.value = a1;
+            }
+            legacy::LEGACY_CONSOLE_GETCHAR => {
+                ret.error = console::getchar();
+                ret.value = a1;
+            }
+            _ => {}
+        }
+    }
+    ctx.regs().a = [ret.error, ret.value, a2, a3, a4, a5, a6, a7];
+    mepc::write(mepc::read() + 4);
+    ctx.restore()
+}
+
+/// Delegate trap handling to supervisor mode.
+#[inline]
+pub fn delegate(ctx: &mut FastContext) {
+    use riscv::register::{mcause, mepc, mtval, scause, sepc, sstatus, stval, stvec};
+    unsafe {
+        sepc::write(ctx.regs().pc);
+        scause::write(mcause::read().bits());
+        stval::write(mtval::read());
+        sstatus::clear_sie();
+        if mstatus::read().mpp() == mstatus::MPP::Supervisor {
+            sstatus::set_spp(sstatus::SPP::Supervisor);
+        } else {
+            sstatus::set_spp(sstatus::SPP::User);
+        }
+        mstatus::set_mpp(mstatus::MPP::Supervisor);
+        mepc::write(stvec::read().address());
+    }
+}
+
+/// Handle illegal instructions, particularly CSR access.
+#[inline]
+pub fn illegal_instruction_handler(ctx: &mut FastContext) -> bool {
+    use riscv::register::{mepc, mtval};
+    use riscv_decode::{Instruction, decode};
+
+    let inst = decode(mtval::read() as u32);
+    match inst {
+        Ok(Instruction::Csrrs(csr)) => match csr.csr() {
+            CSR_TIME => {
+                assert!(
+                    10 <= csr.rd() && csr.rd() <= 17,
+                    "Unsupported CSR rd: {}",
+                    csr.rd()
+                );
+                ctx.regs().a[(csr.rd() - 10) as usize] =
+                    unsafe { PLATFORM.sbi.ipi.as_ref() }.unwrap().get_time();
+            }
+            CSR_TIMEH => {
+                assert!(
+                    10 <= csr.rd() && csr.rd() <= 17,
+                    "Unsupported CSR rd: {}",
+                    csr.rd()
+                );
+                ctx.regs().a[(csr.rd() - 10) as usize] =
+                    unsafe { PLATFORM.sbi.ipi.as_ref() }.unwrap().get_timeh();
+            }
+            _ => return false,
+        },
+        _ => return false,
+    }
+    mepc::write(mepc::read() + 4);
+    true
+}

+ 75 - 0
prototyper/prototyper/src/sbi/trap/mod.rs

@@ -0,0 +1,75 @@
+pub mod boot;
+pub mod handler;
+
+use crate::fail::unsupported_trap;
+
+use fast_trap::{FastContext, FastResult};
+use riscv::interrupt::machine::{Exception, Interrupt};
+use riscv::register::{
+    mcause::{self, Trap},
+    mepc, mip, mstatus,
+};
+
+/// Fast trap handler for all trap.
+pub extern "C" fn fast_handler(
+    mut ctx: FastContext,
+    a1: usize,
+    a2: usize,
+    a3: usize,
+    a4: usize,
+    a5: usize,
+    a6: usize,
+    a7: usize,
+) -> FastResult {
+    // Save mepc into context
+    ctx.regs().pc = mepc::read();
+
+    let save_regs = |ctx: &mut FastContext| {
+        ctx.regs().a = [ctx.a0(), a1, a2, a3, a4, a5, a6, a7];
+    };
+
+    match mcause::read().cause().try_into() {
+        Ok(cause) => {
+            match cause {
+                // Handle Msoft
+                Trap::Interrupt(Interrupt::MachineSoft) => {
+                    save_regs(&mut ctx);
+                    handler::msoft_handler(ctx)
+                }
+                // Handle MTimer
+                Trap::Interrupt(Interrupt::MachineTimer) => {
+                    use crate::sbi::ipi;
+
+                    ipi::clear_mtime();
+                    unsafe {
+                        mip::clear_stimer();
+                    }
+                    save_regs(&mut ctx);
+                    ctx.restore()
+                }
+                // Handle SBI calls
+                Trap::Exception(Exception::SupervisorEnvCall) => {
+                    handler::sbi_call_handler(ctx, a1, a2, a3, a4, a5, a6, a7)
+                }
+                // Handle illegal instructions
+                Trap::Exception(Exception::IllegalInstruction) => {
+                    if mstatus::read().mpp() == mstatus::MPP::Machine {
+                        panic!("Cannot handle illegal instruction exception from M-MODE");
+                    }
+
+                    save_regs(&mut ctx);
+                    if !handler::illegal_instruction_handler(&mut ctx) {
+                        handler::delegate(&mut ctx);
+                    }
+                    ctx.restore()
+                }
+                // Handle other traps
+                trap => unsupported_trap(Some(trap)),
+            }
+        }
+        Err(err) => {
+            error!("Failed to parse mcause: {:?}", err);
+            unsupported_trap(None);
+        }
+    }
+}

+ 88 - 0
prototyper/prototyper/src/sbi/trap_stack.rs

@@ -0,0 +1,88 @@
+use crate::cfg::{LEN_STACK_PER_HART, NUM_HART_MAX};
+use crate::riscv::current_hartid;
+use crate::sbi::hart_context::HartContext;
+use crate::sbi::trap::fast_handler;
+use core::mem::forget;
+use fast_trap::FreeTrapStack;
+
+/// Root stack array for all harts, placed in BSS Stack section.
+#[unsafe(link_section = ".bss.stack")]
+pub(crate) static mut ROOT_STACK: [Stack; NUM_HART_MAX] = [Stack::ZERO; NUM_HART_MAX];
+
+/// Locates and initializes stack for each hart.
+///
+/// This is a naked function that sets up the stack pointer based on hart ID.
+#[naked]
+pub(crate) unsafe extern "C" fn locate() {
+    unsafe {
+        core::arch::naked_asm!(
+            "   la   sp, {stack}            // Load stack base address
+            li   t0, {per_hart_stack_size} // Load stack size per hart
+            csrr t1, mhartid            // Get current hart ID
+            addi t1, t1,  1             // Add 1 to hart ID
+         1: add  sp, sp, t0             // Calculate stack pointer
+            addi t1, t1, -1             // Decrement counter
+            bnez t1, 1b                 // Loop if not zero
+            call t1, {move_stack}       // Call stack reuse function
+            ret                         // Return
+        ",
+            per_hart_stack_size = const LEN_STACK_PER_HART,
+            stack               =   sym ROOT_STACK,
+            move_stack          =   sym fast_trap::reuse_stack_for_trap,
+        )
+    }
+}
+
+/// Prepares trap stack for current hart
+pub(crate) fn prepare_for_trap() {
+    unsafe {
+        ROOT_STACK
+            .get_unchecked_mut(current_hartid())
+            .load_as_stack()
+    };
+}
+
+/// Stack type for each hart.
+///
+/// Memory layout:
+/// - Bottom: HartContext struct.
+/// - Middle: Stack space for the hart.
+/// - Top: Trap handling space.
+///
+/// Each hart has a single stack that contains both its context and working space.
+#[repr(C, align(128))]
+pub(crate) struct Stack([u8; LEN_STACK_PER_HART]);
+
+impl Stack {
+    const ZERO: Self = Self([0; LEN_STACK_PER_HART]);
+
+    /// Gets mutable reference to hart context at bottom of stack.
+    #[inline]
+    pub fn hart_context(&mut self) -> &mut HartContext {
+        unsafe { &mut *self.0.as_mut_ptr().cast() }
+    }
+
+    /// Initializes stack for trap handling.
+    /// - Sets up hart context.
+    /// - Creates and loads FreeTrapStack with the stack range.
+    fn load_as_stack(&'static mut self) {
+        let hart = self.hart_context();
+        let context_ptr = hart.context_ptr();
+        hart.init();
+
+        // Get stack memory range.
+        let range = self.0.as_ptr_range();
+
+        // Create and load trap stack, forgetting it to avoid drop
+        forget(
+            FreeTrapStack::new(
+                range.start as usize..range.end as usize,
+                |_| {}, // Empty callback
+                context_ptr,
+                fast_handler,
+            )
+            .unwrap()
+            .load(),
+        );
+    }
+}

+ 26 - 0
prototyper/test-kernel/Cargo.toml

@@ -0,0 +1,26 @@
+cargo-features = ["per-package-target"]
+
+[package]
+name = "rustsbi-test-kernel"
+version = "0.0.0"
+edition.workspace = true
+license.workspace = true
+repository.workspace = true
+forced-target = "riscv64imac-unknown-none-elf"
+publish = false
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+sbi-testing = { git = "https://github.com/rustsbi/rustsbi" , features = ["log"] }
+log = "0.4"
+riscv = "0.11.1"
+spin = "0.9"
+uart16550 = "0.0.1"
+rcore-console = "0.0.0"
+dtb-walker = "=0.2.0-alpha.3"
+
+[[bin]]
+name = "rustsbi-test-kernel"
+test = false
+bench = false

+ 52 - 0
prototyper/test-kernel/build.rs

@@ -0,0 +1,52 @@
+use std::{env, path::PathBuf};
+
+fn main() {
+    let out = PathBuf::from(env::var_os("OUT_DIR").unwrap());
+    let ld = &out.join("rustsbi-test-kernel.ld");
+
+    std::fs::write(ld, LINKER_SCRIPT).unwrap();
+
+    println!("cargo:rustc-link-arg=-T{}", ld.display());
+    println!("cargo:rustc-link-search={}", out.display());
+}
+
+const LINKER_SCRIPT: &[u8] = b"OUTPUT_ARCH(riscv)
+ENTRY(_start) 
+SECTIONS {
+    . = 0x80200000;
+    istart = .;
+	  .head.text : ALIGN(8) {		
+        KEEP(*(.head.text))
+	  }
+
+    .text : ALIGN(8) { 
+        *(.text.entry)
+        *(.text .text.*)
+    }
+    .rodata : ALIGN(8) { 
+        srodata = .;
+        *(.rodata .rodata.*)
+        *(.srodata .srodata.*)
+        . = ALIGN(8);  
+        erodata = .;
+    } 
+    .data : ALIGN(8) { 
+        sdata = .;
+        *(.data .data.*)
+        *(.sdata .sdata.*)
+        . = ALIGN(8); 
+        edata = .;
+    }
+    sidata = LOADADDR(.data);
+    .bss (NOLOAD) : ALIGN(8) {  
+        *(.bss.uninit)
+        sbss = .;
+        *(.bss .bss.*)
+        *(.sbss .sbss.*)
+        ebss = .;
+    } 
+    iend = .;
+    /DISCARD/ : {
+        *(.eh_frame)
+    }
+}";

+ 44 - 0
prototyper/test-kernel/scripts/rustsbi-test-kernel.its

@@ -0,0 +1,44 @@
+/*
+ * Configuration to load RustSBI before RustSBI Test Kernel
+ */
+ 
+/dts-v1/;
+
+/ {
+			description = "Configuration to load RustSBI before RustSBI Test Kernel";
+
+			images {
+				kernel {
+					description = "rustsbi-test-kernel";
+					data = /incbin/("./rustsbi-test-kernel.bin");
+					type = "standalone";
+					os = "u-boot";
+					arch = "riscv";
+					compression = "none";
+					load = /bits/ 64 <0x80200000>;
+				};
+
+				rustsbi {
+					description = "RustSBI Firmware";
+					data = /incbin/("./rustsbi-prototyper.bin");
+					type = "firmware";
+					os = "opensbi";
+					arch = "riscv";
+					compression = "none";
+					load = /bits/ 64 <0x80100000>;
+					entry = /bits/ 64 <0x80100000>;
+				};
+
+			};
+
+		configurations {
+				default = "conf-1";
+
+				conf-1 {
+					description = "RustSBI & RustSBI Test Kernel";
+					firmware = "rustsbi";
+					loadables = "kernel";
+				};
+		};
+};
+

+ 213 - 0
prototyper/test-kernel/src/main.rs

@@ -0,0 +1,213 @@
+#![no_std]
+#![no_main]
+#![feature(naked_functions)]
+#![allow(static_mut_refs)]
+
+#[macro_use]
+extern crate rcore_console;
+
+use core::{
+    arch::{asm, naked_asm},
+    ptr::null,
+};
+use sbi_testing::sbi;
+use uart16550::Uart16550;
+
+const RISCV_HEAD_FLAGS: u64 = 0;
+const RISCV_HEADER_VERSION: u32 = 0x2;
+const RISCV_IMAGE_MAGIC: u64 = 0x5643534952; /* Magic number, little endian, "RISCV" */
+const RISCV_IMAGE_MAGIC2: u32 = 0x05435352; /* Magic number 2, little endian, "RSC\x05" */
+
+/// boot header
+#[naked]
+#[unsafe(no_mangle)]
+#[unsafe(link_section = ".head.text")]
+unsafe extern "C" fn _boot_header() -> ! {
+    unsafe {
+        naked_asm!(
+            "j _start",
+            ".word 0",
+            ".balign 8",
+            ".dword 0x200000",
+            ".dword iend - istart",
+            ".dword {RISCV_HEAD_FLAGS}",
+            ".word  {RISCV_HEADER_VERSION}",
+            ".word  0",
+            ".dword 0",
+            ".dword {RISCV_IMAGE_MAGIC}",
+            ".balign 4",
+            ".word  {RISCV_IMAGE_MAGIC2}",
+            ".word  0",
+            RISCV_HEAD_FLAGS = const RISCV_HEAD_FLAGS,
+            RISCV_HEADER_VERSION = const RISCV_HEADER_VERSION,
+            RISCV_IMAGE_MAGIC = const RISCV_IMAGE_MAGIC,
+            RISCV_IMAGE_MAGIC2 = const RISCV_IMAGE_MAGIC2,
+        );
+    }
+}
+
+/// 内核入口。
+///
+/// # Safety
+///
+/// 裸函数。
+#[naked]
+#[unsafe(no_mangle)]
+#[unsafe(link_section = ".text.entry")]
+unsafe extern "C" fn _start(hartid: usize, device_tree_paddr: usize) -> ! {
+    const STACK_SIZE: usize = 16384; // 16 KiB
+
+    #[unsafe(link_section = ".bss.uninit")]
+    static mut STACK: [u8; STACK_SIZE] = [0u8; STACK_SIZE];
+
+    unsafe {
+        naked_asm!(
+            // clear bss segment
+            "   la      t0, sbss
+            la      t1, ebss
+        1:  bgeu    t0, t1, 2f
+            sd      zero, 0(t0)
+            addi    t0, t0, 8
+            j       1b",
+            "2:",
+            "   la sp, {stack} + {stack_size}",
+            "   j  {main}",
+            stack_size = const STACK_SIZE,
+            stack      =   sym STACK,
+            main       =   sym rust_main,
+        )
+    }
+}
+
+extern "C" fn rust_main(hartid: usize, dtb_pa: usize) -> ! {
+    let BoardInfo {
+        smp,
+        frequency,
+        uart,
+    } = BoardInfo::parse(dtb_pa);
+    unsafe { UART = Uart16550Map(uart as _) };
+    rcore_console::init_console(&Console);
+    rcore_console::set_log_level(option_env!("LOG"));
+    println!(
+        r"
+ _____         _     _  __                    _
+|_   _|__  ___| |_  | |/ /___ _ __ _ __   ___| |
+  | |/ _ \/ __| __| | ' // _ \ '__| '_ \ / _ \ |
+  | |  __/\__ \ |_  | . \  __/ |  | | | |  __/ |
+  |_|\___||___/\__| |_|\_\___|_|  |_| |_|\___|_|
+================================================
+| boot hart id          | {hartid:20} |
+| smp                   | {smp:20} |
+| timebase frequency    | {frequency:17} Hz |
+| dtb physical address  | {dtb_pa:#20x} |
+------------------------------------------------"
+    );
+    let testing = sbi_testing::Testing {
+        hartid,
+        hart_mask: (1 << smp) - 1,
+        hart_mask_base: 0,
+        delay: frequency,
+    };
+    if testing.test() {
+        sbi::system_reset(sbi::Shutdown, sbi::NoReason);
+    } else {
+        sbi::system_reset(sbi::Shutdown, sbi::SystemFailure);
+    }
+    unreachable!()
+}
+
+#[cfg_attr(not(test), panic_handler)]
+fn panic(info: &core::panic::PanicInfo) -> ! {
+    let (hart_id, pc): (usize, usize);
+    unsafe { asm!("mv    {}, tp", out(reg) hart_id) };
+    unsafe { asm!("auipc {},  0", out(reg) pc) };
+    println!("[test-kernel-panic] hart {hart_id} {info}");
+    println!("[test-kernel-panic] pc = {pc:#x}");
+    println!("[test-kernel-panic] SBI test FAILED due to panic");
+    sbi::system_reset(sbi::Shutdown, sbi::SystemFailure);
+    loop {}
+}
+
+struct BoardInfo {
+    smp: usize,
+    frequency: u64,
+    uart: usize,
+}
+
+impl BoardInfo {
+    fn parse(dtb_pa: usize) -> Self {
+        use dtb_walker::{Dtb, DtbObj, HeaderError as E, Property, Str, WalkOperation::*};
+
+        let mut ans = Self {
+            smp: 0,
+            frequency: 0,
+            uart: 0,
+        };
+        unsafe {
+            Dtb::from_raw_parts_filtered(dtb_pa as _, |e| {
+                matches!(e, E::Misaligned(4) | E::LastCompVersion(_))
+            })
+        }
+        .unwrap()
+        .walk(|ctx, obj| match obj {
+            DtbObj::SubNode { name } => {
+                if ctx.is_root() && (name == Str::from("cpus") || name == Str::from("soc")) {
+                    StepInto
+                } else if ctx.name() == Str::from("cpus") && name.starts_with("cpu@") {
+                    ans.smp += 1;
+                    StepOver
+                } else if ctx.name() == Str::from("soc")
+                    && (name.starts_with("uart") || name.starts_with("serial"))
+                {
+                    StepInto
+                } else {
+                    StepOver
+                }
+            }
+            DtbObj::Property(Property::Reg(mut reg)) => {
+                if ctx.name().starts_with("uart") || ctx.name().starts_with("serial") {
+                    ans.uart = reg.next().unwrap().start;
+                }
+                StepOut
+            }
+            DtbObj::Property(Property::General { name, value }) => {
+                if ctx.name() == Str::from("cpus") && name == Str::from("timebase-frequency") {
+                    ans.frequency = match *value {
+                        [a, b, c, d] => u32::from_be_bytes([a, b, c, d]) as _,
+                        [a, b, c, d, e, f, g, h] => u64::from_be_bytes([a, b, c, d, e, f, g, h]),
+                        _ => unreachable!(),
+                    };
+                }
+                StepOver
+            }
+            DtbObj::Property(_) => StepOver,
+        });
+        ans
+    }
+}
+
+struct Console;
+static mut UART: Uart16550Map = Uart16550Map(null());
+
+pub struct Uart16550Map(*const Uart16550<u8>);
+
+unsafe impl Sync for Uart16550Map {}
+
+impl Uart16550Map {
+    #[inline]
+    pub fn get(&self) -> &Uart16550<u8> {
+        unsafe { &*self.0 }
+    }
+}
+
+impl rcore_console::Console for Console {
+    #[inline]
+    fn put_char(&self, c: u8) {
+        unsafe { UART.get().write(core::slice::from_ref(&c)) };
+    }
+
+    #[inline]
+    fn put_str(&self, s: &str) {
+        unsafe { UART.get().write(s.as_bytes()) };
+    }
+}

+ 5 - 0
rust-toolchain.toml

@@ -0,0 +1,5 @@
+[toolchain]
+channel = "nightly-2025-02-08"
+components = ["rustfmt", "llvm-tools-preview", "clippy", "rust-src"]
+targets = ["riscv64imac-unknown-none-elf"]
+profile = "minimal"

+ 11 - 0
xtask/Cargo.toml

@@ -0,0 +1,11 @@
+[package]
+name = "xtask"
+version = "0.1.0"
+edition.workspace = true
+license.workspace = true
+repository.workspace = true
+
+[dependencies]
+clap = { version = "4.5.4", features = ["derive", "env", "suggestions"] }
+log = "0.4.21"
+clap-verbosity-flag = "3.0.2"

+ 80 - 0
xtask/src/bench.rs

@@ -0,0 +1,80 @@
+use std::{
+    env, fs,
+    process::{Command, ExitStatus},
+};
+
+use clap::Args;
+
+use crate::utils::cargo;
+
+#[derive(Debug, Args, Clone)]
+pub struct BenchArg {
+    /// Package Prototyper and Test-Kernel
+    #[clap(long)]
+    pub pack: bool,
+}
+
+#[must_use]
+pub fn run(arg: &BenchArg) -> Option<ExitStatus> {
+    let arch = "riscv64imac-unknown-none-elf";
+    let current_dir = env::current_dir();
+    let target_dir = current_dir
+        .as_ref()
+        .unwrap()
+        .join("target")
+        .join(arch)
+        .join("release");
+
+    info!("Building bench kernel");
+    cargo::Cargo::new("build")
+        .package("rustsbi-bench-kernel")
+        .target(arch)
+        .release()
+        .status()
+        .ok()?;
+
+    info!("Copy to binary");
+    let exit_status = Command::new("rust-objcopy")
+        .args(["-O", "binary"])
+        .arg("--binary-architecture=riscv64")
+        .arg(target_dir.join("rustsbi-bench-kernel"))
+        .arg(target_dir.join("rustsbi-bench-kernel.bin"))
+        .status()
+        .ok()?;
+
+    if arg.pack {
+        info!("Pack to image");
+        match fs::exists(target_dir.join("rustsbi-prototyper.bin")) {
+            Ok(true) => {}
+            Ok(false) => {
+                panic!(
+                    " Couldn't open \"rustsbi-prototyper.bin\": No such file or directory. Please compile Prototyper first"
+                );
+            }
+            Err(_) => {
+                panic!(
+                    "Can't check existence of file rustsbi-prototyper.bin, please compile Prototyper first"
+                );
+            }
+        }
+        fs::copy(
+            current_dir
+                .as_ref()
+                .unwrap()
+                .join("prototyper")
+                .join("bench-kernel")
+                .join("scripts")
+                .join("rustsbi-bench-kernel.its"),
+            target_dir.join("rustsbi-bench-kernel.its"),
+        )
+        .ok()?;
+        env::set_current_dir(&target_dir).ok()?;
+        Command::new("mkimage")
+            .args(["-f", "rustsbi-bench-kernel.its"])
+            .arg("rustsbi-bench-kernel.itb")
+            .status()
+            .ok()?;
+        fs::remove_file(env::current_dir().unwrap().join("rustsbi-bench-kernel.its")).ok()?;
+    }
+    Some(exit_status)
+}

+ 55 - 0
xtask/src/logger.rs

@@ -0,0 +1,55 @@
+use log::Level;
+use std::io::Write;
+
+use crate::Cli;
+
+/// Simple logger implementation for RustSBI that supports colored output.
+pub struct Logger;
+
+impl Logger {
+    /// Initialize the logger with log level from RUST_LOG env var or default to Info.
+    pub fn init(cli: &Cli) -> Result<(), log::SetLoggerError> {
+        // Set max log level from parmas env var if present, otherwise use Info
+        log::set_max_level(cli.verbose.log_level_filter());
+        log::set_logger(&Logger)
+    }
+}
+
+impl log::Log for Logger {
+    // Always enable logging for all log levels
+    #[inline]
+    fn enabled(&self, _metadata: &log::Metadata) -> bool {
+        true
+    }
+
+    // Log messages with color-coded levels
+    #[inline]
+    fn log(&self, record: &log::Record) {
+        // ANSI color codes for different log levels
+        const ERROR_COLOR: u8 = 31; // Red
+        const WARN_COLOR: u8 = 93; // Bright yellow
+        const INFO_COLOR: u8 = 32; // Green
+        const DEBUG_COLOR: u8 = 36; // Cyan
+        const TRACE_COLOR: u8 = 90; // Bright black
+
+        let color_code = match record.level() {
+            Level::Error => ERROR_COLOR,
+            Level::Warn => WARN_COLOR,
+            Level::Info => INFO_COLOR,
+            Level::Debug => DEBUG_COLOR,
+            Level::Trace => TRACE_COLOR,
+        };
+
+        eprintln!(
+            "\x1b[1;37m[RustSBI-xtask] \x1b[1;{color_code}m{:^5}\x1b[0m - {}",
+            record.level(),
+            record.args(),
+        );
+    }
+
+    // No-op flush since we use println! which is already line-buffered
+    #[inline]
+    fn flush(&self) {
+        std::io::stderr().flush().expect("Unable to flush stderr");
+    }
+}

+ 56 - 0
xtask/src/main.rs

@@ -0,0 +1,56 @@
+use clap::{Parser, Subcommand};
+use clap_verbosity_flag::{InfoLevel, Verbosity};
+use std::process::ExitCode;
+
+#[macro_use]
+mod utils;
+mod bench;
+mod logger;
+mod prototyper;
+mod test;
+
+#[macro_use]
+extern crate log;
+
+use crate::bench::BenchArg;
+use crate::prototyper::PrototyperArg;
+use crate::test::TestArg;
+
+#[derive(Parser)]
+#[clap(
+    name = "xtask",
+    about = "A task runner for building, running and testing Prototyper",
+    long_about = None,
+)]
+struct Cli {
+    #[clap(subcommand)]
+    cmd: Cmd,
+    #[command(flatten)]
+    verbose: Verbosity<InfoLevel>,
+}
+
+#[derive(Subcommand)]
+enum Cmd {
+    Prototyper(PrototyperArg),
+    Test(TestArg),
+    Bench(BenchArg),
+}
+
+fn main() -> ExitCode {
+    let cli_args = Cli::parse();
+    logger::Logger::init(&cli_args).expect("Unable to init logger");
+
+    if let Some(code) = match cli_args.cmd {
+        Cmd::Prototyper(ref arg) => prototyper::run(arg),
+        Cmd::Test(ref arg) => test::run(arg),
+        Cmd::Bench(ref arg) => bench::run(arg),
+    } {
+        if code.success() {
+            info!("Finished");
+            return ExitCode::SUCCESS;
+        }
+    }
+
+    error!("Failed to run task!");
+    ExitCode::FAILURE
+}

+ 120 - 0
xtask/src/prototyper.rs

@@ -0,0 +1,120 @@
+use std::{
+    env, fs,
+    process::{Command, ExitStatus},
+};
+
+use clap::Args;
+
+use crate::utils::CmdOptional;
+use crate::utils::cargo;
+
+#[derive(Debug, Args, Clone)]
+pub struct PrototyperArg {
+    #[clap(long, short = 'f')]
+    pub features: Vec<String>,
+
+    #[clap(long, env = "PROTOTYPER_FDT_PATH")]
+    pub fdt: Option<String>,
+
+    #[clap(long, env = "PROTOTYPER_PAYLOAD_PATH")]
+    pub payload: Option<String>,
+
+    #[clap(long)]
+    pub jump: bool,
+
+    #[clap(long, default_value = "INFO")]
+    pub log_level: String,
+}
+
+#[must_use]
+#[rustfmt::skip] // "export_env!("PROTOTYPER_FDT_PATH" ?= fdt.unwrap());" is a macro, rustfmt will not format it correctly
+pub fn run(arg: &PrototyperArg) -> Option<ExitStatus> {
+    let arch = "riscv64imac-unknown-none-elf";
+    let fdt = arg.fdt.clone();
+    let payload = arg.payload.clone();
+    let jump = arg.jump;
+    let current_dir = env::current_dir();
+    let target_dir = current_dir
+        .as_ref()
+        .unwrap()
+        .join("target")
+        .join(arch)
+        .join("release");
+
+    info!("Building Protoyper");
+    cargo::Cargo::new("build")
+        .package("rustsbi-prototyper")
+        .target(arch)
+        .unstable("build-std", ["core","alloc"])
+        .env("RUSTFLAGS", "-C relocation-model=pie -C link-arg=-pie")
+        .features(&arg.features)
+        .optional(arg.fdt.is_some(), |cargo| {
+            cargo.env("PROTOTYPER_FDT_PATH", fdt.as_ref().unwrap());
+            cargo.features(["fdt".to_string()])
+        })
+        .optional(payload.is_some(), |cargo| {
+            cargo.env("PROTOTYPER_PAYLOAD_PATH", payload.as_ref().unwrap());
+            cargo.features(["payload".to_string()])
+        })
+        .optional(jump, |cargo| {
+            cargo.features(["jump".to_string()])
+        })
+        .env("RUST_LOG", &arg.log_level)
+        .release()
+        .status()
+        .ok()?;
+
+    info!("Copy to binary");
+    let exit_status = Command::new("rust-objcopy")
+        .args(["-O", "binary"])
+        .arg("--binary-architecture=riscv64")
+        .arg(target_dir.join("rustsbi-prototyper"))
+        .arg(target_dir.join("rustsbi-prototyper.bin"))
+        .status()
+        .ok()?;
+    if !exit_status.success() {
+        error!("Failed to exec rust-objcopy, please check if cargo-binutils has been installed?");
+        return Some(exit_status);
+    }
+
+    if arg.payload.is_some() {
+        info!("Copy for payload mode");
+        fs::copy(
+            target_dir.join("rustsbi-prototyper"),
+            target_dir.join("rustsbi-prototyper-payload.elf"),
+        )
+        .ok()?;
+        fs::copy(
+            target_dir.join("rustsbi-prototyper.bin"),
+            target_dir.join("rustsbi-prototyper-payload.bin"),
+        )
+        .ok()?;
+    } else if arg.jump {
+        info!("Copy for jump mode");
+        fs::copy(
+            target_dir.join("rustsbi-prototyper"),
+            target_dir.join("rustsbi-prototyper-jump.elf"),
+        )
+        .ok()?;
+        fs::copy(
+            target_dir.join("rustsbi-prototyper.bin"),
+            target_dir.join("rustsbi-prototyper-jump.bin"),
+        )
+        .ok()?;
+    } else {
+        info!("Copy for dynamic mode");
+        fs::copy(
+            target_dir.join("rustsbi-prototyper"),
+            target_dir.join("rustsbi-prototyper-dynamic.elf"),
+        )
+        .ok()?;
+        fs::copy(
+            target_dir.join("rustsbi-prototyper.bin"),
+            target_dir.join("rustsbi-prototyper-dynamic.bin"),
+        )
+        .ok()?;
+
+    }
+
+    Some(exit_status)
+}

+ 80 - 0
xtask/src/test.rs

@@ -0,0 +1,80 @@
+use std::{
+    env, fs,
+    process::{Command, ExitStatus},
+};
+
+use clap::Args;
+
+use crate::utils::cargo;
+
+#[derive(Debug, Args, Clone)]
+pub struct TestArg {
+    /// Package Prototyper and Test-Kernel
+    #[clap(long)]
+    pub pack: bool,
+}
+
+#[must_use]
+pub fn run(arg: &TestArg) -> Option<ExitStatus> {
+    let arch = "riscv64imac-unknown-none-elf";
+    let current_dir = env::current_dir();
+    let target_dir = current_dir
+        .as_ref()
+        .unwrap()
+        .join("target")
+        .join(arch)
+        .join("release");
+
+    info!("Building test kernel");
+    cargo::Cargo::new("build")
+        .package("rustsbi-test-kernel")
+        .target(arch)
+        .release()
+        .status()
+        .ok()?;
+
+    info!("Copy to binary");
+    let exit_status = Command::new("rust-objcopy")
+        .args(["-O", "binary"])
+        .arg("--binary-architecture=riscv64")
+        .arg(target_dir.join("rustsbi-test-kernel"))
+        .arg(target_dir.join("rustsbi-test-kernel.bin"))
+        .status()
+        .ok()?;
+
+    if arg.pack {
+        info!("Pack to image");
+        match fs::exists(target_dir.join("rustsbi-prototyper.bin")) {
+            Ok(true) => {}
+            Ok(false) => {
+                panic!(
+                    " Couldn't open \"rustsbi-prototyper.bin\": No such file or directory. Please compile Prototyper first"
+                );
+            }
+            Err(_) => {
+                panic!(
+                    "Can't check existence of file rustsbi-prototyper.bin, please compile Prototyper first"
+                );
+            }
+        }
+        fs::copy(
+            current_dir
+                .as_ref()
+                .unwrap()
+                .join("prototyper")
+                .join("test-kernel")
+                .join("scripts")
+                .join("rustsbi-test-kernel.its"),
+            target_dir.join("rustsbi-test-kernel.its"),
+        )
+        .ok()?;
+        env::set_current_dir(&target_dir).ok()?;
+        Command::new("mkimage")
+            .args(["-f", "rustsbi-test-kernel.its"])
+            .arg("rustsbi-test-kernel.itb")
+            .status()
+            .ok()?;
+        fs::remove_file(env::current_dir().unwrap().join("rustsbi-test-kernel.its")).ok()?;
+    }
+    Some(exit_status)
+}

+ 105 - 0
xtask/src/utils/cargo.rs

@@ -0,0 +1,105 @@
+use std::{
+    ffi::OsStr,
+    ops::{Deref, DerefMut},
+    path::Path,
+    process::Command,
+};
+
+use super::CmdOptional;
+
+pub struct Cargo {
+    cmd: Command,
+}
+
+#[allow(unused)]
+impl Cargo {
+    pub fn new(action: &str) -> Self {
+        let mut cmd = Command::new(env!("CARGO"));
+        cmd.arg(action);
+        Self { cmd }
+    }
+
+    pub fn package<S: AsRef<OsStr>>(&mut self, package: S) -> &mut Self {
+        self.args(["--package", package.as_ref().to_str().unwrap()]);
+        self
+    }
+
+    pub fn work_dir<S: AsRef<Path>>(&mut self, dir: S) -> &mut Self {
+        self.current_dir(dir);
+        self
+    }
+
+    pub fn release(&mut self) -> &mut Self {
+        self.arg("--release");
+        self
+    }
+
+    pub fn target<S: AsRef<OsStr>>(&mut self, target: S) -> &mut Self {
+        self.args(["--target", target.as_ref().to_str().unwrap()]);
+        self
+    }
+
+    pub fn features<I, S>(&mut self, features: I) -> &mut Self
+    where
+        I: IntoIterator<Item = S>,
+        S: AsRef<OsStr>,
+    {
+        self.args([
+            "--features",
+            features
+                .into_iter()
+                .map(|f| f.as_ref().to_str().unwrap().to_string())
+                .collect::<Vec<_>>()
+                .join(",")
+                .as_ref(),
+        ]);
+        self
+    }
+
+    pub fn no_default_features(&mut self) -> &mut Self {
+        self.arg("--no-default-features");
+        self
+    }
+
+    pub fn unstable<I, S>(&mut self, key: S, values: I) -> &mut Self
+    where
+        I: IntoIterator<Item = S>,
+        S: AsRef<OsStr>,
+    {
+        self.arg(format!(
+            "-Z{}={}",
+            key.as_ref().to_str().unwrap(),
+            values
+                .into_iter()
+                .map(|f| f.as_ref().to_str().unwrap().to_string())
+                .collect::<Vec<_>>()
+                .join(",")
+        ));
+        self
+    }
+
+    pub fn env<K, V>(&mut self, key: K, value: V) -> &mut Self
+    where
+        K: AsRef<OsStr>,
+        V: AsRef<OsStr>,
+    {
+        self.cmd.env(key, value);
+        self
+    }
+}
+
+impl CmdOptional for Cargo {}
+
+impl Deref for Cargo {
+    type Target = Command;
+
+    fn deref(&self) -> &Self::Target {
+        &self.cmd
+    }
+}
+
+impl DerefMut for Cargo {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.cmd
+    }
+}

+ 14 - 0
xtask/src/utils/envs.rs

@@ -0,0 +1,14 @@
+#[allow(unused)]
+macro_rules! export_env {
+    ($env:literal ?= $val:expr) => {
+        if std::env::vars_os().all(|(k, _)| k != $env) {
+            std::env::set_var($env, $val);
+        }
+    };
+    ($env0:literal ?= $val0:expr, $($env:literal ?= $val:expr,)+) => {
+        export_env!($env0 ?= $val0);
+        $(
+            export_env!($env ?= $val);
+        )+
+    };
+}

+ 13 - 0
xtask/src/utils/mod.rs

@@ -0,0 +1,13 @@
+pub mod cargo;
+
+#[macro_use]
+pub mod envs;
+
+pub trait CmdOptional {
+    fn optional(&mut self, pred: bool, f: impl FnOnce(&mut Self) -> &mut Self) -> &mut Self {
+        if pred {
+            f(self);
+        }
+        self
+    }
+}