Browse Source

Merge pull request #62 from Placebo27/placebo27/spec-rt-merge

Merge `sbi-rt`, `sbi-spec` and `sbi-testing` repositories into `rustsbi` repository
Luo Jia / Zhouqi Jiang 1 year ago
parent
commit
ac3dae2ec9
64 changed files with 5703 additions and 18 deletions
  1. 100 17
      .github/workflows/rust.yml
  2. 1 0
      CHANGELOG.md
  3. 7 1
      Cargo.toml
  4. 2 0
      sbi-rt/.cargo/config.toml
  5. 7 0
      sbi-rt/.gitignore
  6. 52 0
      sbi-rt/CHANGELOG.md
  7. 35 0
      sbi-rt/Cargo.toml
  8. 9 0
      sbi-rt/LICENSE-MIT
  9. 101 0
      sbi-rt/LICENSE-MULAN
  10. 26 0
      sbi-rt/README.md
  11. 21 0
      sbi-rt/README_EN.md
  12. 179 0
      sbi-rt/src/base.rs
  13. 143 0
      sbi-rt/src/binary.rs
  14. 124 0
      sbi-rt/src/cppc.rs
  15. 102 0
      sbi-rt/src/dbcn.rs
  16. 227 0
      sbi-rt/src/hsm.rs
  17. 160 0
      sbi-rt/src/legacy.rs
  18. 46 0
      sbi-rt/src/lib.rs
  19. 146 0
      sbi-rt/src/nacl.rs
  20. 326 0
      sbi-rt/src/pmu.rs
  21. 228 0
      sbi-rt/src/rfnc.rs
  22. 23 0
      sbi-rt/src/spi.rs
  23. 109 0
      sbi-rt/src/srst.rs
  24. 67 0
      sbi-rt/src/sta.rs
  25. 85 0
      sbi-rt/src/susp.rs
  26. 29 0
      sbi-rt/src/time.rs
  27. 7 0
      sbi-spec/.gitignore
  28. 116 0
      sbi-spec/CHANGELOG.md
  29. 20 0
      sbi-spec/Cargo.toml
  30. 8 0
      sbi-spec/LICENSE-MIT
  31. 101 0
      sbi-spec/LICENSE-MULAN
  32. 41 0
      sbi-spec/README.md
  33. 98 0
      sbi-spec/src/base.rs
  34. 907 0
      sbi-spec/src/binary.rs
  35. 25 0
      sbi-spec/src/cppc.rs
  36. 21 0
      sbi-spec/src/dbcn.rs
  37. 70 0
      sbi-spec/src/hsm.rs
  38. 25 0
      sbi-spec/src/legacy.rs
  39. 318 0
      sbi-spec/src/lib.rs
  40. 75 0
      sbi-spec/src/nacl.rs
  41. 187 0
      sbi-spec/src/pmu.rs
  42. 37 0
      sbi-spec/src/rfnc.rs
  43. 13 0
      sbi-spec/src/spi.rs
  44. 25 0
      sbi-spec/src/srst.rs
  45. 13 0
      sbi-spec/src/sta.rs
  46. 13 0
      sbi-spec/src/susp.rs
  47. 13 0
      sbi-spec/src/time.rs
  48. 2 0
      sbi-testing/.cargo/config.toml
  49. 7 0
      sbi-testing/.gitignore
  50. 37 0
      sbi-testing/CHANGELOG.md
  51. 27 0
      sbi-testing/Cargo.toml
  52. 9 0
      sbi-testing/LICENSE-MIT
  53. 101 0
      sbi-testing/LICENSE-MULAN
  54. 21 0
      sbi-testing/README.md
  55. 21 0
      sbi-testing/README_EN.md
  56. 5 0
      sbi-testing/rust-toolchain.toml
  57. 106 0
      sbi-testing/src/base.rs
  58. 66 0
      sbi-testing/src/dbcn.rs
  59. 276 0
      sbi-testing/src/hsm.rs
  60. 40 0
      sbi-testing/src/lib.rs
  61. 167 0
      sbi-testing/src/log_test.rs
  62. 56 0
      sbi-testing/src/spi.rs
  63. 180 0
      sbi-testing/src/thread.rs
  64. 94 0
      sbi-testing/src/time.rs

+ 100 - 17
.github/workflows/rust.yml

@@ -31,7 +31,7 @@ jobs:
           args: --all -- --check
 
   check-stable:
-    name: Cargo check (stable)
+    name: Check rustsbi (stable)
     runs-on: ubuntu-latest
     needs: fmt
     strategy:
@@ -52,26 +52,26 @@ jobs:
       - name: Check (no default features)
         uses: actions-rs/cargo@v1
         with:
-          command: check
-          args: --target ${{ matrix.TARGET }} --verbose
-      - name: Check (machine)
+          command: check 
+          args: --target ${{ matrix.TARGET }} --verbose -p rustsbi
+      - name: Check crate rustsbi (machine)
         uses: actions-rs/cargo@v1
         with:
           command: check
-          args: --target ${{ matrix.TARGET }} --features "machine" --verbose
-      - name: Check (forward)
+          args: --target ${{ matrix.TARGET }} --features "machine" --verbose -p rustsbi
+      - name: Check crate rustsbi (forward)
         uses: actions-rs/cargo@v1
         with:
           command: check
-          args: --target ${{ matrix.TARGET }} --features "forward" --verbose
-      - name: Check (machine + forward)
+          args: --target ${{ matrix.TARGET }} --features "forward" --verbose -p rustsbi
+      - name: Check crate rustsbi (machine + forward)
         uses: actions-rs/cargo@v1
         with:
           command: check
-          args: --target ${{ matrix.TARGET }} --features "machine, forward" --verbose
+          args: --target ${{ matrix.TARGET }} --features "machine, forward" --verbose -p rustsbi
 
   check-nightly:
-    name: Cargo check (nightly)
+    name: Check rustsbi (nightly)
     runs-on: ubuntu-latest
     needs: fmt
     strategy:
@@ -91,7 +91,7 @@ jobs:
           key: ${{ matrix.TARGET }}
 
   tests:
-    name: Run tests
+    name: Test rustsbi
     needs: fmt
     runs-on: ubuntu-latest
     steps:
@@ -104,17 +104,100 @@ jobs:
           override: true
       - name: Cache Dependencies
         uses: Swatinem/rust-cache@v2
-      - name: Clippy
-        uses: actions-rs/cargo@v1
-        with:
-          command: clippy
+      # - name: Clippy
+      #   uses: actions-rs/cargo@v1
+      #   with:
+      #     command: clippy
       - name: Run tests (no default features)
         uses: actions-rs/cargo@v1
         with:
           command: test
-          args: --verbose
+          args: --verbose -p rustsbi
       - name: Run tests (machine)
         uses: actions-rs/cargo@v1
         with:
           command: test
-          args: --features "machine" --verbose
+          args: --features "machine" --verbose -p rustsbi
+
+  sbi-spec:
+    name: Test sbi-spec
+    needs: fmt
+    runs-on: ubuntu-latest
+    strategy:
+      fail-fast: false
+      matrix:
+        target:
+          [
+            riscv32imac-unknown-none-elf,
+            riscv64imac-unknown-none-elf,
+            x86_64-unknown-none,
+          ]
+    steps:
+      - uses: actions/checkout@v3
+      # Cache REF:
+      #   - https://github.com/actions/cache/blob/main/examples.md#rust---cargo
+      #   - https://github.com/actions-rs/toolchain/issues/54
+      - name: Cache Rust
+        uses: actions/cache@v3
+        with:
+          key: rust-toolchain-${{ matrix.target }}
+          path: |
+            ~/.rustup/settings.toml
+            ~/.rustup/toolchains/stable-*
+            ~/.rustup/update-hashes/stable-*
+            ~/.cargo/bin/
+            ~/.cargo/registry/index/
+            ~/.cargo/registry/cache/
+            ~/.cargo/git/db/
+            target/
+      - name: Setup Rust
+        run: |
+          rustup toolchain install stable --profile minimal
+          rustup component add rustfmt clippy
+          rustup target add ${{ matrix.target }}
+      - name: Check format
+        run: cargo fmt --all --check -p sbi-spec
+      # - name: Check clippy
+      #   run: cargo clippy -- -D warnings
+      - name: Check build
+        run: cargo build -p sbi-spec
+      - name: Check test
+        run: cargo test -p sbi-spec
+
+# sbi-testing:
+#     name: Run rust-clippy analyzing
+#     runs-on: ubuntu-latest
+#     permissions:
+#       security-events: write
+#     steps:
+#       - name: Checkout code
+#         uses: actions/checkout@v3
+
+#       - name: Check format
+#         run: cargo fmt --check
+
+#       - name: Install clippy-sarif
+#         uses: actions-rs/[email protected]
+#         with:
+#           crate: clippy-sarif
+#           version: latest
+
+#       - name: Install sarif-fmt
+#         uses: actions-rs/[email protected]
+#         with:
+#           crate: sarif-fmt
+#           version: latest
+
+#       - name: Run rust-clippy
+#         run:
+#           cargo clippy
+#           --all-featuers
+#           --package fast-trap
+#           --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@v2
+#         with:
+#           sarif_file: rust-clippy-results.sarif
+#           wait-for-processing: true

+ 1 - 0
CHANGELOG.md

@@ -22,6 +22,7 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 - run on provided `EnvInfo` by default; bare metal M-mode environment should gate `machine`
 - doc: grammar tweaks in hsm module
 - update dependency `sbi-spec` to v0.0.6, use `Physical` struct from `sbi-spec` crate.
+- merge rustsbi/sbi-{rt, spec, testing} repositories into rustsbi/rustsbi repository.
 
 ### Removed
 

+ 7 - 1
Cargo.toml

@@ -42,6 +42,12 @@ targets = [
 ]
 
 [workspace]
-members = ["macros"]
+members = [
+    "macros",
+    "sbi-rt",
+    "sbi-spec",
+    "sbi-testing",
+]
+resolver = "2"
 
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

+ 2 - 0
sbi-rt/.cargo/config.toml

@@ -0,0 +1,2 @@
+[build]
+target = "riscv64imac-unknown-none-elf"

+ 7 - 0
sbi-rt/.gitignore

@@ -0,0 +1,7 @@
+**/.*/*
+!.github/*
+!.cargo/*
+!.vscode/settings.json
+
+/target
+/Cargo.lock

+ 52 - 0
sbi-rt/CHANGELOG.md

@@ -0,0 +1,52 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres
+to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [Unreleased]
+
+## [0.0.3]
+
+This version adds support to RISC-V SBI Specification version 2.0-rc7.
+
+### Added
+
+- Debug Console extension support
+- `pmu_counter_fw_read_hi` in PMU extension
+- Support for SBI CPPC extension
+- Support for NACL and STA extensions
+
+### Modified
+
+- SPI and RFNC extensions now use `HartMask` parameter
+
+### Fixed
+
+- Minor document fixes on `ConfigFlags` and `set_timer`
+- Document fixes on HSM extension from SBI 2.0-rc1
+
+## [0.0.2] - 2022-10-10
+
+In this version we changed API style to trait based type parameters, which would make it easier to
+check parameter types at runtime to reduce errors. If user choose to use `integer-impls` feature,
+it would fall back to older style functions using integer types.
+
+### Added
+
+- Trait based type parameter for all extensions
+- Feature `integer-impls` to allow fast prototyping with sbi-rt crate
+- Feature `legacy` to gate SBI legacy extension
+- Documents on various functions
+
+### Modified
+
+- Update `sbi-spec` to version 0.0.4, re-export `Version` structure
+- Function `probe_extension` now returns an `ExtensionInfo` value
+- Function `pmu_num_counters` returns a `usize` value
+
+[Unreleased]: https://github.com/rustsbi/sbi-rt/compare/v0.0.3...HEAD
+[0.0.3]: https://github.com/rustsbi/sbi-rt/compare/v0.0.2...v0.0.3
+[0.0.2]: https://github.com/rustsbi/sbi-rt/compare/v0.0.1...v0.0.2
+[0.0.1]: https://github.com/rustsbi/sbi-rt/releases/tag/v0.0.1

+ 35 - 0
sbi-rt/Cargo.toml

@@ -0,0 +1,35 @@
+[package]
+name = "sbi-rt"
+version = "0.0.3-rc.5"
+edition = "2021"
+description = "Runtime library for supervisors to call RISC-V Supervisor Binary Interface (RISC-V SBI)"
+categories = ["os", "embedded", "hardware-support", "no-std"]
+keywords = ["riscv", "sbi", "rustsbi"]
+authors = [
+    "YdrMaster <[email protected]>",
+    "Luo Jia <[email protected]>",
+]
+repository = "https://github.com/rustsbi/sbi-rt"
+documentation = "https://docs.rs/sbi-rt"
+license = "MulanPSL-2.0 OR MIT"
+readme = "README.md"
+
+[package.metadata.docs.rs]
+default-target = "riscv64imac-unknown-none-elf"
+targets = [
+    "riscv32imac-unknown-none-elf", "riscv64imac-unknown-none-elf",
+]
+
+[dependencies]
+sbi-spec = "0.0.7-alpha.3"
+
+[features]
+default = []
+# Implement sbi-rt traits for integer types
+# By using this feature, parameter types of sbi-rt functions fall back to integers,
+# static type checks are disabled so this library won't detect parameters in incorrect orders.
+# Although some people may find it useful in prototyping sbi-rt implementations,
+# users of this crate are strongly encouraged not to enable this feature in production.
+integer-impls = []
+# Support legacy extension; this feature is not included by default.
+legacy = ["sbi-spec/legacy"]

+ 9 - 0
sbi-rt/LICENSE-MIT

@@ -0,0 +1,9 @@
+The MIT License (MIT)
+
+Copyright © 2022 YdrMaster
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ 101 - 0
sbi-rt/LICENSE-MULAN

@@ -0,0 +1,101 @@
+木兰宽松许可证, 第2版
+
+2020年1月 <http://license.coscl.org.cn/MulanPSL2>
+
+您对“软件”的复制、使用、修改及分发受木兰宽松许可证,第2版(“本许可证”)的如下条款的约束:
+
+0. 定义
+
+“软件”是指由“贡献”构成的许可在“本许可证”下的程序和相关文档的集合。
+
+“贡献”是指由任一“贡献者”许可在“本许可证”下的受版权法保护的作品。
+
+“贡献者”是指将受版权法保护的作品许可在“本许可证”下的自然人或“法人实体”。
+
+“法人实体”是指提交贡献的机构及其“关联实体”。
+
+“关联实体”是指,对“本许可证”下的行为方而言,控制、受控制或与其共同受控制的机构,此处的控制是指有受控方或共同受控方至少50%直接或间接的投票权、资金或其他有价证券。
+
+1. 授予版权许可
+
+每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的版权许可,您可以复制、使用、修改、分发其“贡献”,不论修改与否。
+
+2. 授予专利许可
+
+每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的(根据本条规定撤销除外)专利许可,供您制造、委托制造、使用、许诺销售、销售、进口其“贡献”或以其他方式转移其“贡献”。前述专利许可仅限于“贡献者”现在或将来拥有或控制的其“贡献”本身或其“贡献”与许可“贡献”时的“软件”结合而将必然会侵犯的专利权利要求,不包括对“贡献”的修改或包含“贡献”的其他结合。如果您或您的“关联实体”直接或间接地,就“软件”或其中的“贡献”对任何人发起专利侵权诉讼(包括反诉或交叉诉讼)或其他专利维权行动,指控其侵犯专利权,则“本许可证”授予您对“软件”的专利许可自您提起诉讼或发起维权行动之日终止。
+
+3. 无商标许可
+
+“本许可证”不提供对“贡献者”的商品名称、商标、服务标志或产品名称的商标许可,但您为满足第4条规定的声明义务而必须使用除外。
+
+4. 分发限制
+
+您可以在任何媒介中将“软件”以源程序形式或可执行形式重新分发,不论修改与否,但您必须向接收者提供“本许可证”的副本,并保留“软件”中的版权、商标、专利及免责声明。
+
+5. 免责声明与责任限制
+
+“软件”及其中的“贡献”在提供时不带任何明示或默示的担保。在任何情况下,“贡献者”或版权所有者不对任何人因使用“软件”或其中的“贡献”而引发的任何直接或间接损失承担责任,不论因何种原因导致或者基于何种法律理论,即使其曾被建议有此种损失的可能性。
+
+6. 语言
+
+“本许可证”以中英文双语表述,中英文版本具有同等法律效力。如果中英文版本存在任何冲突不一致,以中文版为准。
+
+条款结束
+
+如何将木兰宽松许可证,第2版,应用到您的软件
+
+如果您希望将木兰宽松许可证,第2版,应用到您的新软件,为了方便接收者查阅,建议您完成如下三步:
+
+1,请您补充如下声明中的空白,包括软件名、软件的首次发表年份以及您作为版权人的名字;
+
+2,请您在软件包的一级目录下创建以“LICENSE”为名的文件,将整个许可证文本放入该文件中;
+
+3,请将如下声明文本放入每个源文件的头部注释中。
+
+Copyright (c) 2020 Luo Jia
+
+RustSBI is licensed under Mulan PSL v2. You can use this software according to the terms and conditions of the Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: <http://license.coscl.org.cn/MulanPSL2>
+
+THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. See the Mulan PSL v2 for more details.
+
+January 2020 <http://license.coscl.org.cn/MulanPSL2>
+
+Your reproduction, use, modification and distribution of the Software shall be subject to Mulan PSL v2 (this License) with the following terms and conditions:
+
+0. Definition
+
+Software means the program and related documents which are licensed under this License and comprise all Contribution(s).
+
+Contribution means the copyrightable work licensed by a particular Contributor under this License.
+
+Contributor means the Individual or Legal Entity who licenses its copyrightable work under this License.
+
+Legal Entity means the entity making a Contribution and all its Affiliates.
+
+Affiliates means entities that control, are controlled by, or are under common control with the acting entity under this License, ‘control’ means direct or indirect ownership of at least fifty percent (50%) of the voting power, capital or other securities of controlled or commonly controlled entity.
+
+1. Grant of Copyright License
+
+Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable copyright license to reproduce, use, modify, or distribute its Contribution, with modification or not.
+
+2. Grant of Patent License
+
+Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable (except for revocation under this Section) patent license to make, have made, use, offer for sale, sell, import or otherwise transfer its Contribution, where such patent license is only limited to the patent claims owned or controlled by such Contributor now or in future which will be necessarily infringed by its Contribution alone, or by combination of the Contribution with the Software to which the Contribution was contributed. The patent license shall not apply to any modification of the Contribution, and any other combination which includes the Contribution. If you or your Affiliates directly or indirectly institute patent litigation (including a cross claim or counterclaim in a litigation) or other patent enforcement activities against any individual or entity by alleging that the Software or any Contribution in it infringes patents, then any patent license granted to you under this License for the Software shall terminate as of the date such litigation or activity is filed or taken.
+
+3. No Trademark License
+
+No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, except as required to fulfill notice requirements in Section 4.
+
+4. Distribution Restriction
+
+You may distribute the Software in any medium with or without modification, whether in source or executable forms, provided that you provide recipients with a copy of this License and retain copyright, patent, trademark and disclaimer statements in the Software.
+
+5. Disclaimer of Warranty and Limitation of Liability
+
+THE SOFTWARE AND CONTRIBUTION IN IT ARE PROVIDED WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED. IN NO EVENT SHALL ANY CONTRIBUTOR OR COPYRIGHT HOLDER BE LIABLE TO YOU FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO ANY DIRECT, OR INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING FROM YOUR USE OR INABILITY TO USE THE SOFTWARE OR THE CONTRIBUTION IN IT, NO MATTER HOW IT’S CAUSED OR BASED ON WHICH LEGAL THEORY, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+6. Language
+
+THIS LICENSE IS WRITTEN IN BOTH CHINESE AND ENGLISH, AND THE CHINESE VERSION AND ENGLISH VERSION SHALL HAVE THE SAME LEGAL EFFECT. IN THE CASE OF DIVERGENCE BETWEEN THE CHINESE AND ENGLISH VERSIONS, THE CHINESE VERSION SHALL PREVAIL.
+
+END OF THE TERMS AND CONDITIONS

+ 26 - 0
sbi-rt/README.md

@@ -0,0 +1,26 @@
+# sbi-rt
+
+[![crates.io](https://img.shields.io/crates/v/sbi-rt.svg)](https://crates.io/crates/sbi-rt)
+[![Documentation](https://docs.rs/sbi-rt/badge.svg)](https://docs.rs/sbi-rt)
+![License](https://img.shields.io/crates/l/sbi-rt.svg)
+
+- [An English README](README_EN.md)
+
+为特权软件提供 RISC-V 特权二进制接口(Supervisor Binary Interface)的运行时库。
+
+2.0-rc7 标准各章节的实现情况:
+
+- [x] §3
+- [x] §4
+- [x] §5
+- [x] §6
+- [x] §7
+- [x] §8
+- [x] §9
+- [x] §10
+- [x] §11
+- [x] §12
+- [x] §13
+- [x] §14
+- [x] §15
+- [x] §16

+ 21 - 0
sbi-rt/README_EN.md

@@ -0,0 +1,21 @@
+# sbi-rt
+
+- [中文自述文件](README.md)
+
+Runtime library for supervisors to call RISC-V Supervisor Binary Interface (RISC-V SBI).
+
+Charaters implementation in 2.0-rc7 specification:
+
+- [x] §3
+- [x] §4
+- [x] §5
+- [x] §6
+- [x] §7
+- [x] §8
+- [x] §9
+- [x] §10
+- [x] §11
+- [x] §13
+- [x] §14
+- [x] §15
+- [x] §16

+ 179 - 0
sbi-rt/src/base.rs

@@ -0,0 +1,179 @@
+//! Chapter 4. Base Extension (EID #0x10)
+
+use crate::binary::{sbi_call_0, sbi_call_1};
+
+pub use sbi_spec::base::Version;
+use sbi_spec::base::{
+    EID_BASE, GET_MARCHID, GET_MIMPID, GET_MVENDORID, GET_SBI_IMPL_ID, GET_SBI_IMPL_VERSION,
+    GET_SBI_SPEC_VERSION, PROBE_EXTENSION,
+};
+
+/// Return the current SBI specification version.
+///
+/// The minor number of the SBI specification is encoded in the low 24 bits,
+/// with the major number encoded in the next 7 bits. Bit 31 must be 0 and
+/// is reserved for future expansion.
+///
+/// This function is defined in RISC-V SBI Specification chapter 4.1.
+/// According to introduction of chapter 4, all base extension functions
+/// must success and return no error code.
+#[inline]
+pub fn get_spec_version() -> Version {
+    Version::from_raw(sbi_call_0(EID_BASE, GET_SBI_SPEC_VERSION).value)
+}
+
+/// Return the current SBI implementation ID.
+///
+/// Implementation ID is different for every SBI implementation.
+/// It is intended that this implementation ID allows software to probe
+/// for SBI implementation quirks.
+///
+/// This function is defined in RISC-V SBI Specification chapter 4.2.
+/// According to introduction of chapter 4, all base extension functions
+/// must success and return no error code.
+#[inline]
+pub fn get_sbi_impl_id() -> usize {
+    sbi_call_0(EID_BASE, GET_SBI_IMPL_ID).value
+}
+
+/// Return the current SBI implementation version.
+///
+/// The encoding of this version number is specific to the SBI implementation.
+///
+/// This function is defined in RISC-V SBI Specification chapter 4.3.
+/// According to introduction of chapter 4, all base extension functions
+/// must success and return no error code.
+#[inline]
+pub fn get_sbi_impl_version() -> usize {
+    sbi_call_0(EID_BASE, GET_SBI_IMPL_VERSION).value
+}
+
+/// Probe information about one SBI extension from current environment.
+///
+/// Returns 0 if given SBI `extension_id` is not available, or typically
+/// 1 if it's available. Implementation would define further non-zero
+/// return values for information about this extension if it is available.
+///
+/// This function is defined in RISC-V SBI Specification chapter 4.4.
+/// According to introduction of chapter 4, all base extension functions
+/// must success and return no error code.
+#[inline]
+pub fn probe_extension<E>(extension: E) -> ExtensionInfo
+where
+    E: Extension,
+{
+    let ans = sbi_call_1(EID_BASE, PROBE_EXTENSION, extension.extension_id());
+    ExtensionInfo { raw: ans.value }
+}
+
+/// Return value of `mvendorid` register in current environment.
+///
+/// This function returns a value that is legal for the `mvendorid` register,
+/// and 0 is always a legal value for this register.
+///
+/// This function is defined in RISC-V SBI Specification chapter 4.5.
+/// According to introduction of chapter 4, all base extension functions
+/// must success and return no error code.
+#[inline]
+pub fn get_mvendorid() -> usize {
+    sbi_call_0(EID_BASE, GET_MVENDORID).value
+}
+
+/// Return value of `marchid` register in current environment.
+///
+/// This function returns a value that is legal for the `marchid` register,
+/// and 0 is always a legal value for this register.
+///
+/// This function is defined in RISC-V SBI Specification chapter 4.6.
+/// According to introduction of chapter 4, all base extension functions
+/// must success and return no error code.
+#[inline]
+pub fn get_marchid() -> usize {
+    sbi_call_0(EID_BASE, GET_MARCHID).value
+}
+
+/// Return value of `mimpid` register in current environment.
+///
+/// This function returns a value that is legal for the `mimpid` register,
+/// and 0 is always a legal value for this register.
+///
+/// This function is defined in RISC-V SBI Specification chapter 4.7.
+/// According to introduction of chapter 4, all base extension functions
+/// must success and return no error code.
+#[inline]
+pub fn get_mimpid() -> usize {
+    sbi_call_0(EID_BASE, GET_MIMPID).value
+}
+
+/// An SBI extension.
+pub trait Extension {
+    /// Get a raw `extension_id` value to pass to SBI environment.
+    fn extension_id(&self) -> usize;
+}
+
+macro_rules! define_extension {
+    ($($struct:ident($value:expr) #[$doc:meta])*) => {
+        $(
+            #[derive(Clone, Copy, Debug)]
+            #[$doc]
+            pub struct $struct;
+            impl Extension for $struct {
+                #[inline]
+                fn extension_id(&self) -> usize {
+                    $value
+                }
+            }
+        )*
+    };
+}
+
+define_extension! {
+    Base(sbi_spec::base::EID_BASE) /// RISC-V SBI Base extension.
+    Timer(sbi_spec::time::EID_TIME) /// Timer programmer extension.
+    Ipi(sbi_spec::spi::EID_SPI) /// Inter-processor Interrupt extension.
+    Fence(sbi_spec::rfnc::EID_RFNC) /// Remote Fence extension.
+    Hsm(sbi_spec::hsm::EID_HSM) /// Hart State Monitor extension.
+    Reset(sbi_spec::srst::EID_SRST) /// System Reset extension.
+    Pmu(sbi_spec::pmu::EID_PMU) /// Performance Monitoring Unit extension.
+    Console(sbi_spec::dbcn::EID_DBCN) /// Debug Console extension.
+    Suspend(sbi_spec::susp::SUSPEND) /// System Suspend extension.
+    Cppc(sbi_spec::cppc::EID_CPPC) /// SBI CPPC extension.
+    Nacl(sbi_spec::cppc::EID_CPPC) /// Nested Acceleration extension.
+    Sta(sbi_spec::cppc::EID_CPPC) /// Steal-time Accounting extension.
+}
+
+#[cfg(feature = "integer-impls")]
+impl Extension for usize {
+    #[inline]
+    fn extension_id(&self) -> usize {
+        *self
+    }
+}
+
+#[cfg(feature = "integer-impls")]
+impl Extension for isize {
+    #[inline]
+    fn extension_id(&self) -> usize {
+        usize::from_ne_bytes(isize::to_ne_bytes(*self))
+    }
+}
+
+/// Information about an SBI extension.
+#[derive(Clone, Copy, Debug)]
+pub struct ExtensionInfo {
+    pub raw: usize,
+}
+
+impl ExtensionInfo {
+    /// Is this extension available?
+    #[inline]
+    pub const fn is_available(&self) -> bool {
+        self.raw != 0
+    }
+
+    /// Is this extension not available?
+    #[inline]
+    pub const fn is_unavailable(&self) -> bool {
+        self.raw == 0
+    }
+}

+ 143 - 0
sbi-rt/src/binary.rs

@@ -0,0 +1,143 @@
+//! Capture 3. Binary Encoding
+
+pub use sbi_spec::binary::SbiRet;
+
+#[inline(always)]
+pub(crate) fn sbi_call_0(eid: usize, fid: usize) -> SbiRet {
+    let (error, value);
+    unsafe {
+        core::arch::asm!(
+            "ecall",
+            in("a7") eid,
+            in("a6") fid,
+            lateout("a0") error,
+            lateout("a1") value,
+        );
+    }
+    SbiRet { error, value }
+}
+
+#[inline(always)]
+pub(crate) fn sbi_call_1(eid: usize, fid: usize, arg0: usize) -> SbiRet {
+    let (error, value);
+    unsafe {
+        core::arch::asm!(
+            "ecall",
+            in("a7") eid,
+            in("a6") fid,
+            inlateout("a0") arg0 => error,
+            lateout("a1") value,
+        );
+    }
+    SbiRet { error, value }
+}
+
+#[inline(always)]
+pub(crate) fn sbi_call_2(eid: usize, fid: usize, arg0: usize, arg1: usize) -> SbiRet {
+    let (error, value);
+    unsafe {
+        core::arch::asm!(
+            "ecall",
+            in("a7") eid,
+            in("a6") fid,
+            inlateout("a0") arg0 => error,
+            inlateout("a1") arg1 => value,
+        );
+    }
+    SbiRet { error, value }
+}
+
+#[inline(always)]
+pub(crate) fn sbi_call_3(eid: usize, fid: usize, arg0: usize, arg1: usize, arg2: usize) -> SbiRet {
+    let (error, value);
+    unsafe {
+        core::arch::asm!(
+            "ecall",
+            in("a7") eid,
+            in("a6") fid,
+            inlateout("a0") arg0 => error,
+            inlateout("a1") arg1 => value,
+            in("a2") arg2,
+        );
+    }
+    SbiRet { error, value }
+}
+
+#[inline(always)]
+pub(crate) fn sbi_call_4(
+    eid: usize,
+    fid: usize,
+    arg0: usize,
+    arg1: usize,
+    arg2: usize,
+    arg3: usize,
+) -> SbiRet {
+    let (error, value);
+    unsafe {
+        core::arch::asm!(
+            "ecall",
+            in("a7") eid,
+            in("a6") fid,
+            inlateout("a0") arg0 => error,
+            inlateout("a1") arg1 => value,
+            in("a2") arg2,
+            in("a3") arg3,
+        );
+    }
+    SbiRet { error, value }
+}
+
+#[inline(always)]
+pub(crate) fn sbi_call_5(
+    eid: usize,
+    fid: usize,
+    arg0: usize,
+    arg1: usize,
+    arg2: usize,
+    arg3: usize,
+    arg4: usize,
+) -> SbiRet {
+    let (error, value);
+    unsafe {
+        core::arch::asm!(
+            "ecall",
+            in("a7") eid,
+            in("a6") fid,
+            inlateout("a0") arg0 => error,
+            inlateout("a1") arg1 => value,
+            in("a2") arg2,
+            in("a3") arg3,
+            in("a4") arg4,
+        );
+    }
+    SbiRet { error, value }
+}
+
+#[cfg(target_pointer_width = "32")]
+#[inline(always)]
+pub(crate) fn sbi_call_6(
+    eid: usize,
+    fid: usize,
+    arg0: usize,
+    arg1: usize,
+    arg2: usize,
+    arg3: usize,
+    arg4: usize,
+    arg5: usize,
+) -> SbiRet {
+    let (error, value);
+    unsafe {
+        core::arch::asm!(
+            "ecall",
+            in("a7") eid,
+            in("a6") fid,
+            inlateout("a0") arg0 => error,
+            inlateout("a1") arg1 => value,
+            in("a2") arg2,
+            in("a3") arg3,
+            in("a4") arg4,
+            in("a5") arg5,
+        );
+    }
+    SbiRet { error, value }
+}

+ 124 - 0
sbi-rt/src/cppc.rs

@@ -0,0 +1,124 @@
+//! Chapter 14. CPPC Extension (EID #0x43505043 "CPPC")
+
+#[cfg(target_pointer_width = "64")]
+use crate::binary::sbi_call_2;
+#[cfg(target_pointer_width = "32")]
+use crate::binary::sbi_call_3;
+use crate::binary::{sbi_call_1, SbiRet};
+use sbi_spec::cppc::{EID_CPPC, PROBE, READ, READ_HI, WRITE};
+
+/// Probe whether the CPPC register is implemented or not by the platform.
+///
+/// # Parameters
+///
+/// The `cppc_reg_id` parameter specifies the CPPC register ID.
+///
+/// # Return value
+///
+/// If the register is implemented, `SbiRet.value` will contain the register width.
+/// If the register is not implemented, `SbiRet.value` will be set to 0.
+///
+/// The possible error codes returned in `SbiRet.error` are shown in the table below:
+///
+/// | Return code               | Description
+/// |:--------------------------|:----------------------------------------------
+/// | `SbiRet::success()`       | Probe completed successfully.
+/// | `SbiRet::invalid_param()` | `cppc_reg_id` is reserved.
+/// | `SbiRet::failed()`        | The probe request failed for unspecified or unknown other reasons.
+///
+/// This function is defined in RISC-V SBI Specification chapter 14.1.
+#[inline]
+pub fn cppc_probe(cppc_reg_id: u32) -> SbiRet {
+    sbi_call_1(EID_CPPC, PROBE, cppc_reg_id as _)
+}
+
+/// Read the CPPC register identified by given `cppc_reg_id`.
+///
+/// # Parameters
+///
+/// The `cppc_reg_id` parameter specifies the CPPC register ID.
+///
+/// # Return value
+///
+/// `SbiRet.value` will contain the register value. When supervisor mode XLEN is 32, the `SbiRet.value`
+/// will only contain the lower 32 bits of the CPPC register value.
+///
+/// The possible error codes returned in `SbiRet.error` are shown in the table below:
+///
+/// | Return code               | Description
+/// |:--------------------------|:----------------------------------------------
+/// | `SbiRet::success()`       | Read completed successfully.
+/// | `SbiRet::invalid_param()` | `cppc_reg_id` is reserved.
+/// | `SbiRet::not_supported()` | `cppc_reg_id` is not implemented by the platform.
+/// | `SbiRet::denied()`        | `cppc_reg_id` is a write-only register.
+/// | `SbiRet::failed()`        | The read request failed for unspecified or unknown other reasons.
+///
+/// This function is defined in RISC-V SBI Specification chapter 14.2.
+#[inline]
+pub fn cppc_read(cppc_reg_id: u32) -> SbiRet {
+    sbi_call_1(EID_CPPC, READ, cppc_reg_id as _)
+}
+
+/// Read the upper 32-bit value of the CPPC register identified by `cppc_reg_id`.
+///
+/// # Parameters
+///
+/// The `cppc_reg_id` parameter specifies the CPPC register ID.
+///
+/// # Return value
+///
+/// `SbiRet.value` will contain the upper 32 bits of the register value. This function always
+/// returns zero in `SbiRet.value` when supervisor mode XLEN is 64 or higher.
+///
+/// The possible error codes returned in `SbiRet.error` are shown in the table below:
+///
+/// | Return code               | Description
+/// |:--------------------------|:----------------------------------------------
+/// | `SbiRet::success()`       | Read completed successfully.
+/// | `SbiRet::invalid_param()` | `cppc_reg_id` is reserved.
+/// | `SbiRet::not_supported()` | `cppc_reg_id` is not implemented by the platform.
+/// | `SbiRet::denied()`        | `cppc_reg_id` is a write-only register.
+/// | `SbiRet::failed()`        | The read request failed for unspecified or unknown other reasons.
+///
+/// This function is defined in RISC-V SBI Specification chapter 14.3.
+#[inline]
+pub fn cppc_read_hi(cppc_reg_id: u32) -> SbiRet {
+    sbi_call_1(EID_CPPC, READ_HI, cppc_reg_id as _)
+}
+
+/// Write 64-bit value to the CPPC register identified by given `cppc_reg_id`.
+///
+/// # Parameters
+///
+/// The `cppc_reg_id` parameter specifies the CPPC register ID.
+///
+/// The `value` parameter specifies the value to be written to the register.
+///
+/// # Return value
+///
+/// The possible error codes returned in `SbiRet.error` are shown in the table below:
+///
+/// | Return code               | Description
+/// |:--------------------------|:----------------------------------------------
+/// | `SbiRet::success()`       | Write completed successfully.
+/// | `SbiRet::invalid_param()` | `cppc_reg_id` is reserved.
+/// | `SbiRet::not_supported()` | `cppc_reg_id` is not implemented by the platform.
+/// | `SbiRet::denied()`        | `cppc_reg_id` is a read-only register.
+/// | `SbiRet::failed()`        | The write request failed for unspecified or unknown other reasons.
+///
+/// This function is defined in RISC-V SBI Specification chapter 14.4.
+#[inline]
+pub fn cppc_write(cppc_reg_id: u32, value: u64) -> SbiRet {
+    match () {
+        #[cfg(target_pointer_width = "32")]
+        () => sbi_call_3(
+            EID_CPPC,
+            WRITE,
+            cppc_reg_id as _,
+            value as _,
+            (value >> 32) as _,
+        ),
+        #[cfg(target_pointer_width = "64")]
+        () => sbi_call_2(EID_CPPC, WRITE, cppc_reg_id as _, value as _),
+    }
+}

+ 102 - 0
sbi-rt/src/dbcn.rs

@@ -0,0 +1,102 @@
+//! Chapter 12. Debug Console Extension (EID #0x4442434E "DBCN")
+use crate::binary::{sbi_call_1, sbi_call_3, SbiRet};
+use sbi_spec::{
+    binary::Physical,
+    dbcn::{CONSOLE_READ, CONSOLE_WRITE, CONSOLE_WRITE_BYTE, EID_DBCN},
+};
+
+/// Write bytes to the debug console from input memory.
+///
+/// # Parameters
+///
+/// The `bytes` parameter specifies the input memory, including its length
+/// and memory physical base address (both lower and upper bits).
+///
+/// # Non-blocking function
+///
+/// This is a non-blocking SBI call and it may do partial/no writes if
+/// the debug console is not able to accept more bytes.
+///
+/// # Return value
+///
+/// The number of bytes written is returned in `SbiRet.value` and the
+/// possible return error codes returned in `SbiRet.error` are shown in
+/// the table below:
+///
+/// | Return code               | Description
+/// |:--------------------------|:----------------------------------------------
+/// | `SbiRet::success()`       | Bytes written successfully.
+/// | `SbiRet::invalid_param()` | The memory pointed to by `bytes` does not satisfy the requirements described in shared memory physical address range.
+/// | `SbiRet::failed()`        | Failed to write due to I/O errors.
+///
+/// This function is defined in RISC-V SBI Specification chapter 12.1.
+#[inline]
+pub fn console_write(bytes: Physical<&[u8]>) -> SbiRet {
+    sbi_call_3(
+        EID_DBCN,
+        CONSOLE_WRITE,
+        bytes.num_bytes(),
+        bytes.phys_addr_lo(),
+        bytes.phys_addr_hi(),
+    )
+}
+
+/// Read bytes from the debug console into an output memory.
+///
+/// # Parameters
+///
+/// The `bytes` parameter specifies the output memory, including the maximum
+/// bytes which can be written, and its memory physical base address
+/// (both lower and upper bits).
+///
+/// # Non-blocking function
+///
+/// This is a non-blocking SBI call and it will not write anything
+/// into the output memory if there are no bytes to be read in the
+/// debug console.
+///
+/// # Return value
+///
+/// The number of bytes read is returned in `SbiRet.value` and the
+/// possible return error codes returned in `SbiRet.error` are shown in
+/// the table below:
+///
+/// | Return code               | Description
+/// |:--------------------------|:----------------------------------------------
+/// | `SbiRet::success()`       | Bytes read successfully.
+/// | `SbiRet::invalid_param()` | The memory pointed to by `bytes` does not satisfy the requirements described in shared memory physical address range.
+/// | `SbiRet::failed()`        | Failed to read due to I/O errors.
+///
+/// This function is defined in RISC-V SBI Specification chapter 12.2.
+pub fn console_read(bytes: Physical<&mut [u8]>) -> SbiRet {
+    sbi_call_3(
+        EID_DBCN,
+        CONSOLE_READ,
+        bytes.num_bytes(),
+        bytes.phys_addr_lo(),
+        bytes.phys_addr_hi(),
+    )
+}
+
+/// Write a single byte to the debug console.
+///
+/// # Blocking function
+///
+/// This is a blocking SBI call and it will only return after writing
+/// the specified byte to the debug console. It will also return, with
+/// `SbiRet::failed()`, if there are I/O errors.
+/// # Return value
+///
+/// The `SbiRet.value` is set to zero and the possible return error
+/// codes returned in `SbiRet.error` are shown in the table below:
+///
+/// | Return code               | Description
+/// |:--------------------------|:----------------------------------------------
+/// | `SbiRet::success()`       | Byte written successfully.
+/// | `SbiRet::failed()`        | Failed to write the byte due to I/O errors.
+///
+/// This function is defined in RISC-V SBI Specification chapter 12.3.
+#[inline]
+pub fn console_write_byte(byte: u8) -> SbiRet {
+    sbi_call_1(EID_DBCN, CONSOLE_WRITE_BYTE, byte as usize)
+}

+ 227 - 0
sbi-rt/src/hsm.rs

@@ -0,0 +1,227 @@
+//! Chapter 9. Hart State Management Extension (EID #0x48534D "HSM")
+
+use crate::binary::{sbi_call_0, sbi_call_1, sbi_call_3, SbiRet};
+
+use sbi_spec::hsm::{EID_HSM, HART_GET_STATUS, HART_START, HART_STOP, HART_SUSPEND};
+
+/// Start executing the given hart at specified address in supervisor-mode.
+///
+/// This call is asynchronous - more specifically, the `hart_start()` may return before target hart
+/// starts executing as long as the SBI implemenation is capable of ensuring the return code is accurate.
+///
+/// It is recommended that if the SBI implementation is a platform runtime firmware executing in machine-mode (M-mode)
+/// then it MUST configure PMP and other the M-mode state before executing in supervisor-mode.
+///
+/// # Parameters
+///
+/// - The `hartid` parameter specifies the target hart which is to be started.
+/// - The `start_addr` parameter points to a runtime-specified physical address, where the hart can start executing in supervisor-mode.
+/// - The `opaque` parameter is a `usize` value which will be set in the `a1` register when the hart starts executing at `start_addr`.
+///
+/// *NOTE:* A single `usize` parameter is sufficient as `start_addr`,
+/// because the hart will start execution in the supervisor-mode with MMU off,
+/// hence the `start_addr` must be less than XLEN bits wide.
+///
+/// # Behavior
+///
+/// The target hart jumps to supervisor mode at address specified by `start_addr` with following values in specific registers.
+///
+/// | Register Name | Register Value
+/// |:--------------|:--------------
+/// | `satp`        | 0
+/// | `sstatus.SIE` | 0
+/// | a0            | hartid
+/// | a1            | `opaque` parameter
+///
+/// # Return value
+///
+/// The possible return error codes returned in `SbiRet.error` are shown in the table below:
+///
+/// | Return code                   | Description
+/// |:------------------------------|:----------------------------------------------
+/// | `SbiRet::success()`           | Hart was previously in stopped state. It will start executing from `start_addr`.
+/// | `SbiRet::invalid_address()`   | `start_addr` is not valid, possibly due to the following reasons: it is not a valid physical address, or the address is prohibited by PMP or H-extension G-stage to run in supervisor-mode.
+/// | `SbiRet::invalid_param()`     | `hartid` is not a valid hartid as corresponding hart cannot started in supervisor mode.
+/// | `SbiRet::already_available()` | The given hartid is already started.
+/// | `SbiRet::failed()`            | The start request failed for unknown reasons.
+///
+/// This function is defined in RISC-V SBI Specification chapter 9.1.
+#[inline]
+pub fn hart_start(hartid: usize, start_addr: usize, opaque: usize) -> SbiRet {
+    sbi_call_3(EID_HSM, HART_START, hartid, start_addr, opaque)
+}
+
+/// Stop executing the calling hart in supervisor-mode.
+///
+/// This function requests the SBI implementation to stop executing the calling hart in
+/// supervisor-mode and return its ownership to the SBI implementation.
+///
+/// This call is not expected to return under normal conditions.
+/// The `sbi_hart_stop()` must be called with the supervisor-mode interrupts disabled.
+///
+/// # Return value
+///
+/// The possible return error codes returned in `SbiRet.error` are shown in the table below:
+///
+/// | Error code  | Description
+/// |:------------|:------------
+/// | `SbiRet::failed()` | Failed to stop execution of the current hart
+///
+/// This function is defined in RISC-V SBI Specification chapter 9.2.
+#[inline]
+pub fn hart_stop() -> SbiRet {
+    sbi_call_0(EID_HSM, HART_STOP)
+}
+
+/// Get the current status (or HSM state id) of the given hart.
+///
+/// The harts may transition HSM states at any time due to any concurrent `hart_start()`
+/// or `hart_stop()` calls, the return value from this function may not represent the actual state
+/// of the hart at the time of return value verification.
+///
+/// # Parameters
+///
+/// The `hartid` parameter specifies the target hart which status is required.
+///
+/// # Return value
+///
+/// The possible status values returned in `SbiRet.value` are shown in the table below:
+///
+/// | Name          | Value | Description
+/// |:--------------|:------|:-------------------------
+/// | STARTED       |   0   | Hart Started
+/// | STOPPED       |   1   | Hart Stopped
+/// | START_PENDING |   2   | Hart start request pending
+/// | STOP_PENDING  |   3   | Hart stop request pending
+///
+/// The possible return error codes returned in `SbiRet.error` are shown in the table below:
+///
+/// | Error code                | Description
+/// |:--------------------------|:------------
+/// | `SbiRet::invalid_param()` | The given `hartid` is not valid
+///
+/// This function is defined in RISC-V SBI Specification chapter 9.3.
+#[inline]
+pub fn hart_get_status(hartid: usize) -> SbiRet {
+    sbi_call_1(EID_HSM, HART_GET_STATUS, hartid)
+}
+
+/// Put the calling hart into suspend or platform specific lower power states.
+///
+/// This function requests the SBI implementation to put the calling hart in a platform specfic suspend
+/// (or low power) state specified by the `suspend_type` parameter.
+///
+/// The hart will automatically come out of suspended state and resume normal execution
+/// when it recieves an interrupt or platform specific hardware event.
+///
+/// # Suspend behavior
+///
+/// The platform specific suspend states for a hart can be either retentive or non-rententive in nature.
+///
+/// A retentive suspend state will preserve hart register and CSR values for all privilege modes,
+/// whereas a non-retentive suspend state will not preserve hart register and CSR values.
+///
+/// # Resuming
+///
+/// Resuming from a retentive suspend state is straight forward and the supervisor-mode software
+/// will see SBI suspend call return without any failures.
+///
+/// Resuming from a non-retentive suspend state is relatively more involved and requires software
+/// to restore various hart registers and CSRs for all privilege modes.
+/// Upon resuming from non-retentive suspend state, the hart will jump to supervisor-mode at address
+/// specified by `resume_addr` with specific registers values described in the table below:
+///
+/// | Register Name | Register Value
+/// |:--------------|:--------------
+/// | `satp`        | 0
+/// | `sstatus.SIE` | 0
+/// | a0            | hartid
+/// | a1            | `opaque` parameter
+///
+/// # Parameters
+///
+/// The `suspend_type` parameter is 32 bits wide and the possible values are shown in the table below:
+///
+/// | Value                   | Description
+/// |:------------------------|:--------------
+/// | 0x00000000              | Default retentive suspend
+/// | 0x00000001 - 0x0FFFFFFF | _Reserved for future use_
+/// | 0x10000000 - 0x7FFFFFFF | Platform specific retentive suspend
+/// | 0x80000000              | Default non-retentive suspend
+/// | 0x80000001 - 0x8FFFFFFF | _Reserved for future use_
+/// | 0x90000000 - 0xFFFFFFFF | Platform specific non-retentive suspend
+/// | > 0xFFFFFFFF            | _Reserved_
+///
+/// The `resume_addr` parameter points to a runtime-specified physical address,
+/// where the hart can resume execution in supervisor-mode after a non-retentive
+/// suspend.
+///
+/// *NOTE:* A single `usize` parameter is sufficient as `resume_addr`,
+/// because the hart will resume execution in the supervisor-mode with MMU off,
+/// hence the `resume_addr` must be less than XLEN bits wide.
+///
+/// The `opaque` parameter is an XLEN-bit value which will be set in the `a1`
+/// register when the hart resumes exectution at `resume_addr` after a
+/// non-retentive suspend.
+///
+/// # Return value
+///
+/// The possible return error codes returned in `SbiRet.error` are shown in the table below:
+///
+/// | Error code                  | Description
+/// |:----------------------------|:------------
+/// | `SbiRet::success()`         | Hart has suspended and resumed back successfully from a retentive suspend state.
+/// | `SbiRet::invalid_param()`   | `suspend_type` is not valid.
+/// | `SbiRet::not_supported()`   | `suspend_type` is valid but not implemented.
+/// | `SbiRet::invalid_address()` | `resume_addr` is not valid, possibly due to the following reasons: it is not a valid physical address, or the address is prohibited by PMP or H-extension G-stage to run in supervisor-mode.
+/// | `SbiRet::failed()`          | The suspend request failed for unknown reasons.
+///
+/// This function is defined in RISC-V SBI Specification chapter 9.4.
+#[inline]
+pub fn hart_suspend<T>(suspend_type: T, resume_addr: usize, opaque: usize) -> SbiRet
+where
+    T: SuspendType,
+{
+    sbi_call_3(
+        EID_HSM,
+        HART_SUSPEND,
+        suspend_type.raw() as _,
+        resume_addr,
+        opaque,
+    )
+}
+
+/// A valid suspend type for hart state monitor.
+pub trait SuspendType {
+    /// Get a raw value to pass to SBI environment.
+    fn raw(&self) -> u32;
+}
+
+#[cfg(feature = "integer-impls")]
+impl SuspendType for u32 {
+    #[inline]
+    fn raw(&self) -> u32 {
+        *self
+    }
+}
+
+macro_rules! define_suspend_type {
+    ($($struct:ident($value:expr) #[$doc:meta])*) => {
+        $(
+            #[derive(Clone, Copy, Debug)]
+            #[$doc]
+            pub struct $struct;
+            impl SuspendType for $struct {
+                #[inline]
+                fn raw(&self) -> u32 {
+                    $value
+                }
+            }
+        )*
+    };
+}
+
+define_suspend_type! {
+    Retentive(sbi_spec::hsm::suspend_type::RETENTIVE) /// Default retentive hart suspension.
+    NonRetentive(sbi_spec::hsm::suspend_type::NON_RETENTIVE) /// Default non-retentive hart suspension.
+}

+ 160 - 0
sbi-rt/src/legacy.rs

@@ -0,0 +1,160 @@
+//! Chapter 5. Legacy Extensions (EIDs #0x00 - #0x0F)
+
+pub use sbi_spec::legacy::*;
+
+/// §5.1
+#[deprecated = "replaced by `set_timer` from Timer extension"]
+#[inline]
+pub fn set_timer(stime_value: u64) -> usize {
+    match () {
+        #[cfg(target_pointer_width = "32")]
+        () => sbi_call_legacy_2(LEGACY_SET_TIMER, stime_value as _, (stime_value >> 32) as _),
+        #[cfg(target_pointer_width = "64")]
+        () => sbi_call_legacy_1(LEGACY_SET_TIMER, stime_value as _),
+    }
+}
+
+/// §5.2
+///
+/// No replacement.
+#[deprecated = "no replacement"]
+#[inline]
+pub fn console_putchar(c: usize) -> usize {
+    sbi_call_legacy_1(LEGACY_CONSOLE_PUTCHAR, c)
+}
+
+/// §5.3
+///
+/// No replacement.
+#[deprecated = "no replacement"]
+#[inline]
+pub fn console_getchar() -> usize {
+    sbi_call_legacy_0(LEGACY_CONSOLE_GETCHAR)
+}
+
+/// §5.4
+///
+/// No replacement. Just clear `sip.SSIP` directly.
+#[deprecated = "you can clear `sip.SSIP` CSR bit directly"]
+#[inline]
+pub fn clear_ipi() -> usize {
+    sbi_call_legacy_0(LEGACY_CLEAR_IPI)
+}
+
+/// §5.5
+///
+/// Replaced by [`send_ipi`](super::send_ipi) from [`sPI`](super::EID_SPI) extension.
+#[deprecated = "replaced by `send_ipi` from `sPI` extension"]
+#[inline]
+pub fn send_ipi(hart_mask: usize) -> usize {
+    sbi_call_legacy_1(LEGACY_SEND_IPI, hart_mask)
+}
+
+/// §5.6
+///
+/// Replaced by [`remote_fence_i`](super::remote_fence_i) from [`RFNC`](super::EID_RFNC) extension.
+#[deprecated = "replaced by `remote_fence_i` from `RFNC` extension"]
+#[inline]
+pub fn remote_fence_i(hart_mask: usize) -> usize {
+    sbi_call_legacy_1(LEGACY_REMOTE_FENCE_I, hart_mask)
+}
+
+/// §5.7
+///
+/// Replaced by [`remote_sfence_vma`](super::remote_sfence_vma) from [`RFNC`](super::EID_RFNC) extension.
+#[deprecated = "replaced by `remote_sfence_vma` from `RFNC` extension"]
+#[inline]
+pub fn remote_fence_vma(hart_mask: usize, start: usize, size: usize) -> usize {
+    sbi_call_legacy_3(LEGACY_REMOTE_SFENCE_VMA, hart_mask, start, size)
+}
+
+/// §5.8
+///
+/// Replaced by [`remote_sfence_vma_asid`](super::remote_sfence_vma_asid) from [`RFNC`](super::EID_RFNC) extension.
+#[deprecated = "replaced by `remote_sfence_vma_asid` from `RFNC` extension"]
+#[inline]
+pub fn remote_fence_vma_asid(hart_mask: usize, start: usize, size: usize, asid: usize) -> usize {
+    sbi_call_legacy_4(LEGACY_REMOTE_SFENCE_VMA_ASID, hart_mask, start, size, asid)
+}
+
+/// §5.9
+///
+/// Replaced by [`system_reset`](super::system_reset) from [`SRST`](super::EID_SRST) extension.
+#[deprecated = "replaced by `system_reset` from System `SRST` extension"]
+#[inline]
+pub fn shutdown() -> ! {
+    sbi_call_legacy_0(LEGACY_SHUTDOWN);
+    unreachable!()
+}
+
+#[inline(always)]
+fn sbi_call_legacy_0(eid: usize) -> usize {
+    let error;
+    unsafe {
+        core::arch::asm!(
+            "ecall",
+            in("a7") eid,
+            lateout("a0") error,
+        );
+    }
+    error
+}
+
+#[inline(always)]
+fn sbi_call_legacy_1(eid: usize, arg0: usize) -> usize {
+    let error;
+    unsafe {
+        core::arch::asm!(
+            "ecall",
+            in("a7") eid,
+            inlateout("a0") arg0 => error,
+        );
+    }
+    error
+}
+
+#[cfg(target_pointer_width = "32")]
+#[inline(always)]
+fn sbi_call_legacy_2(eid: usize, arg0: usize, arg1: usize) -> usize {
+    let error;
+    unsafe {
+        core::arch::asm!(
+            "ecall",
+            in("a7") eid,
+            inlateout("a0") arg0 => error,
+            in("a1") arg1,
+        );
+    }
+    error
+}
+
+#[inline(always)]
+fn sbi_call_legacy_3(eid: usize, arg0: usize, arg1: usize, arg2: usize) -> usize {
+    let error;
+    unsafe {
+        core::arch::asm!(
+            "ecall",
+            in("a7") eid,
+            inlateout("a0") arg0 => error,
+            in("a1") arg1,
+            in("a2") arg2,
+        );
+    }
+    error
+}
+
+#[inline(always)]
+fn sbi_call_legacy_4(eid: usize, arg0: usize, arg1: usize, arg2: usize, arg3: usize) -> usize {
+    let error;
+    unsafe {
+        core::arch::asm!(
+            "ecall",
+            in("a7") eid,
+            inlateout("a0") arg0 => error,
+            in("a1") arg1,
+            in("a2") arg2,
+            in("a3") arg3,
+        );
+    }
+    error
+}

+ 46 - 0
sbi-rt/src/lib.rs

@@ -0,0 +1,46 @@
+//! Simple RISC-V SBI runtime primitives.
+#![no_std]
+#[cfg_attr(not(feature = "legacy"), deny(missing_docs))]
+// §3
+mod binary;
+// §4
+mod base;
+// §5
+#[cfg(feature = "legacy")]
+pub mod legacy;
+// §6
+mod time;
+// §7
+mod spi;
+// §8
+mod rfnc;
+// §9
+mod hsm;
+// §10
+mod srst;
+// §11
+mod pmu;
+// §12
+mod dbcn;
+// §13
+mod susp;
+// §14
+mod cppc;
+// §15
+mod nacl;
+// §16
+mod sta;
+
+pub use base::*;
+pub use binary::*;
+pub use cppc::*;
+pub use dbcn::*;
+pub use hsm::*;
+pub use nacl::*;
+pub use pmu::*;
+pub use rfnc::*;
+pub use spi::*;
+pub use srst::*;
+pub use sta::*;
+pub use susp::*;
+pub use time::*;

+ 146 - 0
sbi-rt/src/nacl.rs

@@ -0,0 +1,146 @@
+//! Chapter 15. Nested Acceleration Extension (EID #0x4E41434C "NACL")
+
+use crate::binary::{sbi_call_0, sbi_call_1, sbi_call_3, SbiRet};
+
+use sbi_spec::{
+    binary::SharedPtr,
+    nacl::{shmem_size, EID_NACL, PROBE_FEATURE, SET_SHMEM, SYNC_CSR, SYNC_HFENCE, SYNC_SRET},
+};
+
+/// Probe a nested acceleration feature.
+///
+/// This is a mandatory function of the SBI nested acceleration extension.
+///
+/// # Parameters
+///
+/// The `feature_id` parameter specifies the nested acceleration feature to probe.
+/// Possible feature IDs are defined in the table below:
+///
+/// # Return value
+///
+/// This function always returns `SbiRet::success()` in `SbiRet.error`.
+/// It returns 0 in `SbiRet.value` if the given `feature_id` is not available,
+/// or 1 in `SbiRet.value` if it is available.
+///
+/// This function is defined in RISC-V SBI Specification chapter 15.5.
+#[inline]
+pub fn nacl_probe_feature(feature_id: u32) -> SbiRet {
+    sbi_call_1(EID_NACL, PROBE_FEATURE, feature_id as _)
+}
+
+/// Set and enable the shared memory for nested acceleration on the calling hart.
+///
+/// This is a mandatory function of the SBI nested acceleration extension.
+///
+/// # Parameters
+///
+/// If `shmem` parameter is not all-ones bitwise then `shmem` specifies the shared
+/// memory physical base address. `shmem` MUST be 4096 bytes (i.e. page) aligned and
+/// the size of the shared memory must be `4096 + (XLEN * 128)` bytes.
+///
+/// If `shmem` parameter is all-ones bitwise then the nested acceleration features
+/// are disabled.
+///
+/// The `flags` parameter is reserved for future use and must be zero.
+///
+/// The possible error codes returned in `SbiRet.error` are shown in the table below:
+///
+/// | Error code                  | Description
+/// |:----------------------------|:---------------------------------
+/// | `SbiRet::success()`         | Shared memory was set or cleared successfully.
+/// | `SbiRet::invalid_param()`   | The `flags` parameter is not zero or or the `shmem` parameter is not 4096 bytes aligned.
+/// | `SbiRet::invalid_address()` | The shared memory pointed to by the `shmem` parameters does not satisfy the requirements.
+///
+/// This function is defined in RISC-V SBI Specification chapter 15.6.
+#[inline]
+pub fn nacl_set_shmem(shmem: SharedPtr<[u8; shmem_size::NATIVE]>, flags: usize) -> SbiRet {
+    sbi_call_3(
+        EID_NACL,
+        SET_SHMEM,
+        shmem.phys_addr_lo(),
+        shmem.phys_addr_hi(),
+        flags,
+    )
+}
+
+/// Synchronize CSRs in the nested acceleration shared memory.
+///
+/// This is an optional function which is only available if the SBI_NACL_FEAT_SYNC_CSR feature is available.
+///
+/// # Parameters
+///
+/// The parameter `csr_num` specifies the set of RISC-V H-extension CSRs to be synchronized.
+///
+/// If `csr_num` is all-ones bitwise then all RISC-V H-extension CSRs implemented by the SBI implementation (or L0 hypervisor) are synchronized.
+///
+/// If `(csr_num & 0x300) == 0x200` and `csr_num < 0x1000` then only a single
+/// RISC-V H-extension CSR specified by the csr_num parameter is synchronized.
+///
+/// # Return value
+///
+/// The possible error codes returned in `SbiRet.error` are shown in the table below:
+///
+/// | Error code                | Description
+/// |:--------------------------|:---------------------------------
+/// | `SbiRet::success()`       | CSRs synchronized successfully.
+/// | `SbiRet::not_supported()` | SBI_NACL_FEAT_SYNC_CSR feature is not available.
+/// | `SbiRet::invalid_param()` | `csr_num` is not all-ones bitwise and either: <br> * `(csr_num & 0x300) != 0x200` or <br> * `csr_num >= 0x1000` or <br> * `csr_num` is not implemented by the SBI implementation
+/// | `SbiRet::no_shmem()`      | Nested acceleration shared memory not available.
+///
+/// This function is defined in RISC-V SBI Specification chapter 15.7.
+#[inline]
+pub fn nacl_sync_csr(csr_num: usize) -> SbiRet {
+    sbi_call_1(EID_NACL, SYNC_CSR, csr_num)
+}
+
+/// Synchronize HFENCEs in the nested acceleration shared memory.
+///
+/// This is an optional function which is only available if the SBI_NACL_FEAT_SYNC_HFENCE feature is available.
+///
+/// # Parameters
+///
+/// The parameter `entry_index` specifies the set of nested HFENCE entries to be synchronized.
+///
+/// If `entry_index` is all-ones bitwise then all nested HFENCE entries are synchronized.
+///
+/// If `entry_index < (3840 / XLEN)` then only a single nested HFENCE entry specified by the `entry_index` parameter is synchronized
+///
+/// # Return value
+///
+/// The possible error codes returned in `SbiRet.error` are shown in the table below:
+///
+/// | Error code                | Description
+/// |:--------------------------|:---------------------------------
+/// | `SbiRet::success()`       | HFENCEs synchronized successfully.
+/// | `SbiRet::not_supported()` | SBI_NACL_FEAT_SYNC_HFENCE feature is not available.
+/// | `SbiRet::invalid_param()` | `entry_index` is not all-ones bitwise and `entry_index >= (3840 / XLEN)`.
+/// | `SbiRet::no_shmem()`      | Nested acceleration shared memory not available.
+///
+/// This function is defined in RISC-V SBI Specification chapter 15.8.
+#[inline]
+pub fn nacl_sync_hfence(entry_index: usize) -> SbiRet {
+    sbi_call_1(EID_NACL, SYNC_HFENCE, entry_index)
+}
+
+/// Synchronize CSRs and HFENCEs in the NACL shared memory and emulate the SRET instruction.
+///
+/// This is an optional function which is only available if the SBI_NACL_FEAT_SYNC_SRET feature is available.
+///
+/// This function is used by supervisor software (or L1 hypervisor) to do a synchronize SRET request
+/// and the SBI implementation (or L0 hypervisor) MUST handle it.
+///
+/// # Return value
+///
+/// This function does not return upon success and the possible error codes
+/// returned in `SbiRet.error` upon failure are shown in table below:
+///
+/// | Error code                | Description
+/// |:--------------------------|:------------
+/// | `SbiRet::no_shmem()`      | Nested acceleration shared memory not available.
+/// | `SbiRet::not_supported()` | SBI_NACL_FEAT_SYNC_SRET feature is not available.
+///
+/// This function is defined in RISC-V SBI Specification chapter 15.9.
+#[inline]
+pub fn nacl_sync_sret() -> SbiRet {
+    sbi_call_0(EID_NACL, SYNC_SRET)
+}

+ 326 - 0
sbi-rt/src/pmu.rs

@@ -0,0 +1,326 @@
+//! Chapter 11. Performance Monitoring Unit Extension (EID #0x504D55 "PMU")
+
+use crate::binary::{sbi_call_0, sbi_call_1, sbi_call_3, SbiRet};
+
+use sbi_spec::pmu::{
+    COUNTER_CONFIG_MATCHING, COUNTER_FW_READ, COUNTER_FW_READ_HI, COUNTER_GET_INFO, COUNTER_START,
+    COUNTER_STOP, EID_PMU, NUM_COUNTERS,
+};
+
+/// Returns the number of counters, both hardware and firmware.
+///
+/// This call would always succeed without returning any error.
+///
+/// This function is defined in RISC-V SBI Specification chapter 11.5.
+#[inline]
+pub fn pmu_num_counters() -> usize {
+    sbi_call_0(EID_PMU, NUM_COUNTERS).value
+}
+
+/// Get details about the specified counter.
+///
+/// The value returned includes details such as underlying CSR number, width of the counter,
+/// type of counter hardware/firmware, etc.
+///
+/// The `counter_info` returned by this SBI call is encoded as follows:
+///
+/// ```text
+///     counter_info[11:0] = CSR; // (12bit CSR number)
+///     counter_info[17:12] = Width; // (One less than number of bits in CSR)
+///     counter_info[XLEN-2:18] = Reserved; // Reserved for future use
+///     counter_info[XLEN-1] = Type; // (0 = hardware and 1 = firmware)
+/// ```
+/// If `counter_info.type` == `1` then `counter_info.csr` and `counter_info.width` should be ignored.
+///
+/// This function is defined in RISC-V SBI Specification chapter 11.6.
+///
+/// # Return value
+///
+/// Returns the `counter_info` described above in `SbiRet.value`.
+///
+/// The possible return error codes returned in `SbiRet.error` are shown in the table below:    
+///
+/// | Return code               | Description
+/// |:--------------------------|:----------------------------------------------
+/// | `SbiRet::success()`       | `counter_info` read successfully.
+/// | `SbiRet::invalid_param()` | `counter_idx` points to an invalid counter.
+///
+/// This function is defined in RISC-V SBI Specification chapter 11.6.
+#[inline]
+pub fn pmu_counter_get_info(counter_idx: usize) -> SbiRet {
+    sbi_call_1(EID_PMU, COUNTER_GET_INFO, counter_idx)
+}
+
+/// Find and configure a counter from a set of counters.
+///
+/// The counters to be found and configured should not be started (or enabled)
+/// and should be able to monitor the specified event.
+///
+/// # Parameters
+///
+/// The `counter_idx_base` and `counter_idx_mask` parameters represent the set of counters,
+/// whereas the `event_idx` represent the event to be monitored
+/// and `event_data` represents any additional event configuration.
+///
+/// The `config_flags` parameter represent additional counter configuration and filter flags.
+/// The bit definitions of the `config_flags` parameter are shown in the table below:
+///
+/// | Flag Name                    | Bits       | Description
+/// |:-----------------------------|:-----------|:------------
+/// | SBI_PMU_CFG_FLAG_SKIP_MATCH  | 0:0        | Skip the counter matching
+/// | SBI_PMU_CFG_FLAG_CLEAR_VALUE | 1:1        | Clear (or zero) the counter value in counter configuration
+/// | SBI_PMU_CFG_FLAG_AUTO_START  | 2:2        | Start the counter after configuring a matching counter
+/// | SBI_PMU_CFG_FLAG_SET_VUINH   | 3:3        | Event counting inhibited in VU-mode
+/// | SBI_PMU_CFG_FLAG_SET_VSINH   | 4:4        | Event counting inhibited in VS-mode
+/// | SBI_PMU_CFG_FLAG_SET_UINH    | 5:5        | Event counting inhibited in U-mode
+/// | SBI_PMU_CFG_FLAG_SET_SINH    | 6:6        | Event counting inhibited in S-mode
+/// | SBI_PMU_CFG_FLAG_SET_MINH    | 7:7        | Event counting inhibited in M-mode
+/// | _RESERVED_                   | 8:(XLEN-1) | _All non-zero values are reserved for future use._
+///
+/// *NOTE:* When *SBI_PMU_CFG_FLAG_SKIP_MATCH* is set in `config_flags`, the
+/// SBI implementation will unconditionally select the first counter from the
+/// set of counters specified by the `counter_idx_base` and `counter_idx_mask`.
+///
+/// *NOTE:* The *SBI_PMU_CFG_FLAG_AUTO_START* flag in `config_flags` has no
+/// impact on the counter value.    
+///
+/// *NOTE:* The `config_flags[3:7]` bits are event filtering hints so these
+/// can be ignored or overridden by the SBI implementation for security concerns
+/// or due to lack of event filtering support in the underlying RISC-V platform.
+///
+/// # Return value
+///
+/// Returns the `counter_idx` in `sbiret.value` upon success.
+///
+/// In case of failure, the possible error codes returned in `sbiret.error` are shown in the table below:    
+///
+/// | Return code               | Description
+/// |:--------------------------|:----------------------------------------------
+/// | `SbiRet::success()`       | counter found and configured successfully.
+/// | `SbiRet::invalid_param()` | set of counters has an invalid counter.
+/// | `SbiRet::not_supported()` | none of the counters can monitor specified event.
+///
+/// This function is defined in RISC-V SBI Specification chapter 11.7.
+#[inline]
+pub fn pmu_counter_config_matching<T>(
+    counter_idx_base: usize,
+    counter_idx_mask: usize,
+    config_flags: T,
+    event_idx: usize,
+    event_data: u64,
+) -> SbiRet
+where
+    T: ConfigFlags,
+{
+    match () {
+        #[cfg(target_pointer_width = "32")]
+        () => crate::binary::sbi_call_6(
+            EID_PMU,
+            COUNTER_CONFIG_MATCHING,
+            counter_idx_base,
+            counter_idx_mask,
+            config_flags.raw(),
+            event_idx,
+            event_data as _,
+            (event_data >> 32) as _,
+        ),
+        #[cfg(target_pointer_width = "64")]
+        () => crate::binary::sbi_call_5(
+            EID_PMU,
+            COUNTER_CONFIG_MATCHING,
+            counter_idx_base,
+            counter_idx_mask,
+            config_flags.raw(),
+            event_idx,
+            event_data as _,
+        ),
+    }
+}
+
+/// Start or enable a set of counters on the calling hart with the specified initial value.
+///
+/// # Parameters
+///
+/// The `counter_idx_base` and `counter_idx_mask` parameters represent the set of counters.
+/// whereas the `initial_value` parameter specifies the initial value of the counter.
+///
+/// The bit definitions of the `start_flags` parameter are shown in the table below:
+///
+/// | Flag Name                    | Bits       | Description
+/// |:-----------------------------|:-----------|:------------
+/// | SBI_PMU_START_SET_INIT_VALUE | 0:0        | Set the value of counters based on the `initial_value` parameter.
+/// | _RESERVED_                   | 1:(XLEN-1) | _All non-zero values are reserved for future use._
+///
+/// *NOTE*: When `SBI_PMU_START_SET_INIT_VALUE` is not set in `start_flags`, the counter value will
+/// not be modified and event counting will start from current counter value.
+///
+/// # Return value
+///
+/// The possible return error codes returned in `SbiRet.error` are shown in the table below:    
+///
+/// | Return code                 | Description
+/// |:----------------------------|:----------------------------------------------
+/// | `SbiRet::success()`         | counter started successfully.
+/// | `SbiRet::invalid_param()`   | some of the counters specified in parameters are invalid.
+/// | `SbiRet::already_started()` | some of the counters specified in parameters are already started.
+///
+/// This function is defined in RISC-V SBI Specification chapter 11.8.
+#[inline]
+pub fn pmu_counter_start<T>(
+    counter_idx_base: usize,
+    counter_idx_mask: usize,
+    start_flags: T,
+    initial_value: u64,
+) -> SbiRet
+where
+    T: StartFlags,
+{
+    match () {
+        #[cfg(target_pointer_width = "32")]
+        () => crate::binary::sbi_call_5(
+            EID_PMU,
+            COUNTER_START,
+            counter_idx_base,
+            counter_idx_mask,
+            start_flags.raw(),
+            initial_value as _,
+            (initial_value >> 32) as _,
+        ),
+        #[cfg(target_pointer_width = "64")]
+        () => crate::binary::sbi_call_4(
+            EID_PMU,
+            COUNTER_START,
+            counter_idx_base,
+            counter_idx_mask,
+            start_flags.raw(),
+            initial_value as _,
+        ),
+    }
+}
+
+/// Stop or disable a set of counters on the calling hart.
+///
+/// # Parameters
+///
+/// The `counter_idx_base` and `counter_idx_mask` parameters represent the set of counters.
+/// The bit definitions of the `stop_flags` parameter are shown in the table below:
+///
+/// | Flag Name               | Bits       | Description
+/// |:------------------------|:-----------|:------------
+/// | SBI_PMU_STOP_FLAG_RESET | 0:0        | Reset the counter to event mapping.
+/// | _RESERVED_              | 1:(XLEN-1) | *All non-zero values are reserved for future use.*
+///
+/// # Return value
+///
+/// The possible return error codes returned in `SbiRet.error` are shown in the table below:    
+///
+/// | Return code                 | Description
+/// |:----------------------------|:----------------------------------------------
+/// | `SbiRet::success()`         | counter stopped successfully.
+/// | `SbiRet::invalid_param()`   | some of the counters specified in parameters are invalid.
+/// | `SbiRet::already_stopped()` | some of the counters specified in parameters are already stopped.
+///
+/// This function is defined in RISC-V SBI Specification chapter 11.9.
+#[inline]
+pub fn pmu_counter_stop<T>(
+    counter_idx_base: usize,
+    counter_idx_mask: usize,
+    stop_flags: T,
+) -> SbiRet
+where
+    T: StopFlags,
+{
+    sbi_call_3(
+        EID_PMU,
+        COUNTER_STOP,
+        counter_idx_base,
+        counter_idx_mask,
+        stop_flags.raw(),
+    )
+}
+
+/// Provide the current value of a firmware counter.
+///
+/// On RV32 systems, the `SbiRet.value` will only contain the lower 32 bits of the current
+/// firmware counter value.
+///
+/// # Parameters
+///
+/// This function should be only used to read a firmware counter. It will return an error
+/// when user provides a hardware counter in `counter_idx` parameter.
+///
+/// # Return value
+///
+/// The possible return error codes returned in `SbiRet.error` are shown in the table below:    
+///
+/// | Return code               | Description
+/// |:--------------------------|:----------------------------------------------
+/// | `SbiRet::success()`       | firmware counter read successfully.
+/// | `SbiRet::invalid_param()` | `counter_idx` points to a hardware counter or an invalid counter.
+///
+/// This function is defined in RISC-V SBI Specification chapter 11.10.
+#[inline]
+pub fn pmu_counter_fw_read(counter_idx: usize) -> SbiRet {
+    sbi_call_1(EID_PMU, COUNTER_FW_READ, counter_idx)
+}
+
+/// Provide the upper 32 bits of the current firmware counter value.
+///
+/// This function always returns zero in `SbiRet.value` for RV64 (or higher) systems.
+///
+/// # Return value
+///
+/// The possible return error codes returned in `SbiRet.error` are shown in the table below:
+///
+/// | Return code               | Description
+/// |:--------------------------|:----------------------------------------------
+/// | `SbiRet::success()`       | firmware counter read successfully.
+/// | `SbiRet::invalid_param()` | `counter_idx` points to a hardware counter or an invalid counter.
+///
+/// This function is defined in RISC-V SBI Specification chapter 11.11.
+#[inline]
+pub fn pmu_counter_fw_read_hi(counter_idx: usize) -> SbiRet {
+    sbi_call_1(EID_PMU, COUNTER_FW_READ_HI, counter_idx)
+}
+
+/// Flags to configure performance counter.
+pub trait ConfigFlags {
+    /// Get a raw value to pass to SBI environment.
+    fn raw(&self) -> usize;
+}
+
+#[cfg(feature = "integer-impls")]
+impl ConfigFlags for usize {
+    #[inline]
+    fn raw(&self) -> usize {
+        *self
+    }
+}
+
+/// Flags to start performance counter.
+pub trait StartFlags {
+    /// Get a raw value to pass to SBI environment.
+    fn raw(&self) -> usize;
+}
+
+#[cfg(feature = "integer-impls")]
+impl StartFlags for usize {
+    #[inline]
+    fn raw(&self) -> usize {
+        *self
+    }
+}
+
+/// Flags to stop performance counter.
+pub trait StopFlags {
+    /// Get a raw value to pass to SBI environment.
+    fn raw(&self) -> usize;
+}
+
+#[cfg(feature = "integer-impls")]
+impl StopFlags for usize {
+    #[inline]
+    fn raw(&self) -> usize {
+        *self
+    }
+}

+ 228 - 0
sbi-rt/src/rfnc.rs

@@ -0,0 +1,228 @@
+//! Chapter 8. RFENCE Extension (EID #0x52464E43 "RFNC")
+
+use crate::binary::{sbi_call_2, sbi_call_4, sbi_call_5, SbiRet};
+
+use sbi_spec::{
+    binary::HartMask,
+    rfnc::{
+        EID_RFNC, REMOTE_FENCE_I, REMOTE_HFENCE_GVMA, REMOTE_HFENCE_GVMA_VMID, REMOTE_HFENCE_VVMA,
+        REMOTE_HFENCE_VVMA_ASID, REMOTE_SFENCE_VMA, REMOTE_SFENCE_VMA_ASID,
+    },
+};
+
+/// Execute `FENCE.I` instruction on remote harts.
+///
+/// # Return value
+///
+/// Returns `SbiRet::success()` when remote fence was sent to all the targeted harts successfully.
+///
+/// This function is defined in RISC-V SBI Specification chapter 8.1.
+#[inline]
+pub fn remote_fence_i(hart_mask: HartMask) -> SbiRet {
+    let (hart_mask, hart_mask_base) = hart_mask.into_inner();
+    sbi_call_2(EID_RFNC, REMOTE_FENCE_I, hart_mask, hart_mask_base)
+}
+
+/// Execute `SFENCE.VMA` instructions for all address spaces on remote harts.
+///
+/// This function instructs the remote harts to execute one or more `SFENCE.VMA` instructions,
+/// covering the range of virtual addresses between `start_addr` and `size`.
+///
+/// # Return value
+///
+/// The possible return error codes returned in `SbiRet.error` are shown in the table below:
+///
+/// | Return code                 | Description
+/// |:----------------------------|:----------------------------------------------
+/// | `SbiRet::success()`         | Remote fence was sent to all the targeted harts successfully.
+/// | `SbiRet::invalid_address()` | `start_addr` or `size` is not valid.
+///
+/// This function is defined in RISC-V SBI Specification chapter 8.2.
+#[inline]
+pub fn remote_sfence_vma(hart_mask: HartMask, start_addr: usize, size: usize) -> SbiRet {
+    let (hart_mask, hart_mask_base) = hart_mask.into_inner();
+    sbi_call_4(
+        EID_RFNC,
+        REMOTE_SFENCE_VMA,
+        hart_mask,
+        hart_mask_base,
+        start_addr,
+        size,
+    )
+}
+
+/// Execute address space based `SFENCE.VMA` instructions on remote harts.
+///
+/// This function instructs the remote harts to execute one or more `SFENCE.VMA` instructions,
+/// covering the range of virtual addresses between `start_addr` and `size`.
+/// This covers only the given address space by `asid`.
+///
+/// # Return value
+///
+/// The possible return error codes returned in `SbiRet.error` are shown in the table below:
+///
+/// | Return code                 | Description
+/// |:----------------------------|:----------------------------------------------
+/// | `SbiRet::success()`         | Remote fence was sent to all the targeted harts successfully.
+/// | `SbiRet::invalid_address()` | `start_addr` or `size` is not valid.
+///
+/// This function is defined in RISC-V SBI Specification chapter 8.3.
+#[inline]
+pub fn remote_sfence_vma_asid(
+    hart_mask: HartMask,
+    start_addr: usize,
+    size: usize,
+    asid: usize,
+) -> SbiRet {
+    let (hart_mask, hart_mask_base) = hart_mask.into_inner();
+    sbi_call_5(
+        EID_RFNC,
+        REMOTE_SFENCE_VMA_ASID,
+        hart_mask,
+        hart_mask_base,
+        start_addr,
+        size,
+        asid,
+    )
+}
+
+/// Execute virtual machine id based `HFENCE.GVMA` instructions on remote harts.
+///
+/// This function instructs the remote harts to execute one or more `HFENCE.GVMA`
+/// instructions, covering the range of guest physical addresses between `start_addr`
+/// and `size` only for the given virtual machine by `vmid`.
+///
+/// This function call is only valid for harts implementing hypervisor extension.
+///
+/// # Return value
+///
+/// The possible return error codes returned in `SbiRet.error` are shown in the table below:
+///
+/// | Return code                 | Description
+/// |:----------------------------|:----------------------------------------------
+/// | `SbiRet::success()`         | Remote fence was sent to all the targeted harts successfully.
+/// | `SbiRet::not_supported()`   | This function is not supported as it is not implemented or one of the target hart doesn’t support hypervisor extension.
+/// | `SbiRet::invalid_address()` | `start_addr` or `size` is not valid.
+///
+/// This function is defined in RISC-V SBI Specification chapter 8.4.
+#[inline]
+pub fn remote_hfence_gvma_vmid(
+    hart_mask: HartMask,
+    start_addr: usize,
+    size: usize,
+    vmid: usize,
+) -> SbiRet {
+    let (hart_mask, hart_mask_base) = hart_mask.into_inner();
+    sbi_call_5(
+        EID_RFNC,
+        REMOTE_HFENCE_GVMA_VMID,
+        hart_mask,
+        hart_mask_base,
+        start_addr,
+        size,
+        vmid,
+    )
+}
+
+/// Execute `HFENCE.GVMA` instructions for all virtual machines on remote harts.
+///
+/// This function instructs the remote harts to execute one or more `HFENCE.GVMA` instructions,
+/// covering the range of guest physical addresses between `start_addr` and `size`
+/// for all the guests.
+///
+/// This function call is only valid for harts implementing hypervisor extension.
+///
+/// # Return value
+///
+/// The possible return error codes returned in `SbiRet.error` are shown in the table below:
+///
+/// | Return code                 | Description
+/// |:----------------------------|:----------------------------------------------
+/// | `SbiRet::success()`         | Remote fence was sent to all the targeted harts successfully.
+/// | `SbiRet::not_supported()`   | This function is not supported as it is not implemented or one of the target hart does not support hypervisor extension.
+/// | `SbiRet::invalid_address()` | `start_addr` or `size` is not valid.
+///
+/// This function is defined in RISC-V SBI Specification chapter 8.5.
+#[inline]
+pub fn remote_hfence_gvma(hart_mask: HartMask, start_addr: usize, size: usize) -> SbiRet {
+    let (hart_mask, hart_mask_base) = hart_mask.into_inner();
+    sbi_call_4(
+        EID_RFNC,
+        REMOTE_HFENCE_GVMA,
+        hart_mask,
+        hart_mask_base,
+        start_addr,
+        size,
+    )
+}
+
+/// Execute address space based `HFENCE.VVMA` for current virtual machine on remote harts.
+///
+/// This function instructs the remote harts to execute one or more `HFENCE.VVMA` instructions,
+/// covering the range of guest virtual addresses between `start_addr` and `size` for the given
+/// address space by `asid` and current virtual machine (by `vmid` in `hgatp` CSR)
+/// of calling hart.
+///
+/// This function call is only valid for harts implementing hypervisor extension.
+///
+/// # Return value
+///
+/// The possible return error codes returned in `SbiRet.error` are shown in the table below:
+///
+/// | Return code                 | Description
+/// |:----------------------------|:----------------------------------------------
+/// | `SbiRet::success()`         | Remote fence was sent to all the targeted harts successfully.
+/// | `SbiRet::not_supported()`   | This function is not supported as it is not implemented or one of the target hart does not support hypervisor extension.
+/// | `SbiRet::invalid_address()` | `start_addr` or `size` is not valid.
+///
+/// This function is defined in RISC-V SBI Specification chapter 8.6.
+#[inline]
+pub fn remote_hfence_vvma_asid(
+    hart_mask: HartMask,
+    start_addr: usize,
+    size: usize,
+    asid: usize,
+) -> SbiRet {
+    let (hart_mask, hart_mask_base) = hart_mask.into_inner();
+    sbi_call_5(
+        EID_RFNC,
+        REMOTE_HFENCE_VVMA_ASID,
+        hart_mask,
+        hart_mask_base,
+        start_addr,
+        size,
+        asid,
+    )
+}
+
+/// Execute `HFENCE.VVMA` for all address spaces in current virtual machine on remote harts.
+///
+/// This function instructs the remote harts to execute one or more `HFENCE.VVMA` instructions,
+/// covering the range of guest virtual addresses between `start_addr` and `size`
+/// for current virtual machine (by `vmid` in `hgatp` CSR) of calling hart.
+///
+/// This function call is only valid for harts implementing hypervisor extension.
+///
+/// # Return value
+///
+/// The possible return error codes returned in `SbiRet.error` are shown in the table below:
+///
+/// | Return code                 | Description
+/// |:----------------------------|:----------------------------------------------
+/// | `SbiRet::success()`         | Remote fence was sent to all the targeted harts successfully.
+/// | `SbiRet::not_supported()`   | This function is not supported as it is not implemented or one of the target hart doesn’t support hypervisor extension.
+/// | `SbiRet::invalid_address()` | `start_addr` or `size` is not valid.
+///
+/// This function is defined in RISC-V SBI Specification chapter 8.7.
+#[inline]
+pub fn remote_hfence_vvma(hart_mask: HartMask, start_addr: usize, size: usize) -> SbiRet {
+    let (hart_mask, hart_mask_base) = hart_mask.into_inner();
+    sbi_call_4(
+        EID_RFNC,
+        REMOTE_HFENCE_VVMA,
+        hart_mask,
+        hart_mask_base,
+        start_addr,
+        size,
+    )
+}

+ 23 - 0
sbi-rt/src/spi.rs

@@ -0,0 +1,23 @@
+//! Chapter 7. IPI Extension (EID #0x735049 "sPI: s-mode IPI")
+
+use crate::binary::{sbi_call_2, SbiRet};
+
+use sbi_spec::{
+    binary::HartMask,
+    spi::{EID_SPI, SEND_IPI},
+};
+
+/// Send an inter-processor interrupt to all harts defined in hart mask.
+///
+/// Inter-processor interrupts manifest at the receiving harts as the supervisor software interrupts.
+///
+/// # Return value
+///
+/// Should return `SbiRet::success()` if IPI was sent to all the targeted harts successfully.
+///
+/// This function is defined in RISC-V SBI Specification chapter 7.1.
+#[inline]
+pub fn send_ipi(hart_mask: HartMask) -> SbiRet {
+    let (hart_mask, hart_mask_base) = hart_mask.into_inner();
+    sbi_call_2(EID_SPI, SEND_IPI, hart_mask, hart_mask_base)
+}

+ 109 - 0
sbi-rt/src/srst.rs

@@ -0,0 +1,109 @@
+//! Chapter 10. System Reset Extension (EID #0x53525354 "SRST")
+
+use crate::binary::{sbi_call_2, SbiRet};
+
+use sbi_spec::srst::{
+    EID_SRST, RESET_REASON_NO_REASON, RESET_REASON_SYSTEM_FAILURE, RESET_TYPE_COLD_REBOOT,
+    RESET_TYPE_SHUTDOWN, RESET_TYPE_WARM_REBOOT, SYSTEM_RESET,
+};
+
+/// Reset the system based on provided `reset_type` and `reset_reason`.
+///
+/// This is a synchronous call and does not return if it succeeds.
+///
+/// # Warm reboot and cold reboot
+///
+/// When supervisor software is running natively, the SBI implementation is machine mode firmware.
+/// In this case, shutdown is equivalent to physical power down of the entire system and
+/// cold reboot is equivalent to physical power cycle of the entire system. Further, warm reboot
+/// is equivalent to a power cycle of main processor and parts of the system but not the entire system.
+///
+/// For example, on a server class system with a BMC (board management controller),
+/// a warm reboot will not power cycle the BMC whereas a cold reboot will definitely power cycle the BMC.
+///
+/// When supervisor software is running inside a virtual machine, the SBI implementation is a hypervisor.
+/// The shutdown, cold reboot and warm reboot will behave functionally the same as the native case but might
+/// not result in any physical power changes.
+///
+/// This function is defined in RISC-V SBI Specification chapter 10.1.
+#[inline]
+pub fn system_reset<T, R>(reset_type: T, reset_reason: R) -> SbiRet
+where
+    T: ResetType,
+    R: ResetReason,
+{
+    sbi_call_2(
+        EID_SRST,
+        SYSTEM_RESET,
+        reset_type.raw() as _,
+        reset_reason.raw() as _,
+    )
+}
+
+/// A valid type for system reset.
+pub trait ResetType {
+    /// Get a raw value to pass to SBI environment.
+    fn raw(&self) -> u32;
+}
+
+#[cfg(feature = "integer-impls")]
+impl ResetType for u32 {
+    #[inline]
+    fn raw(&self) -> u32 {
+        *self
+    }
+}
+
+#[cfg(feature = "integer-impls")]
+impl ResetType for i32 {
+    #[inline]
+    fn raw(&self) -> u32 {
+        u32::from_ne_bytes(i32::to_ne_bytes(*self))
+    }
+}
+
+/// A valid reason for system reset.
+pub trait ResetReason {
+    /// Get a raw value to pass to SBI environment.
+    fn raw(&self) -> u32;
+}
+
+#[cfg(feature = "integer-impls")]
+impl ResetReason for u32 {
+    #[inline]
+    fn raw(&self) -> u32 {
+        *self
+    }
+}
+
+#[cfg(feature = "integer-impls")]
+impl ResetReason for i32 {
+    #[inline]
+    fn raw(&self) -> u32 {
+        u32::from_ne_bytes(i32::to_ne_bytes(*self))
+    }
+}
+
+macro_rules! define_reset_param {
+    ($($struct:ident($value:expr): $trait:ident #[$doc:meta])*) => {
+        $(
+            #[derive(Clone, Copy, Debug)]
+            #[$doc]
+            pub struct $struct;
+            impl $trait for $struct {
+                #[inline]
+                fn raw(&self) -> u32 {
+                    $value
+                }
+            }
+        )*
+    };
+}
+
+define_reset_param! {
+    Shutdown(RESET_TYPE_SHUTDOWN): ResetType /// Shutdown as reset type.
+    ColdReboot(RESET_TYPE_COLD_REBOOT): ResetType /// Cold reboot as reset type.
+    WarmReboot(RESET_TYPE_WARM_REBOOT): ResetType /// Warm reboot as reset type.
+    NoReason(RESET_REASON_NO_REASON): ResetReason /// No reason as reset reason.
+    SystemFailure(RESET_REASON_SYSTEM_FAILURE): ResetReason /// System failure as reset reason.
+}

+ 67 - 0
sbi-rt/src/sta.rs

@@ -0,0 +1,67 @@
+//! Chapter 16. Steal-time Accounting Extension (EID #0x535441 "STA")
+
+use crate::binary::{sbi_call_3, SbiRet};
+
+use sbi_spec::{
+    binary::SharedPtr,
+    sta::{EID_STA, SET_SHMEM},
+};
+
+/// Prepare shared memory for steal-time accounting feature.
+///
+/// Set the shared memory physical base address for steal-time accounting of the calling virtual hart and
+/// enable the SBI implementation’s steal-time information reporting.
+///
+/// It is not expected for the shared memory to be written by the supervisor-mode software
+/// while it is in use for steal-time accounting. However, the SBI implementation MUST not misbehave
+/// if a write from supervisor-mode software occurs, however, in that case,
+/// it MAY leave the shared memory filled with inconsistent data.
+///
+/// *NOTE:* Not writing to the shared memory when the supervisor-mode software is not runnable
+/// avoids unnecessary work and supports repeatable capture of a system image
+/// while the supervisor-mode software is suspended.
+///
+/// # STA Shared Memory Structure
+///
+/// | Name      | Offset | Size | Description
+/// |:----------|:-------|:-----|:------------
+/// | sequence  | 0      | 4    | The SBI implementation MUST increment this field to an odd value before writing the `steal` field, and increment it again to an even value after writing `steal` (i.e. an odd sequence number indicates an in-progress update). The SBI implementation SHOULD ensure that the sequence field remains odd for only very short periods of time. <br><br> The supervisor-mode software MUST check this field before and after reading the `steal` field, and repeat the read if it is different or odd. <br><br> This sequence field enables the value of the steal field to be read by supervisor-mode software executing in a 32-bit environment.
+/// | flags     | 4      | 4    | Always zero. <br><br> Future extensions of the SBI call might allow the supervisor-mode software to write to some of the fields of the shared memory. Such extensions will not be enabled as long as a zero value is used for the flags argument to the SBI call.
+/// | steal     | 8      | 8    | The amount of time in which this virtual hart was not idle and scheduled out, in nanoseconds. The time during which the virtual hart is idle will not be reported as steal-time.
+/// | preempted | 16     | 1    | An advisory flag indicating whether the virtual hart which registered this structure is running or not. A non-zero value MAY be written by the SBI implementation if the virtual hart has been preempted (i.e. while the `steal` field is increasing), while a zero value MUST be written before the virtual hart starts to run again. <br><br> This preempted field can, for example, be used by the supervisor-mode software to check if a lock holder has been preempted, and, in that case, disable optimistic spinning.
+/// | pad       | 17     | 47   | Pad with zeros to a 64 byte boundary.
+///
+/// # Parameters
+///
+/// If `shmem` address is not all-ones bitwise, then `shmem` specifies the shared memory
+/// physical base address. `shmem` MUST be 64-byte aligned. The size of the shared memory
+/// must be 64 bytes. All bytes MUST be set to zero by the SBI implementation before returning
+/// from the SBI call.
+///
+/// If `shmem` address is all-ones bitwise, the SBI implementation will stop reporting
+/// steal-time information for the virtual hart.
+///
+/// The `flags` parameter is reserved for future use and MUST be zero.
+///
+/// # Return value
+///
+/// `SbiRet.value` is set to zero and the possible error codes returned in `SbiRet.error` are shown in the table below:
+///
+/// | Error code                  | Description
+/// |:----------------------------|:---------------------------------
+/// | `SbiRet::success()`         | The steal-time shared memory physical base address was set or cleared successfully.
+/// | `SbiRet::invalid_param()`   | The `flags` parameter is not zero or the shmem_phys_lo is not 64-byte aligned.
+/// | `SbiRet::invalid_address()` | The shared memory pointed to by the `shmem_phys_lo` and `shmem_phys_hi` parameters is not writable or does not satisfy other requirements of STA Shared Memory Structure.
+/// | `SbiRet::failed()`          | The request failed for unspecified or unknown other reasons.
+///
+/// This function is defined in RISC-V SBI Specification chapter 16.1.
+#[inline]
+pub fn sta_set_shmem(shmem: SharedPtr<[u8; 64]>, flags: usize) -> SbiRet {
+    sbi_call_3(
+        EID_STA,
+        SET_SHMEM,
+        shmem.phys_addr_lo(),
+        shmem.phys_addr_hi(),
+        flags,
+    )
+}

+ 85 - 0
sbi-rt/src/susp.rs

@@ -0,0 +1,85 @@
+//! Chapter 13. System Suspend Extension (EID #0x53555350 "SUSP")
+
+use crate::binary::{sbi_call_3, SbiRet};
+use sbi_spec::susp::{EID_SUSP, SUSPEND};
+
+/// Suspend the system based on provided `sleep_type`.
+///
+/// # Parameters
+///
+/// The `sleep_type` parameter specifies the sleep type.
+///
+/// | Type                    | Name           | Description
+/// |:------------------------|:---------------|:----------------------------------------------
+/// | 0                       | SUSPEND_TO_RAM | This is a "suspend to RAM" sleep type, similar to ACPI's S2 or S3. Entry requires all but the calling hart be in the HSM `STOPPED` state and all hart registers and CSRs saved to RAM.
+/// | 0x00000001 - 0x7fffffff |                | Reserved for future use
+/// | 0x80000000 - 0xffffffff |                | Platform-specific system sleep types
+///
+/// The `resume_addr` parameter points to a runtime-specified physical address,
+/// where the hart can resume execution in supervisor-mode after a system suspend.
+///
+/// The `opaque` parameter is an XLEN-bit value which will be set in the `a1`
+/// register when the hart resumes execution at `resume_addr` after a system
+/// suspend.
+///
+/// # Return value
+///
+/// The possible return error codes returned in `SbiRet.error` are shown in
+/// the table below:
+///
+/// | Return code               | Description
+/// |:--------------------------|:----------------------------------------------
+/// | `SbiRet::success()`       | The suspend request is accepted and the system is suspended. The system will resume execution at `resume_addr` after the sleep period.
+/// | `SbiRet::invalid_param()` | `sleep_type` is reserved or is platform-specific and unimplemented.
+/// | `SbiRet::not_supported()` | `sleep_type` is not reserved and is implemented, but the platform does not support it due to one or more missing dependencies.
+/// | `SbiRet::invalid_address()` | `resume_addr` is not valid, possibly due to the following reasons: + * It is not a valid physical address. + * Executable access to the address is prohibited by a physical memory protection mechanism or H-extension G-stage for supervisor mode.
+/// | `SbiRet::denied()`        | The suspend request failed due to unsatisfied entry criteria.
+/// | `SbiRet::failed()`        | The suspend request failed for unspecified or unknown other reasons.
+///
+/// This function is defined in RISC-V SBI Specification chapter 13.1.
+#[inline]
+pub fn system_suspend<T>(sleep_type: T, resume_addr: usize, opaque: usize) -> SbiRet
+where
+    T: SleepType,
+{
+    sbi_call_3(
+        EID_SUSP,
+        SUSPEND,
+        sleep_type.raw() as _,
+        resume_addr,
+        opaque,
+    )
+}
+
+/// A valid sleep type for system suspend.
+pub trait SleepType {
+    /// Get a raw value to pass to SBI environment.
+    fn raw(&self) -> u32;
+}
+
+#[cfg(feature = "integer-impls")]
+impl SleepType for u32 {
+    #[inline]
+    fn raw(&self) -> u32 {
+        *self
+    }
+}
+
+#[cfg(feature = "integer-impls")]
+impl SleepType for i32 {
+    #[inline]
+    fn raw(&self) -> u32 {
+        u32::from_ne_bytes(i32::to_ne_bytes(*self))
+    }
+}
+
+/// Suspend to RAM as sleep type.
+#[derive(Clone, Copy, Debug)]
+pub struct SuspendToRam;
+
+impl SleepType for SuspendToRam {
+    #[inline]
+    fn raw(&self) -> u32 {
+        0
+    }
+}

+ 29 - 0
sbi-rt/src/time.rs

@@ -0,0 +1,29 @@
+//! Chapter 6. Timer Extension (EID #0x54494D45 "TIME")
+
+use crate::SbiRet;
+
+use sbi_spec::time::{EID_TIME, SET_TIMER};
+
+/// Programs the clock for next event after an absolute time.
+///
+/// Parameter `stime_value` is in absolute time. This function must clear the pending timer interrupt bit as well.
+///
+/// If the supervisor wishes to clear the timer interrupt without scheduling the next timer event,
+/// it can either request a timer interrupt infinitely far into the future (i.e., `u64::MAX`),
+/// or it can instead mask the timer interrupt by clearing `sie.STIE` CSR bit.
+///
+/// This function is defined in RISC-V SBI Specification chapter 6.1.
+#[inline]
+pub fn set_timer(stime_value: u64) -> SbiRet {
+    match () {
+        #[cfg(target_pointer_width = "32")]
+        () => crate::binary::sbi_call_2(
+            EID_TIME,
+            SET_TIMER,
+            stime_value as _,
+            (stime_value >> 32) as _,
+        ),
+        #[cfg(target_pointer_width = "64")]
+        () => crate::binary::sbi_call_1(EID_TIME, SET_TIMER, stime_value as _),
+    }
+}

+ 7 - 0
sbi-spec/.gitignore

@@ -0,0 +1,7 @@
+**/.*/*
+!/.github/*
+!/.cargo/*
+!/.vscode/settings.json
+
+/target
+Cargo.lock

+ 116 - 0
sbi-spec/CHANGELOG.md

@@ -0,0 +1,116 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [Unreleased]
+
+### Added
+
+### Modified
+
+### Fixed
+
+## [0.0.7] - 2023-12-08
+
+`sbi-spec` crate now supports RISC-V SBI version 2.0-rc7.
+
+### Added
+
+- Support to PMU events in Chapter 11
+- Support `NACL` extension in Chapter 15
+- Support `STA` extension in Chapter 16
+- Add new SBI error `NoShmem`
+- binary: add `SharedPtr` struct to represent shared memory range feature.
+- nacl: add `shmem_size` module
+- Move `HartMask` structure to `sbi-spec` crate from `rustsbi` crate.
+
+### Modified
+
+- Rearrange `HSM` constants into modules.
+
+### Fixed
+
+- Remove redundant prefixes in `PMU`
+- Add new function id `SNAPSHOT_SET_SHMEM` in `PMU`
+
+## [0.0.6] - 2023-04-04
+
+### Added
+
+- `Physical` shared memory physical address range with type annotation in Chapter 3
+- Support to RISC-V SBI System Suspend extension
+- Support to CPPC extension
+
+## [0.0.5] - 2023-02-16
+
+### Added
+
+- Adapt to RISC-V SBI specification version 2.0-rc1
+- `PMU_COUNTER_FW_READ_HI` function in `pmu` module for RV32 systems
+- SBI DBCN extension support
+- `Result`-like documents to `SbiRet`
+
+### Modified
+
+- style: add period to docs
+
+## [0.0.4] - 2022-10-10
+
+### Added
+
+- Various convenient functions to `SbiRet` structure
+- Add documents on whole `sbi-rt` crate to coply with `deny(missing_docs)`
+- Feature `legacy` to gate legacy SBI extension
+
+### Modified
+
+- Rename `SbiRet::ok` to `SbiRet::success`
+- Rename `SbiSpecVersion` to struct `Version` 
+
+## [0.0.3] - 2022-10-06
+
+### Added
+
+- deps: static_assertions
+  check implementations during compilation, and provide an item list for developers
+- denied: warnings and unsafe code
+- a github workflow to check building
+- `SbiSpecVersion` type defination for sbi base
+
+### Modified
+
+- rename `GET_SPEC_VERSION` to `GET_SBI_SPEC_VERSION`
+- rename `impl_id::IMPL_XXX` to `impl_id::XXX`
+
+### Removed
+
+- default target to RISC-V
+
+## [0.0.2] - 2022-07-21
+
+### Added
+
+- A changelog to this project
+
+### Modified
+
+- Lift build target limit; now this crate would build on targets other than RISC-V
+
+## [0.0.1] - 2022-07-11
+
+This is the first release of sbi-spec crate. This crate includes definition of RISC-V Supervisor Binary Interface (SBI) including structures and constants.
+
+### Added
+
+- Adapt to SBI specification version 1.0.0 ratified
+
+[Unreleased]: https://github.com/rustsbi/sbi-spec/compare/v0.0.7...HEAD
+[0.0.7]: https://github.com/rustsbi/sbi-spec/compare/v0.0.6...v0.0.7
+[0.0.6]: https://github.com/rustsbi/sbi-spec/compare/v0.0.5...v0.0.6
+[0.0.5]: https://github.com/rustsbi/sbi-spec/compare/v0.0.4...v0.0.5
+[0.0.4]: https://github.com/rustsbi/sbi-spec/compare/v0.0.3...v0.0.4
+[0.0.3]: https://github.com/rustsbi/sbi-spec/compare/v0.0.2...v0.0.3
+[0.0.2]: https://github.com/rustsbi/sbi-spec/compare/v0.0.1...v0.0.2
+[0.0.1]: https://github.com/rustsbi/sbi-spec/releases/tag/v0.0.1

+ 20 - 0
sbi-spec/Cargo.toml

@@ -0,0 +1,20 @@
+[package]
+name = "sbi-spec"
+description = "Definitions and constants in RISC-V Supervisor Binary Interface (RISC-V SBI)"
+version = "0.0.7-alpha.3"
+authors = ["YdrMaster <[email protected]>", "Luo Jia <[email protected]>"]
+repository = "https://github.com/rustsbi/sbi-spec"
+documentation = "https://docs.rs/sbi-spec"
+license = "MulanPSL-2.0 OR MIT"
+readme = "README.md"
+keywords = ["riscv", "sbi", "rustsbi"]
+categories = ["os", "embedded", "hardware-support", "no-std"]
+edition = "2021"
+
+[dev-dependencies]
+static_assertions = "1.1.0"
+
+[features]
+default = []
+# Support legacy extension; this feature is not included by default.
+legacy = []

+ 8 - 0
sbi-spec/LICENSE-MIT

@@ -0,0 +1,8 @@
+Copyright 2020 Luo Jia
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+

+ 101 - 0
sbi-spec/LICENSE-MULAN

@@ -0,0 +1,101 @@
+木兰宽松许可证, 第2版
+
+2020年1月 http://license.coscl.org.cn/MulanPSL2
+
+您对“软件”的复制、使用、修改及分发受木兰宽松许可证,第2版(“本许可证”)的如下条款的约束:
+
+0. 定义
+
+“软件”是指由“贡献”构成的许可在“本许可证”下的程序和相关文档的集合。
+
+“贡献”是指由任一“贡献者”许可在“本许可证”下的受版权法保护的作品。
+
+“贡献者”是指将受版权法保护的作品许可在“本许可证”下的自然人或“法人实体”。
+
+“法人实体”是指提交贡献的机构及其“关联实体”。
+
+“关联实体”是指,对“本许可证”下的行为方而言,控制、受控制或与其共同受控制的机构,此处的控制是指有受控方或共同受控方至少50%直接或间接的投票权、资金或其他有价证券。
+
+1. 授予版权许可
+
+每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的版权许可,您可以复制、使用、修改、分发其“贡献”,不论修改与否。
+
+2. 授予专利许可
+
+每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的(根据本条规定撤销除外)专利许可,供您制造、委托制造、使用、许诺销售、销售、进口其“贡献”或以其他方式转移其“贡献”。前述专利许可仅限于“贡献者”现在或将来拥有或控制的其“贡献”本身或其“贡献”与许可“贡献”时的“软件”结合而将必然会侵犯的专利权利要求,不包括对“贡献”的修改或包含“贡献”的其他结合。如果您或您的“关联实体”直接或间接地,就“软件”或其中的“贡献”对任何人发起专利侵权诉讼(包括反诉或交叉诉讼)或其他专利维权行动,指控其侵犯专利权,则“本许可证”授予您对“软件”的专利许可自您提起诉讼或发起维权行动之日终止。
+
+3. 无商标许可
+
+“本许可证”不提供对“贡献者”的商品名称、商标、服务标志或产品名称的商标许可,但您为满足第4条规定的声明义务而必须使用除外。
+
+4. 分发限制
+
+您可以在任何媒介中将“软件”以源程序形式或可执行形式重新分发,不论修改与否,但您必须向接收者提供“本许可证”的副本,并保留“软件”中的版权、商标、专利及免责声明。
+
+5. 免责声明与责任限制
+
+“软件”及其中的“贡献”在提供时不带任何明示或默示的担保。在任何情况下,“贡献者”或版权所有者不对任何人因使用“软件”或其中的“贡献”而引发的任何直接或间接损失承担责任,不论因何种原因导致或者基于何种法律理论,即使其曾被建议有此种损失的可能性。
+
+6. 语言
+
+“本许可证”以中英文双语表述,中英文版本具有同等法律效力。如果中英文版本存在任何冲突不一致,以中文版为准。
+
+条款结束
+
+如何将木兰宽松许可证,第2版,应用到您的软件
+
+如果您希望将木兰宽松许可证,第2版,应用到您的新软件,为了方便接收者查阅,建议您完成如下三步:
+
+1,请您补充如下声明中的空白,包括软件名、软件的首次发表年份以及您作为版权人的名字;
+
+2,请您在软件包的一级目录下创建以“LICENSE”为名的文件,将整个许可证文本放入该文件中;
+
+3,请将如下声明文本放入每个源文件的头部注释中。
+
+Copyright (c) 2020 Luo Jia
+
+RustSBI is licensed under Mulan PSL v2. You can use this software according to the terms and conditions of the Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: http://license.coscl.org.cn/MulanPSL2 
+
+THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. See the Mulan PSL v2 for more details.
+
+January 2020 http://license.coscl.org.cn/MulanPSL2
+
+Your reproduction, use, modification and distribution of the Software shall be subject to Mulan PSL v2 (this License) with the following terms and conditions:
+
+0. Definition
+
+Software means the program and related documents which are licensed under this License and comprise all Contribution(s).
+
+Contribution means the copyrightable work licensed by a particular Contributor under this License.
+
+Contributor means the Individual or Legal Entity who licenses its copyrightable work under this License.
+
+Legal Entity means the entity making a Contribution and all its Affiliates.
+
+Affiliates means entities that control, are controlled by, or are under common control with the acting entity under this License, ‘control’ means direct or indirect ownership of at least fifty percent (50%) of the voting power, capital or other securities of controlled or commonly controlled entity.
+
+1. Grant of Copyright License
+
+Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable copyright license to reproduce, use, modify, or distribute its Contribution, with modification or not.
+
+2. Grant of Patent License
+
+Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable (except for revocation under this Section) patent license to make, have made, use, offer for sale, sell, import or otherwise transfer its Contribution, where such patent license is only limited to the patent claims owned or controlled by such Contributor now or in future which will be necessarily infringed by its Contribution alone, or by combination of the Contribution with the Software to which the Contribution was contributed. The patent license shall not apply to any modification of the Contribution, and any other combination which includes the Contribution. If you or your Affiliates directly or indirectly institute patent litigation (including a cross claim or counterclaim in a litigation) or other patent enforcement activities against any individual or entity by alleging that the Software or any Contribution in it infringes patents, then any patent license granted to you under this License for the Software shall terminate as of the date such litigation or activity is filed or taken.
+
+3. No Trademark License
+
+No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, except as required to fulfill notice requirements in Section 4.
+
+4. Distribution Restriction
+
+You may distribute the Software in any medium with or without modification, whether in source or executable forms, provided that you provide recipients with a copy of this License and retain copyright, patent, trademark and disclaimer statements in the Software.
+
+5. Disclaimer of Warranty and Limitation of Liability
+
+THE SOFTWARE AND CONTRIBUTION IN IT ARE PROVIDED WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED. IN NO EVENT SHALL ANY CONTRIBUTOR OR COPYRIGHT HOLDER BE LIABLE TO YOU FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO ANY DIRECT, OR INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING FROM YOUR USE OR INABILITY TO USE THE SOFTWARE OR THE CONTRIBUTION IN IT, NO MATTER HOW IT’S CAUSED OR BASED ON WHICH LEGAL THEORY, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+6. Language
+
+THIS LICENSE IS WRITTEN IN BOTH CHINESE AND ENGLISH, AND THE CHINESE VERSION AND ENGLISH VERSION SHALL HAVE THE SAME LEGAL EFFECT. IN THE CASE OF DIVERGENCE BETWEEN THE CHINESE AND ENGLISH VERSIONS, THE CHINESE VERSION SHALL PREVAIL.
+
+END OF THE TERMS AND CONDITIONS

+ 41 - 0
sbi-spec/README.md

@@ -0,0 +1,41 @@
+# SBI 标准的 Rust 实现
+
+[![CI](https://github.com/rustsbi/sbi-spec/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/rustsbi/sbi-spec/actions)
+[![Latest version](https://img.shields.io/crates/v/sbi-spec.svg)](https://crates.io/crates/sbi-spec)
+[![issue](https://img.shields.io/github/issues/rustsbi/sbi-spec)](https://github.com/rustsbi/sbi-spec/issues)
+[![Documentation](https://docs.rs/sbi-spec/badge.svg)](https://docs.rs/sbi-spec)
+![license](https://img.shields.io/github/license/rustsbi/sbi-spec)
+
+这个库用 Rust 实现了 [SBI 标准](https://github.com/riscv-non-isa/riscv-sbi-doc) 定义的常量和结构。
+
+2.0-rc7 标准各章节的实现情况:
+
+- §3
+  - [x] 常量
+  - [x] 结构
+- §4
+  - [x] 常量
+- §5
+  - [x] 常量
+- §6
+  - [x] 常量
+- §7
+  - [x] 常量
+- §8
+  - [x] 常量
+- §9
+  - [x] 常量
+- §10
+  - [x] 常量
+- §11
+  - [x] 常量
+- §12
+  - [x] 常量
+- §13
+  - [x] 常量
+- §14
+  - [x] 常量
+- §15
+  - [x] 常量
+- §16
+  - [x] 常量

+ 98 - 0
sbi-spec/src/base.rs

@@ -0,0 +1,98 @@
+//! Chapter 4. Base Extension (EID #0x10).
+
+/// Extension ID for RISC-V SBI Base extension.
+pub const EID_BASE: usize = 0x10;
+pub use fid::*;
+
+/// Default probe value for the target SBI extension is unavailable.
+pub const UNAVAILABLE_EXTENSION: usize = 0;
+
+/// SBI specification version.
+///
+/// Not to be confused with 'implementation version'.
+///
+/// Declared in §4.1.
+#[derive(Clone, Copy, Debug)]
+#[repr(transparent)]
+pub struct Version {
+    raw: usize,
+}
+
+impl Version {
+    /// Converts raw extension value into Version structure.
+    #[inline]
+    pub const fn from_raw(raw: usize) -> Self {
+        Self { raw }
+    }
+
+    /// Reads major version of specification.
+    #[inline]
+    pub const fn major(self) -> usize {
+        (self.raw >> 24) & ((1 << 7) - 1)
+    }
+
+    /// Reads minor version of specification.
+    #[inline]
+    pub const fn minor(self) -> usize {
+        self.raw & ((1 << 24) - 1)
+    }
+}
+
+impl core::fmt::Display for Version {
+    #[inline]
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        write!(f, "{}.{}", self.major(), self.minor())
+    }
+}
+
+/// Declared in §4.8
+mod fid {
+    /// Function ID to get the current SBI specification version.
+    ///
+    /// Declared in §4.1.
+    pub const GET_SBI_SPEC_VERSION: usize = 0x0;
+    /// Function ID to get the current SBI implementation ID.
+    ///
+    /// Declared in §4.2.
+    pub const GET_SBI_IMPL_ID: usize = 0x1;
+    /// Function ID to get the current SBI implementation version.
+    ///
+    /// Declared in §4.3.
+    pub const GET_SBI_IMPL_VERSION: usize = 0x2;
+    /// Function ID to probe information about one SBI extension from current environment.
+    ///
+    /// Declared in §4.4.
+    pub const PROBE_EXTENSION: usize = 0x3;
+    /// Function ID to get value of `mvendorid` register in current environment.
+    ///
+    /// Declared in §4.5.
+    pub const GET_MVENDORID: usize = 0x4;
+    /// Function ID to get value of `marchid` register in current environment.
+    ///
+    /// Declared in §4.6.
+    pub const GET_MARCHID: usize = 0x5;
+    /// Function ID to get value of `mimpid` register in current environment.
+    ///
+    /// Declared in §4.7.
+    pub const GET_MIMPID: usize = 0x6;
+}
+
+/// SBI Implementation IDs.
+///
+/// Declared in §4.9.
+pub mod impl_id {
+    /// Berkley Bootloader
+    pub const BBL: usize = 0;
+    /// OpenSBI
+    pub const OPEN_SBI: usize = 1;
+    /// Xvisor
+    pub const XVISOR: usize = 2;
+    /// KVM
+    pub const KVM: usize = 3;
+    /// RustSBI
+    pub const RUST_SBI: usize = 4;
+    /// Diosix
+    pub const DIOSIX: usize = 5;
+    /// Coffer
+    pub const COFFER: usize = 6;
+}

+ 907 - 0
sbi-spec/src/binary.rs

@@ -0,0 +1,907 @@
+//! Chapter 3. Binary Encoding.
+
+use core::marker::PhantomData;
+
+/// SBI functions return type.
+///
+/// > SBI functions must return a pair of values in a0 and a1,
+/// > with a0 returning an error code.
+/// > This is analogous to returning the C structure `SbiRet`.
+///
+/// Note: if this structure is used in function return on conventional
+/// Rust code, it would not require to pin memory representation as
+/// extern C. The `repr(C)` is set in case that some users want to use
+/// this structure in FFI code.
+#[derive(Clone, Copy, PartialEq, Eq)]
+#[repr(C)]
+pub struct SbiRet {
+    /// Error number.
+    pub error: usize,
+    /// Result value.
+    pub value: usize,
+}
+
+/// SBI success state return value.
+pub const RET_SUCCESS: usize = 0;
+/// Error for SBI call failed for unknown reasons.
+pub const RET_ERR_FAILED: usize = -1isize as _;
+/// Error for target operation not supported.
+pub const RET_ERR_NOT_SUPPORTED: usize = -2isize as _;
+/// Error for invalid parameter.
+pub const RET_ERR_INVALID_PARAM: usize = -3isize as _;
+/// Error for denied (unused in standard extensions).
+pub const RET_ERR_DENIED: usize = -4isize as _;
+/// Error for invalid address.
+pub const RET_ERR_INVALID_ADDRESS: usize = -5isize as _;
+/// Error for resource already available.
+pub const RET_ERR_ALREADY_AVAILABLE: usize = -6isize as _;
+/// Error for resource already started.
+pub const RET_ERR_ALREADY_STARTED: usize = -7isize as _;
+/// Error for resource already stopped.
+pub const RET_ERR_ALREADY_STOPPED: usize = -8isize as _;
+/// Error for shared memory not available.
+pub const RET_ERR_NO_SHMEM: usize = -9isize as _;
+
+impl core::fmt::Debug for SbiRet {
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        match self.error {
+            RET_SUCCESS => self.value.fmt(f),
+            RET_ERR_FAILED => write!(f, "<SBI call failed>"),
+            RET_ERR_NOT_SUPPORTED => write!(f, "<SBI feature not supported>"),
+            RET_ERR_INVALID_PARAM => write!(f, "<SBI invalid parameter>"),
+            RET_ERR_DENIED => write!(f, "<SBI denied>"),
+            RET_ERR_INVALID_ADDRESS => write!(f, "<SBI invalid address>"),
+            RET_ERR_ALREADY_AVAILABLE => write!(f, "<SBI already available>"),
+            RET_ERR_ALREADY_STARTED => write!(f, "<SBI already started>"),
+            RET_ERR_ALREADY_STOPPED => write!(f, "<SBI already stopped>"),
+            RET_ERR_NO_SHMEM => write!(f, "<SBI shared memory not available>"),
+            unknown => write!(f, "[SBI Unknown error: {unknown:#x}]"),
+        }
+    }
+}
+
+/// RISC-V SBI error in enumeration.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Error {
+    /// Error for SBI call failed for unknown reasons.
+    Failed,
+    /// Error for target operation not supported.
+    NotSupported,
+    /// Error for invalid parameter.
+    InvalidParam,
+    /// Error for denied (unused in standard extensions).
+    Denied,
+    /// Error for invalid address.
+    InvalidAddress,
+    /// Error for resource already available.
+    AlreadyAvailable,
+    /// Error for resource already started.
+    AlreadyStarted,
+    /// Error for resource already stopped.
+    AlreadyStopped,
+    /// Error for shared memory not available.
+    NoShmem,
+    /// Custom error code.
+    Custom(isize),
+}
+
+impl SbiRet {
+    /// Returns success SBI state with given `value`.
+    #[inline]
+    pub const fn success(value: usize) -> Self {
+        Self {
+            error: RET_SUCCESS,
+            value,
+        }
+    }
+
+    /// The SBI call request failed for unknown reasons.
+    #[inline]
+    pub const fn failed() -> Self {
+        Self {
+            error: RET_ERR_FAILED,
+            value: 0,
+        }
+    }
+
+    /// SBI call failed due to not supported by target ISA,
+    /// operation type not supported,
+    /// or target operation type not implemented on purpose.
+    #[inline]
+    pub const fn not_supported() -> Self {
+        Self {
+            error: RET_ERR_NOT_SUPPORTED,
+            value: 0,
+        }
+    }
+
+    /// SBI call failed due to invalid hart mask parameter,
+    /// invalid target hart id,
+    /// invalid operation type,
+    /// or invalid resource index.
+    #[inline]
+    pub const fn invalid_param() -> Self {
+        Self {
+            error: RET_ERR_INVALID_PARAM,
+            value: 0,
+        }
+    }
+    /// SBI call failed due to denied.
+    ///
+    /// As the time this document was written,
+    /// there is currently no function in SBI standard that returns this error.
+    /// However, custom extensions or future standard functions may return this
+    /// error if appropriate.
+    #[inline]
+    pub const fn denied() -> Self {
+        Self {
+            error: RET_ERR_DENIED,
+            value: 0,
+        }
+    }
+
+    /// SBI call failed for invalid mask start address,
+    /// not a valid physical address parameter,
+    /// or the target address is prohibited by PMP to run in supervisor mode.
+    #[inline]
+    pub const fn invalid_address() -> Self {
+        Self {
+            error: RET_ERR_INVALID_ADDRESS,
+            value: 0,
+        }
+    }
+
+    /// SBI call failed for the target resource is already available,
+    /// e.g. the target hart is already started when caller still request it to start.
+    #[inline]
+    pub const fn already_available() -> Self {
+        Self {
+            error: RET_ERR_ALREADY_AVAILABLE,
+            value: 0,
+        }
+    }
+
+    /// SBI call failed for the target resource is already started,
+    /// e.g. target performance counter is started.
+    #[inline]
+    pub const fn already_started() -> Self {
+        Self {
+            error: RET_ERR_ALREADY_STARTED,
+            value: 0,
+        }
+    }
+
+    /// SBI call failed for the target resource is already stopped,
+    /// e.g. target performance counter is stopped.
+    #[inline]
+    pub const fn already_stopped() -> Self {
+        Self {
+            error: RET_ERR_ALREADY_STOPPED,
+            value: 0,
+        }
+    }
+
+    /// SBI call failed for shared memory is not available,
+    /// e.g. nested acceleration shared memory is not available.
+    #[inline]
+    pub const fn no_shmem() -> Self {
+        Self {
+            error: RET_ERR_NO_SHMEM,
+            value: 0,
+        }
+    }
+}
+
+impl SbiRet {
+    /// Converts to a [`Result`] of value and error.
+    #[inline]
+    pub const fn into_result(self) -> Result<usize, Error> {
+        match self.error {
+            RET_SUCCESS => Ok(self.value),
+            RET_ERR_FAILED => Err(Error::Failed),
+            RET_ERR_NOT_SUPPORTED => Err(Error::NotSupported),
+            RET_ERR_INVALID_PARAM => Err(Error::InvalidParam),
+            RET_ERR_DENIED => Err(Error::Denied),
+            RET_ERR_INVALID_ADDRESS => Err(Error::InvalidAddress),
+            RET_ERR_ALREADY_AVAILABLE => Err(Error::AlreadyAvailable),
+            RET_ERR_ALREADY_STARTED => Err(Error::AlreadyStarted),
+            RET_ERR_ALREADY_STOPPED => Err(Error::AlreadyStopped),
+            RET_ERR_NO_SHMEM => Err(Error::NoShmem),
+            unknown => Err(Error::Custom(unknown as _)),
+        }
+    }
+
+    /// Returns `true` if current SBI return succeeded.
+    ///
+    /// # Examples
+    ///
+    /// Basic usage:
+    ///
+    /// ```
+    /// # use sbi_spec::binary::SbiRet;
+    /// let x = SbiRet::success(0);
+    /// assert_eq!(x.is_ok(), true);
+    ///
+    /// let x = SbiRet::failed();
+    /// assert_eq!(x.is_ok(), false);
+    /// ```
+    #[must_use = "if you intended to assert that this is ok, consider `.unwrap()` instead"]
+    #[inline]
+    pub const fn is_ok(&self) -> bool {
+        matches!(self.error, RET_SUCCESS)
+    }
+
+    /// Returns `true` if current SBI return is an error.
+    ///
+    /// # Examples
+    ///
+    /// Basic usage:
+    ///
+    /// ```
+    /// # use sbi_spec::binary::SbiRet;
+    /// let x = SbiRet::success(0);
+    /// assert_eq!(x.is_err(), false);
+    ///
+    /// let x = SbiRet::not_supported();
+    /// assert_eq!(x.is_err(), true);
+    /// ```
+    #[must_use = "if you intended to assert that this is err, consider `.unwrap_err()` instead"]
+    #[inline]
+    pub const fn is_err(&self) -> bool {
+        !self.is_ok()
+    }
+
+    /// Converts from `SbiRet` to [`Option<usize>`].
+    ///
+    /// Converts `self` into an [`Option<usize>`], consuming `self`,
+    /// and discarding the error, if any.
+    ///
+    /// # Examples
+    ///
+    /// Basic usage:
+    ///
+    /// ```
+    /// # use sbi_spec::binary::SbiRet;
+    /// let x = SbiRet::success(2);
+    /// assert_eq!(x.ok(), Some(2));
+    ///
+    /// let x = SbiRet::invalid_param();
+    /// assert_eq!(x.ok(), None);
+    /// ```
+    // fixme: should be pub const fn once this function in Result is stablized in constant
+    #[inline]
+    pub fn ok(self) -> Option<usize> {
+        self.into_result().ok()
+    }
+
+    /// Converts from `SbiRet` to [`Option<Error>`].
+    ///
+    /// Converts `self` into an [`Option<Error>`], consuming `self`,
+    /// and discarding the success value, if any.
+    ///
+    /// # Examples
+    ///
+    /// Basic usage:
+    ///
+    /// ```
+    /// # use sbi_spec::binary::{SbiRet, Error};
+    /// let x = SbiRet::success(2);
+    /// assert_eq!(x.err(), None);
+    ///
+    /// let x = SbiRet::denied();
+    /// assert_eq!(x.err(), Some(Error::Denied));
+    /// ```
+    // fixme: should be pub const fn once this function in Result is stablized in constant
+    #[inline]
+    pub fn err(self) -> Option<Error> {
+        self.into_result().err()
+    }
+
+    /// Maps a `SbiRet` to `Result<U, Error>` by applying a function to a
+    /// contained success value, leaving an error value untouched.
+    ///
+    /// This function can be used to compose the results of two functions.
+    ///
+    /// # Examples
+    ///
+    /// Gets detail of a PMU counter and judge if it is a firmware counter.
+    ///
+    /// ```
+    /// # use sbi_spec::binary::SbiRet;
+    /// # use core::mem::size_of;
+    /// # mod sbi_rt {
+    /// #     use sbi_spec::binary::SbiRet;
+    /// #     const TYPE_MASK: usize = 1 << (core::mem::size_of::<usize>() - 1);
+    /// #     pub fn pmu_counter_get_info(_: usize) -> SbiRet { SbiRet::success(TYPE_MASK) }
+    /// # }
+    /// // We assume that counter index 42 is a firmware counter.
+    /// let counter_idx = 42;
+    /// // Masks PMU counter type by setting highest bit in `usize`.
+    /// const TYPE_MASK: usize = 1 << (size_of::<usize>() - 1);
+    /// // Highest bit of returned `counter_info` represents whether it's
+    /// // a firmware counter or a hardware counter.
+    /// let is_firmware_counter = sbi_rt::pmu_counter_get_info(counter_idx)
+    ///     .map(|counter_info| counter_info & TYPE_MASK != 0);
+    /// // If that bit is set, it is a firmware counter.
+    /// assert_eq!(is_firmware_counter, Ok(true));
+    /// ```
+    #[inline]
+    pub fn map<U, F: FnOnce(usize) -> U>(self, op: F) -> Result<U, Error> {
+        self.into_result().map(op)
+    }
+
+    /// Returns the provided default (if error),
+    /// or applies a function to the contained value (if success).
+    ///
+    /// Arguments passed to `map_or` are eagerly evaluated;
+    /// if you are passing the result of a function call,
+    /// it is recommended to use [`map_or_else`],
+    /// which is lazily evaluated.
+    ///
+    /// [`map_or_else`]: SbiRet::map_or_else
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use sbi_spec::binary::SbiRet;
+    /// let x = SbiRet::success(3);
+    /// assert_eq!(x.map_or(42, |v| v & 0b1), 1);
+    ///
+    /// let x = SbiRet::invalid_address();
+    /// assert_eq!(x.map_or(42, |v| v & 0b1), 42);
+    /// ```
+    #[inline]
+    pub fn map_or<U, F: FnOnce(usize) -> U>(self, default: U, f: F) -> U {
+        self.into_result().map_or(default, f)
+    }
+
+    /// Maps a `SbiRet` to `usize` value by applying fallback function `default` to
+    /// a contained error, or function `f` to a contained success value.
+    ///
+    /// This function can be used to unpack a successful result
+    /// while handling an error.
+    ///
+    /// # Examples
+    ///
+    /// Basic usage:
+    ///
+    /// ```
+    /// # use sbi_spec::binary::SbiRet;
+    /// let k = 21;
+    ///
+    /// let x = SbiRet::success(3);
+    /// assert_eq!(x.map_or_else(|e| k * 2, |v| v & 0b1), 1);
+    ///
+    /// let x = SbiRet::already_available();
+    /// assert_eq!(x.map_or_else(|e| k * 2, |v| v & 0b1), 42);
+    /// ```
+    #[inline]
+    pub fn map_or_else<U, D: FnOnce(Error) -> U, F: FnOnce(usize) -> U>(
+        self,
+        default: D,
+        f: F,
+    ) -> U {
+        self.into_result().map_or_else(default, f)
+    }
+
+    /// Maps a `SbiRet` to `Result<T, F>` by applying a function to a
+    /// contained error as [`Error`] struct, leaving success value untouched.
+    ///
+    /// This function can be used to pass through a successful result while handling
+    /// an error.
+    ///
+    /// # Examples
+    ///
+    /// Basic usage:
+    ///
+    /// ```
+    /// # use sbi_spec::binary::{SbiRet, Error};
+    /// fn stringify(x: Error) -> String {
+    ///     if x == Error::AlreadyStarted {
+    ///         format!("error: already started!")
+    ///     } else {
+    ///         format!("error: other error!")
+    ///     }
+    /// }
+    ///
+    /// let x = SbiRet::success(2);
+    /// assert_eq!(x.map_err(stringify), Ok(2));
+    ///
+    /// let x = SbiRet::already_started();
+    /// assert_eq!(x.map_err(stringify), Err("error: already started!".to_string()));
+    /// ```
+    #[inline]
+    pub fn map_err<F, O: FnOnce(Error) -> F>(self, op: O) -> Result<usize, F> {
+        self.into_result().map_err(op)
+    }
+
+    /// Returns the contained success value, consuming the `self` value.
+    ///
+    /// # Panics
+    ///
+    /// Panics if self is an SBI error with a panic message including the
+    /// passed message, and the content of the SBI state.
+    ///
+    /// # Examples
+    ///
+    /// Basic usage:
+    ///
+    /// ```should_panic
+    /// # use sbi_spec::binary::SbiRet;
+    /// let x = SbiRet::already_stopped();
+    /// x.expect("Testing expect"); // panics with `Testing expect`
+    /// ```
+    #[inline]
+    pub fn expect(self, msg: &str) -> usize {
+        self.into_result().expect(msg)
+    }
+
+    /// Returns the contained success value, consuming the `self` value.
+    ///
+    /// # Panics
+    ///
+    /// Panics if self is an SBI error, with a panic message provided by the
+    /// SBI error converted into [`Error`] struct.
+    ///
+    /// # Examples
+    ///
+    /// Basic usage:
+    ///
+    /// ```
+    /// # use sbi_spec::binary::SbiRet;
+    /// let x = SbiRet::success(2);
+    /// assert_eq!(x.unwrap(), 2);
+    /// ```
+    ///
+    /// ```should_panic
+    /// # use sbi_spec::binary::SbiRet;
+    /// let x = SbiRet::failed();
+    /// x.unwrap(); // panics
+    /// ```
+    #[inline]
+    pub fn unwrap(self) -> usize {
+        self.into_result().unwrap()
+    }
+
+    /// Returns the contained error as [`Error`] struct, consuming the `self` value.
+    ///
+    /// # Panics
+    ///
+    /// Panics if the self is SBI success value, with a panic message
+    /// including the passed message, and the content of the success value.
+    ///
+    /// # Examples
+    ///
+    /// Basic usage:
+    ///
+    /// ```should_panic
+    /// # use sbi_spec::binary::SbiRet;
+    /// let x = SbiRet::success(10);
+    /// x.expect_err("Testing expect_err"); // panics with `Testing expect_err`
+    /// ```
+    #[inline]
+    pub fn expect_err(self, msg: &str) -> Error {
+        self.into_result().expect_err(msg)
+    }
+
+    /// Returns the contained error as [`Error`] struct, consuming the `self` value.
+    ///
+    /// # Panics
+    ///
+    /// Panics if the self is SBI success value, with a custom panic message provided
+    /// by the success value.
+    ///
+    /// # Examples
+    ///
+    /// ```should_panic
+    /// # use sbi_spec::binary::SbiRet;
+    /// let x = SbiRet::success(2);
+    /// x.unwrap_err(); // panics with `2`
+    /// ```
+    ///
+    /// ```
+    /// # use sbi_spec::binary::{SbiRet, Error};
+    /// let x = SbiRet::not_supported();
+    /// assert_eq!(x.unwrap_err(), Error::NotSupported);
+    /// ```
+    #[inline]
+    pub fn unwrap_err(self) -> Error {
+        self.into_result().unwrap_err()
+    }
+
+    /// Returns `res` if self is success value, otherwise otherwise returns the contained error
+    /// of `self` as [`Error`] struct.
+    ///
+    /// Arguments passed to `and` are eagerly evaluated; if you are passing the
+    /// result of a function call, it is recommended to use [`and_then`], which is
+    /// lazily evaluated.
+    ///
+    /// [`and_then`]: SbiRet::and_then
+    ///
+    /// # Examples
+    ///
+    /// Basic usage:
+    ///
+    /// ```
+    /// # use sbi_spec::binary::{SbiRet, Error};
+    /// let x = SbiRet::success(2);
+    /// let y = SbiRet::invalid_param().into_result();
+    /// assert_eq!(x.and(y), Err(Error::InvalidParam));
+    ///
+    /// let x = SbiRet::denied();
+    /// let y = SbiRet::success(3).into_result();
+    /// assert_eq!(x.and(y), Err(Error::Denied));
+    ///
+    /// let x = SbiRet::invalid_address();
+    /// let y = SbiRet::already_available().into_result();
+    /// assert_eq!(x.and(y), Err(Error::InvalidAddress));
+    ///
+    /// let x = SbiRet::success(4);
+    /// let y = SbiRet::success(5).into_result();
+    /// assert_eq!(x.and(y), Ok(5));
+    /// ```
+    // fixme: should be pub const fn once this function in Result is stablized in constant
+    // fixme: should parameter be `res: SbiRet`?
+    #[inline]
+    pub fn and(self, res: Result<usize, Error>) -> Result<usize, Error> {
+        self.into_result().and(res)
+    }
+
+    /// Calls `op` if self is success value, otherwise returns the contained error
+    /// as [`Error`] struct.
+    ///
+    /// This function can be used for control flow based on `SbiRet` values.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use sbi_spec::binary::{SbiRet, Error};
+    /// fn sq_then_to_string(x: usize) -> Result<String, Error> {
+    ///     x.checked_mul(x).map(|sq| sq.to_string()).ok_or(Error::Failed)
+    /// }
+    ///
+    /// assert_eq!(SbiRet::success(2).and_then(sq_then_to_string), Ok(4.to_string()));
+    /// assert_eq!(SbiRet::success(1_000_000_000_000).and_then(sq_then_to_string), Err(Error::Failed));
+    /// assert_eq!(SbiRet::invalid_param().and_then(sq_then_to_string), Err(Error::InvalidParam));
+    /// ```
+    #[inline]
+    pub fn and_then<U, F: FnOnce(usize) -> Result<U, Error>>(self, op: F) -> Result<U, Error> {
+        self.into_result().and_then(op)
+    }
+
+    /// Returns `res` if self is SBI error, otherwise returns the success value of `self`.
+    ///
+    /// Arguments passed to `or` are eagerly evaluated; if you are passing the
+    /// result of a function call, it is recommended to use [`or_else`], which is
+    /// lazily evaluated.
+    ///
+    /// [`or_else`]: Result::or_else
+    ///
+    /// # Examples
+    ///
+    /// Basic usage:
+    ///
+    /// ```
+    /// # use sbi_spec::binary::{SbiRet, Error};
+    /// let x = SbiRet::success(2);
+    /// let y = SbiRet::invalid_param().into_result();
+    /// assert_eq!(x.or(y), Ok(2));
+    ///
+    /// let x = SbiRet::denied();
+    /// let y = SbiRet::success(3).into_result();
+    /// assert_eq!(x.or(y), Ok(3));
+    ///
+    /// let x = SbiRet::invalid_address();
+    /// let y = SbiRet::already_available().into_result();
+    /// assert_eq!(x.or(y), Err(Error::AlreadyAvailable));
+    ///
+    /// let x = SbiRet::success(4);
+    /// let y = SbiRet::success(100).into_result();
+    /// assert_eq!(x.or(y), Ok(4));
+    /// ```
+    // fixme: should be pub const fn once this function in Result is stablized in constant
+    // fixme: should parameter be `res: SbiRet`?
+    #[inline]
+    pub fn or<F>(self, res: Result<usize, F>) -> Result<usize, F> {
+        self.into_result().or(res)
+    }
+
+    /// Calls `op` if self is SBI error, otherwise returns the success value of `self`.
+    ///
+    /// This function can be used for control flow based on result values.
+    ///
+    ///
+    /// # Examples
+    ///
+    /// Basic usage:
+    ///
+    /// ```
+    /// # use sbi_spec::binary::{SbiRet, Error};
+    /// fn is_failed(x: Error) -> Result<usize, bool> { Err(x == Error::Failed) }
+    ///
+    /// assert_eq!(SbiRet::success(2).or_else(is_failed), Ok(2));
+    /// assert_eq!(SbiRet::failed().or_else(is_failed), Err(true));
+    /// ```
+    #[inline]
+    pub fn or_else<F, O: FnOnce(Error) -> Result<usize, F>>(self, op: O) -> Result<usize, F> {
+        self.into_result().or_else(op)
+    }
+
+    /// Returns the contained success value or a provided default.
+    ///
+    /// Arguments passed to `unwrap_or` are eagerly evaluated; if you are passing
+    /// the result of a function call, it is recommended to use [`unwrap_or_else`],
+    /// which is lazily evaluated.
+    ///
+    /// [`unwrap_or_else`]: SbiRet::unwrap_or_else
+    ///
+    /// # Examples
+    ///
+    /// Basic usage:
+    ///
+    /// ```
+    /// # use sbi_spec::binary::SbiRet;
+    /// let default = 2;
+    /// let x = SbiRet::success(9);
+    /// assert_eq!(x.unwrap_or(default), 9);
+    ///
+    /// let x = SbiRet::invalid_param();
+    /// assert_eq!(x.unwrap_or(default), default);
+    /// ```
+    // fixme: should be pub const fn once this function in Result is stablized in constant
+    #[inline]
+    pub fn unwrap_or(self, default: usize) -> usize {
+        self.into_result().unwrap_or(default)
+    }
+
+    /// Returns the contained success value or computes it from a closure.
+    ///
+    /// # Examples
+    ///
+    /// Basic usage:
+    ///
+    /// ```
+    /// # use sbi_spec::binary::{SbiRet, Error};
+    /// fn invalid_use_zero(x: Error) -> usize { if x == Error::InvalidParam { 0 } else { 3 } }
+    ///
+    /// assert_eq!(SbiRet::success(2).unwrap_or_else(invalid_use_zero), 2);
+    /// assert_eq!(SbiRet::invalid_param().unwrap_or_else(invalid_use_zero), 0);
+    /// ```
+    #[inline]
+    pub fn unwrap_or_else<F: FnOnce(Error) -> usize>(self, op: F) -> usize {
+        self.into_result().unwrap_or_else(op)
+    }
+}
+
+/// Hart mask structure in SBI function calls.
+#[derive(Debug, Copy, Clone)]
+pub struct HartMask {
+    inner: BitVector,
+}
+
+impl HartMask {
+    /// Construct a hart mask from mask value and base hart id.
+    #[inline]
+    pub const fn from_mask_base(hart_mask: usize, hart_mask_base: usize) -> HartMask {
+        HartMask {
+            inner: BitVector {
+                hart_mask,
+                hart_mask_base,
+            },
+        }
+    }
+
+    /// Returns `hart_mask` and `hart_mask_base` parameters from the hart mask structure.
+    #[inline]
+    pub const fn into_inner(self) -> (usize, usize) {
+        (self.inner.hart_mask, self.inner.hart_mask_base)
+    }
+
+    /// Check if the `hart_id` is included in this hart mask structure.
+    #[inline]
+    pub const fn has_bit(&self, hart_id: usize) -> bool {
+        let BitVector {
+            hart_mask,
+            hart_mask_base,
+        } = self.inner;
+        if hart_mask_base == usize::MAX {
+            // If `hart_mask_base` equals `usize::MAX`, that means `hart_mask` is ignored
+            // and all available harts must be considered.
+            return true;
+        }
+        let Some(idx) = hart_id.checked_sub(hart_mask_base) else {
+            // hart_id < hart_mask_base, not in current mask range
+            return false;
+        };
+        if idx >= usize::BITS as usize {
+            // hart_idx >= hart_mask_base + XLEN, not in current mask range
+            return false;
+        }
+        hart_mask & (1 << idx) != 0
+    }
+}
+
+#[derive(Debug, Copy, Clone)]
+struct BitVector {
+    hart_mask: usize,
+    hart_mask_base: usize,
+}
+
+/// Physical slice wrapper with type annotation.
+///
+/// This struct wraps slices in RISC-V physical memory by low and high part of the
+/// physical base address as well as its length. It is usually used by SBI extensions
+/// as parameter types to pass base address and length parameters on physical memory
+/// other than a virtual one.
+///
+/// Generic parameter `P` represents a hint of how this physical slice would be used.
+/// For example, `Physical<&[u8]>` represents an immutable reference to physical byte slice,
+/// while `Physical<&mut [u8]>` represents a mutable one.
+///
+/// An SBI implementation should load or store memory using both `phys_addr_lo` and
+/// `phys_addr_hi` combined as base address. A supervisor program (kernels etc.)
+/// should provide continuous physical memory, wrapping its reference using this structure
+/// before passing into SBI runtime.
+#[derive(Clone, Copy)]
+pub struct Physical<P> {
+    num_bytes: usize,
+    phys_addr_lo: usize,
+    phys_addr_hi: usize,
+    _marker: PhantomData<P>,
+}
+
+impl<P> Physical<P> {
+    /// Create a physical memory slice by length and physical address.
+    #[inline]
+    pub const fn new(num_bytes: usize, phys_addr_lo: usize, phys_addr_hi: usize) -> Self {
+        Self {
+            num_bytes,
+            phys_addr_lo,
+            phys_addr_hi,
+            _marker: core::marker::PhantomData,
+        }
+    }
+
+    /// Returns length of the physical memory slice.
+    #[inline]
+    pub const fn num_bytes(&self) -> usize {
+        self.num_bytes
+    }
+
+    /// Returns low part base address of physical memory slice.
+    #[inline]
+    pub const fn phys_addr_lo(&self) -> usize {
+        self.phys_addr_lo
+    }
+
+    /// Returns high part base address of physical memory slice.
+    #[inline]
+    pub const fn phys_addr_hi(&self) -> usize {
+        self.phys_addr_hi
+    }
+}
+
+/// Shared memory physical address raw pointer with type annotation.
+///
+/// This is a structure wrapping a raw pointer to value of type `T` without
+/// pointer metadata. `SharedPtr`s are _thin_; they won't include metadata
+/// as RISC-V SBI does not provide an approach to pass them via SBI calls,
+/// thus the length of type `T` should be decided independently from raw
+/// pointer structure.
+///
+/// `SharedPtr` can be used as a parameter to pass shared memory physical pointer
+/// with given base address in RISC-V SBI calls. For example, a `SharedPtr<[u8; 64]>`
+/// would represent a fixed-size 64 byte array on a RISC-V SBI function argument
+/// type.
+///
+/// This structure cannot be dereferenced directly with physical addresses,
+/// because on RISC-V systems the physical address space could be larger than the
+/// virtual ones. Hence, this structure describes physical memory range by
+/// two `usize` values: the upper `phys_addr_hi` and lower `phys_addr_lo`.
+///
+/// RISC-V SBI extensions may declare special pointer values for shared memory
+/// raw pointers. For example, SBI STA declares that steal-time information
+/// should stop from reporting when the SBI call is invoked using all-ones
+/// bitwise shared pointer, i.e. `phys_addr_hi` and `phys_addr_lo` both equals
+/// `usize::MAX`. `SharedPtr` can be constructed using such special values
+/// by providing them to the `SharedPtr::new` function.
+///
+/// # Requirements
+///
+/// If an SBI function needs to pass a shared memory physical address range to
+/// the SBI implementation (or higher privilege mode), then this physical memory
+/// address range MUST satisfy the following requirements:
+///
+/// * The SBI implementation MUST check that the supervisor-mode software is
+///   allowed to access the specified physical memory range with the access
+///   type requested (read and/or write).
+/// * The SBI implementation MUST access the specified physical memory range
+///   using the PMA attributes.
+/// * The data in the shared memory MUST follow little-endian byte ordering.
+///
+/// *NOTE:* If the supervisor-mode software accesses the same physical memory
+/// range using a memory type different than the PMA, then a loss of coherence
+/// or unexpected memory ordering may occur. The invoking software should
+/// follow the rules and sequences defined in the RISC-V Svpbmt specification
+/// to prevent the loss of coherence and memory ordering.
+///
+/// It is recommended that a memory physical address passed to an SBI function
+/// should use at least two `usize` parameters to support platforms
+/// which have memory physical addresses wider than `XLEN` bits.
+// FIXME: should constrain with `T: Thin` once ptr_metadata feature is stabled;
+// RISC-V SBI does not provide an approach to pass pointer metadata by SBI calls.
+pub struct SharedPtr<T> {
+    phys_addr_lo: usize,
+    phys_addr_hi: usize,
+    _marker: PhantomData<*mut T>,
+}
+
+// FIXME: we should consider strict provenance rules for this pointer-like structure
+// once feature strict_provenance is stablized.
+impl<T> SharedPtr<T> {
+    /// Create a shared physical memory pointer by physical address.
+    #[inline]
+    pub const fn new(phys_addr_lo: usize, phys_addr_hi: usize) -> Self {
+        Self {
+            phys_addr_lo,
+            phys_addr_hi,
+            _marker: PhantomData,
+        }
+    }
+
+    /// Returns low part physical address of shared physical memory pointer.
+    #[inline]
+    pub const fn phys_addr_lo(self) -> usize {
+        self.phys_addr_lo
+    }
+
+    /// Returns high part physical address of shared physical memory pointer.
+    #[inline]
+    pub const fn phys_addr_hi(self) -> usize {
+        self.phys_addr_hi
+    }
+}
+
+impl<T> Clone for SharedPtr<T> {
+    #[inline(always)]
+    fn clone(&self) -> Self {
+        *self
+    }
+}
+
+impl<T> Copy for SharedPtr<T> {}
+
+#[cfg(test)]
+mod tests {
+    use super::HartMask;
+
+    #[test]
+    fn rustsbi_hart_mask() {
+        let mask = HartMask::from_mask_base(0b1, 400);
+        assert!(!mask.has_bit(0));
+        assert!(mask.has_bit(400));
+        assert!(!mask.has_bit(401));
+        let mask = HartMask::from_mask_base(0b110, 500);
+        assert!(!mask.has_bit(0));
+        assert!(!mask.has_bit(500));
+        assert!(mask.has_bit(501));
+        assert!(mask.has_bit(502));
+        assert!(!mask.has_bit(500 + (usize::BITS as usize)));
+        let max_bit = 1 << (usize::BITS - 1);
+        let mask = HartMask::from_mask_base(max_bit, 600);
+        assert!(mask.has_bit(600 + (usize::BITS as usize) - 1));
+        assert!(!mask.has_bit(600 + (usize::BITS as usize)));
+        let mask = HartMask::from_mask_base(0b11, usize::MAX - 1);
+        assert!(!mask.has_bit(usize::MAX - 2));
+        assert!(mask.has_bit(usize::MAX - 1));
+        assert!(mask.has_bit(usize::MAX));
+        assert!(!mask.has_bit(0));
+        // hart_mask_base == usize::MAX is special, it means hart_mask should be ignored
+        // and this hart mask contains all harts available
+        let mask = HartMask::from_mask_base(0, usize::MAX);
+        for i in 0..5 {
+            assert!(mask.has_bit(i));
+        }
+        assert!(mask.has_bit(usize::MAX));
+    }
+}

+ 25 - 0
sbi-spec/src/cppc.rs

@@ -0,0 +1,25 @@
+//! Chapter 14. CPPC Extension (EID #0x43505043 "CPPC").
+
+/// Extension ID for CPPC Extension.
+pub const EID_CPPC: usize = crate::eid_from_str("CPPC") as _;
+pub use fid::*;
+
+/// Declared in §14.
+mod fid {
+    /// Function ID to probe a CPPC register.
+    ///
+    /// Declared in §14.1.
+    pub const PROBE: usize = 0;
+    /// Function ID to read CPPC register bits.
+    ///
+    /// Declared in §14.2.
+    pub const READ: usize = 1;
+    /// Function ID to read high bits of a CPPC register.
+    ///
+    /// Declared in §14.3.
+    pub const READ_HI: usize = 2;
+    /// Function ID to write to a CPPC register.
+    ///
+    /// Declared in §14.4.
+    pub const WRITE: usize = 3;
+}

+ 21 - 0
sbi-spec/src/dbcn.rs

@@ -0,0 +1,21 @@
+//! Chapter 12. Debug Console Extension (EID #0x4442434E "DBCN").
+
+/// Extension ID for Debug Console Extension.
+pub const EID_DBCN: usize = crate::eid_from_str("DBCN") as _;
+pub use fid::*;
+
+/// Declared in §12.
+mod fid {
+    /// Function ID to write bytes to the debug console from input memory.
+    ///
+    /// Declared in §12.1.
+    pub const CONSOLE_WRITE: usize = 0;
+    /// Function ID to read bytes from the debug console into an output memory.
+    ///
+    /// Declared in §12.2.
+    pub const CONSOLE_READ: usize = 1;
+    /// Function ID to write a single byte to the debug console.
+    ///
+    /// Declared in §12.3.
+    pub const CONSOLE_WRITE_BYTE: usize = 2;
+}

+ 70 - 0
sbi-spec/src/hsm.rs

@@ -0,0 +1,70 @@
+//! Chapter 9. Hart State Management Extension (EID #0x48534D "HSM").
+
+/// Extension ID for Hart State Management extension.
+pub const EID_HSM: usize = crate::eid_from_str("HSM") as _;
+pub use fid::*;
+
+/// Hart states.
+///
+/// Declared in Table 1 at §9.
+pub mod hart_state {
+    /// The hart is physically powered-up and executing normally.
+    pub const STARTED: usize = 0;
+    /// The hart is not executing in supervisor-mode or any lower privilege mode.
+    ///
+    /// It is probably powered-down by the SBI implementation if the underlying platform
+    /// has a mechanism to physically power-down harts.
+    pub const STOPPED: usize = 1;
+    /// The hart is pending before being started
+    ///
+    /// Some other hart has requested to start (or power-up) the hart from the STOPPED state
+    /// and the SBI implementation is still working to get the hart in the STARTED state.
+    pub const START_PENDING: usize = 2;
+    /// The hart is pending before being stopped.
+    ///
+    /// The hart has requested to stop (or power-down) itself from the STARTED state
+    /// and the SBI implementation is still working to get the hart in the STOPPED state.
+    pub const STOP_PENDING: usize = 3;
+    /// The hart is in a platform specific suspend (or low power) state.
+    pub const SUSPENDED: usize = 4;
+    /// The hart is pending before being suspended.
+    ///
+    /// The hart has requested to put itself in a platform specific low power state
+    /// from the STARTED state and the SBI implementation is still working to get
+    /// the hart in the platform specific SUSPENDED state.
+    pub const SUSPEND_PENDING: usize = 5;
+    /// The hart is pending before being resumed.
+    ///
+    /// An interrupt or platform specific hardware event has caused the hart to resume
+    /// normal execution from the SUSPENDED state and the SBI implementation is still
+    /// working to get the hart in the STARTED state.
+    pub const RESUME_PENDING: usize = 6;
+}
+
+/// Hart suspend types.
+pub mod suspend_type {
+    /// Default retentive hart suspend type.
+    pub const RETENTIVE: u32 = 0;
+    /// Default non-retentive hart suspend type.
+    pub const NON_RETENTIVE: u32 = 0x8000_0000;
+}
+
+/// Declared in §9.5.
+mod fid {
+    /// Function ID to start executing the given hart at specified address in supervisor-mode.
+    ///
+    /// Declared in §9.1.
+    pub const HART_START: usize = 0;
+    /// Function ID to stop executing the calling hart in supervisor-mode.
+    ///
+    /// Declared in §9.2.
+    pub const HART_STOP: usize = 1;
+    /// Function ID to get the current status (or HSM state id) of the given hart.
+    ///
+    /// Declared in §9.3.
+    pub const HART_GET_STATUS: usize = 2;
+    /// Function ID to put the calling hart into suspend or platform specific lower power states.
+    ///
+    /// Declared in §9.4.
+    pub const HART_SUSPEND: usize = 3;
+}

+ 25 - 0
sbi-spec/src/legacy.rs

@@ -0,0 +1,25 @@
+//! Chapter 5. Legacy Extensions (EIDs #0x00 - #0x0F).
+
+pub use id::*;
+
+/// §5.10
+mod id {
+    /// §5.1
+    pub const LEGACY_SET_TIMER: usize = 0;
+    /// §5.2
+    pub const LEGACY_CONSOLE_PUTCHAR: usize = 1;
+    /// §5.3
+    pub const LEGACY_CONSOLE_GETCHAR: usize = 2;
+    /// §5.4
+    pub const LEGACY_CLEAR_IPI: usize = 3;
+    /// §5.5
+    pub const LEGACY_SEND_IPI: usize = 4;
+    /// §5.6
+    pub const LEGACY_REMOTE_FENCE_I: usize = 5;
+    /// §5.7
+    pub const LEGACY_REMOTE_SFENCE_VMA: usize = 6;
+    /// §5.8
+    pub const LEGACY_REMOTE_SFENCE_VMA_ASID: usize = 7;
+    /// §5.9
+    pub const LEGACY_SHUTDOWN: usize = 8;
+}

+ 318 - 0
sbi-spec/src/lib.rs

@@ -0,0 +1,318 @@
+//! RISC-V SBI Specification structure and constant definitions.
+//!
+//! This crate adapts to RISC-V SBI Specification verion 2.0-rc7.
+//! It provides structures in Rust semantics and best practices to simplify
+//! designs of RISC-V SBI ecosystem, both implementation and applications.
+//!
+//! You may find it convenient to use this library in a vast range of packages,
+//! from operating system kernels, hypervisors, to SBI bare metal implementations.
+//! This crate is `no_std` compatible and does not need dymanic memory allocation,
+//! which make it suitable for embedded development.
+//!
+//! Although this library is dedicated to RISC-V architecture, it does not limit
+//! which build target the dependents should compile into. For example, you are
+//! developing a RISC-V emulator on platforms other than RISC-V, the emulator
+//! designed on other platforms can still make use of `sbi-spec` structures to
+//! provide necessary features the emulated RISC-V environment would make use of.
+#![no_std]
+#![deny(missing_docs, unsafe_code, unstable_features)]
+
+// §3
+pub mod binary;
+// §4
+pub mod base;
+// §5
+#[cfg(feature = "legacy")]
+pub mod legacy;
+// §6
+pub mod time;
+// §7
+pub mod spi;
+// §8
+pub mod rfnc;
+// §9
+pub mod hsm;
+// §10
+pub mod srst;
+// §11
+pub mod pmu;
+// §12
+pub mod dbcn;
+// §13
+pub mod susp;
+// §14
+pub mod cppc;
+// §15
+pub mod nacl;
+// §16
+pub mod sta;
+
+/// Converts SBI EID from str.
+const fn eid_from_str(name: &str) -> i32 {
+    match *name.as_bytes() {
+        [a] => i32::from_be_bytes([0, 0, 0, a]),
+        [a, b] => i32::from_be_bytes([0, 0, a, b]),
+        [a, b, c] => i32::from_be_bytes([0, a, b, c]),
+        [a, b, c, d] => i32::from_be_bytes([a, b, c, d]),
+        _ => unreachable!(),
+    }
+}
+
+/// Checks during compilation, and provides an item list for developers.
+#[cfg(test)]
+mod tests {
+    use static_assertions::{
+        assert_eq_align, assert_eq_size, assert_fields, assert_impl_all, const_assert_eq,
+    };
+    // §3
+    #[test]
+    fn test_binary() {
+        use crate::binary::*;
+        assert_eq_align!(SbiRet, usize);
+        assert_eq_size!(SbiRet, [usize; 2]);
+        assert_fields!(SbiRet: error);
+        assert_fields!(SbiRet: value);
+        assert_impl_all!(SbiRet: Copy, Clone, PartialEq, Eq, core::fmt::Debug);
+
+        const_assert_eq!(0, RET_SUCCESS as isize);
+        const_assert_eq!(-1, RET_ERR_FAILED as isize);
+        const_assert_eq!(-2, RET_ERR_NOT_SUPPORTED as isize);
+        const_assert_eq!(-3, RET_ERR_INVALID_PARAM as isize);
+        const_assert_eq!(-4, RET_ERR_DENIED as isize);
+        const_assert_eq!(-5, RET_ERR_INVALID_ADDRESS as isize);
+        const_assert_eq!(-6, RET_ERR_ALREADY_AVAILABLE as isize);
+        const_assert_eq!(-7, RET_ERR_ALREADY_STARTED as isize);
+        const_assert_eq!(-8, RET_ERR_ALREADY_STOPPED as isize);
+        const_assert_eq!(-9, RET_ERR_NO_SHMEM as isize);
+    }
+    // §4
+    #[test]
+    fn test_base() {
+        use crate::base::*;
+        const_assert_eq!(0x10, EID_BASE);
+        const_assert_eq!(0, GET_SBI_SPEC_VERSION);
+        const_assert_eq!(1, GET_SBI_IMPL_ID);
+        const_assert_eq!(2, GET_SBI_IMPL_VERSION);
+        const_assert_eq!(3, PROBE_EXTENSION);
+        const_assert_eq!(4, GET_MVENDORID);
+        const_assert_eq!(5, GET_MARCHID);
+        const_assert_eq!(6, GET_MIMPID);
+        const_assert_eq!(0, impl_id::BBL);
+        const_assert_eq!(1, impl_id::OPEN_SBI);
+        const_assert_eq!(2, impl_id::XVISOR);
+        const_assert_eq!(3, impl_id::KVM);
+        const_assert_eq!(4, impl_id::RUST_SBI);
+        const_assert_eq!(5, impl_id::DIOSIX);
+        const_assert_eq!(6, impl_id::COFFER);
+    }
+    // §5
+    #[cfg(feature = "legacy")]
+    #[test]
+    fn test_legacy() {
+        use crate::legacy::*;
+        const_assert_eq!(0, LEGACY_SET_TIMER);
+        const_assert_eq!(1, LEGACY_CONSOLE_PUTCHAR);
+        const_assert_eq!(2, LEGACY_CONSOLE_GETCHAR);
+        const_assert_eq!(3, LEGACY_CLEAR_IPI);
+        const_assert_eq!(4, LEGACY_SEND_IPI);
+        const_assert_eq!(5, LEGACY_REMOTE_FENCE_I);
+        const_assert_eq!(6, LEGACY_REMOTE_SFENCE_VMA);
+        const_assert_eq!(7, LEGACY_REMOTE_SFENCE_VMA_ASID);
+        const_assert_eq!(8, LEGACY_SHUTDOWN);
+    }
+    // §6
+    #[test]
+    fn test_time() {
+        use crate::time::*;
+        const_assert_eq!(0x54494D45, EID_TIME);
+        const_assert_eq!(0, SET_TIMER);
+    }
+    // §7
+    #[test]
+    fn test_spi() {
+        use crate::spi::*;
+        const_assert_eq!(0x735049, EID_SPI);
+        const_assert_eq!(0, SEND_IPI);
+    }
+    // §8
+    #[test]
+    fn test_rfnc() {
+        use crate::rfnc::*;
+        const_assert_eq!(0x52464E43, EID_RFNC);
+        const_assert_eq!(0, REMOTE_FENCE_I);
+        const_assert_eq!(1, REMOTE_SFENCE_VMA);
+        const_assert_eq!(2, REMOTE_SFENCE_VMA_ASID);
+        const_assert_eq!(3, REMOTE_HFENCE_GVMA_VMID);
+        const_assert_eq!(4, REMOTE_HFENCE_GVMA);
+        const_assert_eq!(5, REMOTE_HFENCE_VVMA_ASID);
+        const_assert_eq!(6, REMOTE_HFENCE_VVMA);
+    }
+    // §9
+    #[test]
+    fn test_hsm() {
+        use crate::hsm::*;
+        const_assert_eq!(0x48534D, EID_HSM);
+        const_assert_eq!(0, hart_state::STARTED);
+        const_assert_eq!(1, hart_state::STOPPED);
+        const_assert_eq!(2, hart_state::START_PENDING);
+        const_assert_eq!(3, hart_state::STOP_PENDING);
+        const_assert_eq!(4, hart_state::SUSPENDED);
+        const_assert_eq!(5, hart_state::SUSPEND_PENDING);
+        const_assert_eq!(6, hart_state::RESUME_PENDING);
+        const_assert_eq!(0x0000_0000, suspend_type::RETENTIVE);
+        const_assert_eq!(0x8000_0000, suspend_type::NON_RETENTIVE);
+        const_assert_eq!(0, HART_START);
+        const_assert_eq!(1, HART_STOP);
+        const_assert_eq!(2, HART_GET_STATUS);
+        const_assert_eq!(3, HART_SUSPEND);
+    }
+    // §10
+    #[test]
+    fn test_srst() {
+        use crate::srst::*;
+        const_assert_eq!(0x53525354, EID_SRST);
+        const_assert_eq!(0, RESET_TYPE_SHUTDOWN);
+        const_assert_eq!(1, RESET_TYPE_COLD_REBOOT);
+        const_assert_eq!(2, RESET_TYPE_WARM_REBOOT);
+        const_assert_eq!(0, RESET_REASON_NO_REASON);
+        const_assert_eq!(1, RESET_REASON_SYSTEM_FAILURE);
+        const_assert_eq!(0, SYSTEM_RESET);
+    }
+    // §11
+    #[test]
+    fn test_pmu() {
+        use crate::pmu::*;
+        const_assert_eq!(0x504D55, EID_PMU);
+        const_assert_eq!(0, NUM_COUNTERS);
+        const_assert_eq!(1, COUNTER_GET_INFO);
+        const_assert_eq!(2, COUNTER_CONFIG_MATCHING);
+        const_assert_eq!(3, COUNTER_START);
+        const_assert_eq!(4, COUNTER_STOP);
+        const_assert_eq!(5, COUNTER_FW_READ);
+        const_assert_eq!(6, COUNTER_FW_READ_HI);
+        const_assert_eq!(7, SNAPSHOT_SET_SHMEM);
+
+        const_assert_eq!(0, event_type::HARDWARE_GENERAL);
+        const_assert_eq!(1, event_type::HARDWARE_CACHE);
+        const_assert_eq!(2, event_type::HARDWARE_RAW);
+        const_assert_eq!(15, event_type::FIRMWARE);
+
+        const_assert_eq!(0, hardware_event::NO_EVENT);
+        const_assert_eq!(1, hardware_event::CPU_CYCLES);
+        const_assert_eq!(2, hardware_event::INSTRUCTIONS);
+        const_assert_eq!(3, hardware_event::CACHE_REFERENCES);
+        const_assert_eq!(4, hardware_event::CACHE_MISSES);
+        const_assert_eq!(5, hardware_event::BRANCH_INSTRUCTIONS);
+        const_assert_eq!(6, hardware_event::BRANCH_MISSES);
+        const_assert_eq!(7, hardware_event::BUS_CYCLES);
+        const_assert_eq!(8, hardware_event::STALLED_CYCLES_FRONTEND);
+        const_assert_eq!(9, hardware_event::STALLED_CYCLES_BACKEND);
+        const_assert_eq!(10, hardware_event::REF_CPU_CYCLES);
+
+        const_assert_eq!(0, cache_event::L1D);
+        const_assert_eq!(1, cache_event::L1I);
+        const_assert_eq!(2, cache_event::LL);
+        const_assert_eq!(3, cache_event::DTLB);
+        const_assert_eq!(4, cache_event::ITLB);
+        const_assert_eq!(5, cache_event::BPU);
+        const_assert_eq!(6, cache_event::NODE);
+
+        const_assert_eq!(0, cache_operation::READ);
+        const_assert_eq!(1, cache_operation::WRITE);
+        const_assert_eq!(2, cache_operation::PREFETCH);
+
+        const_assert_eq!(0, cache_result::ACCESS);
+        const_assert_eq!(1, cache_result::MISS);
+
+        const_assert_eq!(0, firmware_event::MISALIGNED_LOAD);
+        const_assert_eq!(1, firmware_event::MISALIGNED_STORE);
+        const_assert_eq!(2, firmware_event::ACCESS_LOAD);
+        const_assert_eq!(3, firmware_event::ACCESS_STORE);
+        const_assert_eq!(4, firmware_event::ILLEGAL_INSN);
+        const_assert_eq!(5, firmware_event::SET_TIMER);
+        const_assert_eq!(6, firmware_event::IPI_SENT);
+        const_assert_eq!(7, firmware_event::IPI_RECEIVED);
+        const_assert_eq!(8, firmware_event::FENCE_I_SENT);
+        const_assert_eq!(9, firmware_event::FENCE_I_RECEIVED);
+        const_assert_eq!(10, firmware_event::SFENCE_VMA_SENT);
+        const_assert_eq!(11, firmware_event::SFENCE_VMA_RECEIVED);
+        const_assert_eq!(12, firmware_event::SFENCE_VMA_ASID_SENT);
+        const_assert_eq!(13, firmware_event::SFENCE_VMA_ASID_RECEIVED);
+        const_assert_eq!(14, firmware_event::HFENCE_GVMA_SENT);
+        const_assert_eq!(15, firmware_event::HFENCE_GVMA_RECEIVED);
+        const_assert_eq!(16, firmware_event::HFENCE_GVMA_VMID_SENT);
+        const_assert_eq!(17, firmware_event::HFENCE_GVMA_VMID_RECEIVED);
+        const_assert_eq!(18, firmware_event::HFENCE_VVMA_SENT);
+        const_assert_eq!(19, firmware_event::HFENCE_VVMA_RECEIVED);
+        const_assert_eq!(20, firmware_event::HFENCE_VVMA_ASID_SENT);
+        const_assert_eq!(21, firmware_event::HFENCE_VVMA_ASID_RECEIVED);
+        const_assert_eq!(65535, firmware_event::PLATFORM);
+    }
+    // §12
+    #[test]
+    fn test_dbcn() {
+        use crate::dbcn::*;
+        const_assert_eq!(0x4442434E, EID_DBCN);
+        const_assert_eq!(0, CONSOLE_WRITE);
+        const_assert_eq!(1, CONSOLE_READ);
+        const_assert_eq!(2, CONSOLE_WRITE_BYTE);
+    }
+    // §13
+    #[test]
+    fn test_susp() {
+        use crate::susp::*;
+        const_assert_eq!(0x53555350, EID_SUSP);
+        const_assert_eq!(0, SUSPEND);
+    }
+    // §14
+    #[test]
+    fn test_cppc() {
+        use crate::cppc::*;
+        const_assert_eq!(0x43505043, EID_CPPC);
+        const_assert_eq!(0, PROBE);
+        const_assert_eq!(1, READ);
+        const_assert_eq!(2, READ_HI);
+        const_assert_eq!(3, WRITE);
+    }
+    // §15
+    #[test]
+    fn test_nacl() {
+        use crate::nacl::*;
+        const_assert_eq!(0x4E41434C, EID_NACL);
+        const_assert_eq!(0, PROBE_FEATURE);
+        const_assert_eq!(1, SET_SHMEM);
+        const_assert_eq!(2, SYNC_CSR);
+        const_assert_eq!(3, SYNC_HFENCE);
+        const_assert_eq!(4, SYNC_SRET);
+
+        const_assert_eq!(0, feature_id::SYNC_CSR);
+        const_assert_eq!(1, feature_id::SYNC_HFENCE);
+        const_assert_eq!(2, feature_id::SYNC_SRET);
+        const_assert_eq!(3, feature_id::AUTOSWAP_CSR);
+
+        const_assert_eq!(8192, shmem_size::RV32);
+        const_assert_eq!(12288, shmem_size::RV64);
+        const_assert_eq!(20480, shmem_size::RV128);
+        match () {
+            #[cfg(target_pointer_width = "32")]
+            () => {
+                const_assert_eq!(shmem_size::NATIVE, shmem_size::RV32);
+            }
+            #[cfg(target_pointer_width = "64")]
+            () => {
+                const_assert_eq!(shmem_size::NATIVE, shmem_size::RV64);
+            }
+            #[cfg(target_pointer_width = "128")]
+            () => {
+                const_assert_eq!(shmem_size::NATIVE, shmem_size::RV128);
+            }
+        }
+    }
+    // §16
+    #[test]
+    fn test_sta() {
+        use crate::sta::*;
+        const_assert_eq!(0x535441, EID_STA);
+        const_assert_eq!(0, SET_SHMEM);
+    }
+}

+ 75 - 0
sbi-spec/src/nacl.rs

@@ -0,0 +1,75 @@
+//! Chapter 15. Nested Acceleration Extension (EID #0x4E41434C "NACL").
+
+/// Extension ID for Nested Acceleration Extension.
+pub const EID_NACL: usize = crate::eid_from_str("NACL") as _;
+
+pub use fid::*;
+
+/// Declared in § 15.15.
+mod fid {
+    /// Function ID to probe a nested acceleration feature.
+    ///
+    /// Declared in §15.5.
+    pub const PROBE_FEATURE: usize = 0;
+    /// Function ID to set and enable the shared memory for nested acceleration on the calling hart.
+    ///
+    /// Declared in §15.6.
+    pub const SET_SHMEM: usize = 1;
+    /// Function ID to synchronize CSRs in the nested acceleration shared memory.
+    ///
+    /// Declared in §15.7.
+    pub const SYNC_CSR: usize = 2;
+    /// Function ID to synchronize HFENCEs in the nested acceleration shared memory.
+    ///
+    /// Declared in §15.8.
+    pub const SYNC_HFENCE: usize = 3;
+    /// Function ID to synchronize CSRs and HFENCEs in the nested acceleration shared memory and emulate the SRET instruction.
+    ///
+    /// Declared in §15.9.
+    pub const SYNC_SRET: usize = 4;
+}
+
+/// Nested Acceleration Feature ID.
+///
+/// Declared in §15.
+pub mod feature_id {
+    /// Feature ID for the synchronize CSR feature.
+    ///
+    /// Declared in §15.1.
+    pub const SYNC_CSR: usize = 0;
+    /// Feature ID for the synchronize HFENCE feature.
+    ///
+    /// Declared in §15.2.
+    pub const SYNC_HFENCE: usize = 1;
+    /// Feature ID for the synchronize SRET feature.
+    ///
+    /// Declared in §15.3.
+    pub const SYNC_SRET: usize = 2;
+    /// Feature ID for the autoswap CSR feature.
+    ///
+    /// Declared in §15.4.
+    pub const AUTOSWAP_CSR: usize = 3;
+}
+
+/// Size of shared memory set by supervisor software for current hart.
+///
+/// NACL shared memory includes scratch space and CSR space. Due to the difference
+/// of CSR width, this size varies between different `XLEN` values. `NATIVE`
+/// constant here only matches the integer width for the target this crate is compiled.
+/// If you are writing an SEE with different `XLEN` from host platform, you should
+/// choose other correct constant value from `RV32`, `RV64` or `RV128` in module `shmem_size`
+/// instead.
+pub mod shmem_size {
+    use core::mem::size_of;
+    /// Size of NACL shared memory on platforms with `XLEN` of same width as the current platform.
+    pub const NATIVE: usize = 4096 + 1024 * size_of::<usize>();
+
+    /// Size of NACL shared memory on RV32 platforms.
+    pub const RV32: usize = 4096 + 1024 * size_of::<u32>();
+
+    /// Size of NACL shared memory on RV64 platforms.
+    pub const RV64: usize = 4096 + 1024 * size_of::<u64>();
+
+    /// Size of NACL shared memory on RV128 platforms.
+    pub const RV128: usize = 4096 + 1024 * size_of::<u128>();
+}

+ 187 - 0
sbi-spec/src/pmu.rs

@@ -0,0 +1,187 @@
+//! Chapter 11. Performance Monitoring Unit Extension (EID #0x504D55 "PMU").
+
+/// Extension ID for Performance Monitoring Unit extension.
+pub const EID_PMU: usize = crate::eid_from_str("PMU") as _;
+pub use fid::*;
+
+/// Declared in §11.11.
+mod fid {
+    /// Function ID to get the number of counters, both hardware and firmware.
+    ///
+    /// Declared in §11.5.
+    pub const NUM_COUNTERS: usize = 0;
+    /// Function ID to get details about the specified counter.
+    ///
+    /// Declared in §11.6.
+    pub const COUNTER_GET_INFO: usize = 1;
+    /// Function ID to find and configure a counter from a set of counters.
+    ///
+    /// Declared in §11.7.
+    pub const COUNTER_CONFIG_MATCHING: usize = 2;
+    /// Function ID to start or enable a set of counters on the calling hart with the specified initial value.
+    ///
+    /// Declared in §11.8.
+    pub const COUNTER_START: usize = 3;
+    /// Function ID to stop or disable a set of counters on the calling hart.
+    ///
+    /// Declared in §11.9.
+    pub const COUNTER_STOP: usize = 4;
+    /// Function ID to provide the current value of a firmware counter.
+    ///
+    /// Declared in §11.10.
+    pub const COUNTER_FW_READ: usize = 5;
+    /// Function ID to provide the upper 32 bits of the current firmware counter value.
+    ///
+    /// Declared in §11.11.
+    pub const COUNTER_FW_READ_HI: usize = 6;
+    /// Function ID to set and enable the PMU snapshot shared memory.
+    ///
+    /// Declared in §11.12.
+    pub const SNAPSHOT_SET_SHMEM: usize = 7;
+}
+
+/// PMU Event Types.
+///
+/// Declared in §11.
+pub mod event_type {
+    /// Type for all hardware general events.
+    ///
+    /// Declared in §11.1.
+    pub const HARDWARE_GENERAL: usize = 0;
+    /// Type for all hardware cache events.
+    ///
+    /// Declared in §11.2.
+    pub const HARDWARE_CACHE: usize = 1;
+    /// Type for all hardware raw events.
+    ///
+    /// Declared in §11.3.
+    pub const HARDWARE_RAW: usize = 2;
+    /// Type for for all firmware events.
+    ///
+    /// Declared in §11.4.
+    pub const FIRMWARE: usize = 15;
+}
+
+/// Hardware General Event Codes.
+///
+/// Declared in §11.1.
+pub mod hardware_event {
+    /// Unused event because event_idx cannot be zero
+    pub const NO_EVENT: usize = 0;
+    /// Event for each CPU cycle
+    pub const CPU_CYCLES: usize = 1;
+    /// Event for each completed instruction
+    pub const INSTRUCTIONS: usize = 2;
+    /// Event for cache hit
+    pub const CACHE_REFERENCES: usize = 3;
+    /// Event for cache miss
+    pub const CACHE_MISSES: usize = 4;
+    /// Event for a branch instruction
+    pub const BRANCH_INSTRUCTIONS: usize = 5;
+    /// Event for a branch misprediction
+    pub const BRANCH_MISSES: usize = 6;
+    /// Event for each BUS cycle
+    pub const BUS_CYCLES: usize = 7;
+    /// Event for a stalled cycle in microarchitecture frontend
+    pub const STALLED_CYCLES_FRONTEND: usize = 8;
+    /// Event for a stalled cycle in microarchitecture backend
+    pub const STALLED_CYCLES_BACKEND: usize = 9;
+    /// Event for each reference CPU cycle
+    pub const REF_CPU_CYCLES: usize = 10;
+}
+
+/// Hardware Cache Event ID.
+///
+/// Declared in §11.2.
+pub mod cache_event {
+    /// Level 1 data cache event.
+    pub const L1D: usize = 0;
+    /// Level 1 instruction cache event.
+    pub const L1I: usize = 1;
+    /// Last level cache event.
+    pub const LL: usize = 2;
+    /// Data TLB event.
+    pub const DTLB: usize = 3;
+    /// Instruction TLB event.
+    pub const ITLB: usize = 4;
+    /// Branch predictor unit event.
+    pub const BPU: usize = 5;
+    /// NUMA node cache event.
+    pub const NODE: usize = 6;
+}
+
+/// Hardware Cache Operation ID.
+///
+/// Declared in §11.2.
+pub mod cache_operation {
+    /// Read cache line.
+    pub const READ: usize = 0;
+    /// Write cache line.
+    pub const WRITE: usize = 1;
+    /// Prefetch cache line.
+    pub const PREFETCH: usize = 2;
+}
+
+/// Hardware Cache Operation Result ID.
+///
+/// Declared in §11.2.
+pub mod cache_result {
+    /// Cache access.
+    pub const ACCESS: usize = 0;
+    /// Cache miss.
+    pub const MISS: usize = 1;
+}
+
+/// Firmware Event Codes.
+///
+/// Declared in §11.4.
+pub mod firmware_event {
+    /// Misaligned load trap event.
+    pub const MISALIGNED_LOAD: usize = 0;
+    /// Misaligned store trap event.
+    pub const MISALIGNED_STORE: usize = 1;
+    /// Load access trap event.
+    pub const ACCESS_LOAD: usize = 2;
+    /// Store access trap event.
+    pub const ACCESS_STORE: usize = 3;
+    /// Illegal instruction trap event.
+    pub const ILLEGAL_INSN: usize = 4;
+    /// Set timer event.
+    pub const SET_TIMER: usize = 5;
+    /// Sent IPI to other HART event.
+    pub const IPI_SENT: usize = 6;
+    /// Received IPI from other HART event.
+    pub const IPI_RECEIVED: usize = 7;
+    /// Sent FENCE.I request to other HART event.
+    pub const FENCE_I_SENT: usize = 8;
+    /// Received FENCE.I request from other HART event.
+    pub const FENCE_I_RECEIVED: usize = 9;
+    /// Sent SFENCE.VMA request to other HART event.
+    pub const SFENCE_VMA_SENT: usize = 10;
+    /// Received SFENCE.VMA request from other HART event.
+    pub const SFENCE_VMA_RECEIVED: usize = 11;
+    /// Sent SFENCE.VMA with ASID request to other HART event.
+    pub const SFENCE_VMA_ASID_SENT: usize = 12;
+    /// Received SFENCE.VMA with ASID request from other HART event.
+    pub const SFENCE_VMA_ASID_RECEIVED: usize = 13;
+    /// Sent HFENCE.GVMA request to other HART event.
+    pub const HFENCE_GVMA_SENT: usize = 14;
+    /// Received HFENCE.GVMA request from other HART event.
+    pub const HFENCE_GVMA_RECEIVED: usize = 15;
+    /// Sent HFENCE.GVMA with VMID request to other HART event.
+    pub const HFENCE_GVMA_VMID_SENT: usize = 16;
+    /// Received HFENCE.GVMA with VMID request from other HART event.
+    pub const HFENCE_GVMA_VMID_RECEIVED: usize = 17;
+    /// Sent HFENCE.VVMA request to other HART event.
+    pub const HFENCE_VVMA_SENT: usize = 18;
+    /// Received HFENCE.VVMA request from other HART event.
+    pub const HFENCE_VVMA_RECEIVED: usize = 19;
+    /// Sent HFENCE.VVMA with ASID request to other HART event.
+    pub const HFENCE_VVMA_ASID_SENT: usize = 20;
+    /// Received HFENCE.VVMA with ASID request from other HART event.
+    pub const HFENCE_VVMA_ASID_RECEIVED: usize = 21;
+    /// RISC-V platform specific firmware events.
+    ///
+    /// The `event_data` configuration (or parameter) contains the event encoding.
+    pub const PLATFORM: usize = 65535;
+}

+ 37 - 0
sbi-spec/src/rfnc.rs

@@ -0,0 +1,37 @@
+//! Chapter 8. RFENCE Extension (EID #0x52464E43 "RFNC").
+
+/// Extension ID for Remote Fence extension.
+pub const EID_RFNC: usize = crate::eid_from_str("RFNC") as _;
+pub use fid::*;
+
+/// Declared in §8.8.
+mod fid {
+    /// Function ID to `FENCE.I` instruction on remote harts.
+    ///
+    /// Declared in §8.1.
+    pub const REMOTE_FENCE_I: usize = 0;
+    /// Function ID to `SFENCE.VMA` for all address spaces on remote harts.
+    ///
+    /// Declared in §8.2.
+    pub const REMOTE_SFENCE_VMA: usize = 1;
+    /// Function ID to address space based `SFENCE.VMA` on remote harts.
+    ///
+    /// Declared in §8.3.
+    pub const REMOTE_SFENCE_VMA_ASID: usize = 2;
+    /// Function ID to virtual machine id based `HFENCE.GVMA` on remote harts.
+    ///
+    /// Declared in §8.4.
+    pub const REMOTE_HFENCE_GVMA_VMID: usize = 3;
+    /// Function ID to `HFENCE.GVMA` for all virtual machines on remote harts.
+    ///
+    /// Declared in §8.5.
+    pub const REMOTE_HFENCE_GVMA: usize = 4;
+    /// Function ID to address space based `HFENCE.VVMA` for current virtual machine on remote harts.
+    ///
+    /// Declared in §8.6.
+    pub const REMOTE_HFENCE_VVMA_ASID: usize = 5;
+    /// Function ID to `HFENCE.VVMA` for all address spaces in current virtual machine on remote harts.
+    ///
+    /// Declared in §8.7.
+    pub const REMOTE_HFENCE_VVMA: usize = 6;
+}

+ 13 - 0
sbi-spec/src/spi.rs

@@ -0,0 +1,13 @@
+//! Chapter 7. IPI Extension (EID #0x735049 "sPI: s-mode IPI").
+
+/// Extension ID for Inter-processor Interrupt extension.
+pub const EID_SPI: usize = crate::eid_from_str("sPI") as _;
+pub use fid::*;
+
+/// Declared in §7.2.
+mod fid {
+    /// Function ID to send an inter-processor interrupt to all harts defined in hart mask.
+    ///
+    /// Declared in §7.1.
+    pub const SEND_IPI: usize = 0;
+}

+ 25 - 0
sbi-spec/src/srst.rs

@@ -0,0 +1,25 @@
+//! Chapter 10. System Reset Extension (EID #0x53525354 "SRST").
+
+/// Extension ID for System Reset extension.
+pub const EID_SRST: usize = crate::eid_from_str("SRST") as _;
+pub use fid::*;
+
+/// Shutdown as reset type.
+pub const RESET_TYPE_SHUTDOWN: u32 = 0;
+/// Cold Reboot as reset type.
+pub const RESET_TYPE_COLD_REBOOT: u32 = 1;
+/// Warm Reboot as reset type.
+pub const RESET_TYPE_WARM_REBOOT: u32 = 2;
+
+/// No Reason as reset reason.
+pub const RESET_REASON_NO_REASON: u32 = 0;
+/// System Failure as reset reason.
+pub const RESET_REASON_SYSTEM_FAILURE: u32 = 1;
+
+/// Declared in §10.2.
+mod fid {
+    /// Function ID to reset the system based on provided reset type and reason.
+    ///
+    /// Declared in §10.1.
+    pub const SYSTEM_RESET: usize = 0;
+}

+ 13 - 0
sbi-spec/src/sta.rs

@@ -0,0 +1,13 @@
+//! Chapter 16. Steal-time Accounting Extension (EID #0x535441 "STA").
+
+/// Extension ID for Steal-time Accounting Extension.
+pub const EID_STA: usize = crate::eid_from_str("STA") as _;
+pub use fid::*;
+
+/// Declared in §16.2.
+mod fid {
+    /// Function ID to set the shared memory physical base address for steal-time accounting of the calling virtual hart and enable the SBI implementation’s steal-time information reporting.
+    ///
+    /// Declared in §16.1.
+    pub const SET_SHMEM: usize = 0;
+}

+ 13 - 0
sbi-spec/src/susp.rs

@@ -0,0 +1,13 @@
+//! Chapter 13. System Suspend Extension (EID #0x53555350 "SUSP").
+
+/// Extension ID for System Suspend Extension.
+pub const EID_SUSP: usize = crate::eid_from_str("SUSP") as _;
+pub use fid::*;
+
+/// Declared in §13.
+mod fid {
+    /// Function ID to suspend under system-level sleep states.
+    ///
+    /// Declared in §13.1.
+    pub const SUSPEND: usize = 0;
+}

+ 13 - 0
sbi-spec/src/time.rs

@@ -0,0 +1,13 @@
+//! Chapter 6. Timer Extension (EID #0x54494D45 "TIME").
+
+/// Extension ID for Timer extension.
+pub const EID_TIME: usize = crate::eid_from_str("TIME") as _;
+pub use fid::*;
+
+/// Declared in §6.2.
+mod fid {
+    /// Function ID to program the clock for next event after an absolute time.
+    ///
+    /// Declared in §6.1.
+    pub const SET_TIMER: usize = 0;
+}

+ 2 - 0
sbi-testing/.cargo/config.toml

@@ -0,0 +1,2 @@
+[build]
+target = "riscv64imac-unknown-none-elf"

+ 7 - 0
sbi-testing/.gitignore

@@ -0,0 +1,7 @@
+**/.*/*
+!/.github/*
+!/.cargo/*
+!/.vscode/settings.json
+
+/target
+/Cargo.lock

+ 37 - 0
sbi-testing/CHANGELOG.md

@@ -0,0 +1,37 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres
+to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [Unreleased]
+
+### Added
+
+- Test new extension DBCN
+
+### Modified
+
+- Update sbi-spec to version 0.0.6
+- Update sbi-rt to version 0.0.3
+
+### Fixed
+
+## [0.0.2] - 2023-01-20
+
+### Modified
+
+- Update dependency crate `riscv` to version 0.10.1
+- Remove feature declaration `asm_sym`, bump MSRV to 1.66.0
+
+## [0.0.1] - 2022-10-10
+
+### Modified
+
+- Project structure to keep test functions at root module
+- Use `sbi-rt` v0.0.2 and `sbi-spec` v0.0.4
+
+[Unreleased]: https://github.com/rustsbi/sbi-testing/compare/v0.0.2...HEAD
+[0.0.2]: https://github.com/rustsbi/sbi-testing/compare/v0.0.1...v0.0.2
+[0.0.1]: https://github.com/rustsbi/sbi-testing/compare/v0.0.0...v0.0.1

+ 27 - 0
sbi-testing/Cargo.toml

@@ -0,0 +1,27 @@
+[package]
+name = "sbi-testing"
+version = "0.0.3-rc.0"
+edition = "2021"
+description = "Provide a set of test cases for supervisors to verify functions of the supervisor executation environment"
+categories = ["os", "no-std"]
+keywords = ["riscv", "sbi", "rustsbi"]
+authors = ["YdrMaster <[email protected]>"]
+repository = "https://github.com/rustsbi/sbi-testing"
+documentation = "https://docs.rs/sbi-testing"
+license = "MulanPSL-2.0 OR MIT"
+readme = "README.md"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[package.metadata.docs.rs]
+default-target = "riscv64imac-unknown-none-elf"
+targets = ["riscv32imac-unknown-none-elf", "riscv64imac-unknown-none-elf"]
+
+[dependencies]
+sbi-rt = "0.0.3-rc.5"
+sbi-spec = "0.0.7-alpha.3"
+riscv = "0.10.1"
+log_crate = { version = "0.4", package = "log", optional = true }
+
+[features]
+log = ["log_crate"]

+ 9 - 0
sbi-testing/LICENSE-MIT

@@ -0,0 +1,9 @@
+The MIT License (MIT)
+
+Copyright © 2022 YdrMaster
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ 101 - 0
sbi-testing/LICENSE-MULAN

@@ -0,0 +1,101 @@
+木兰宽松许可证, 第2版
+
+2020年1月 <http://license.coscl.org.cn/MulanPSL2>
+
+您对“软件”的复制、使用、修改及分发受木兰宽松许可证,第2版(“本许可证”)的如下条款的约束:
+
+0. 定义
+
+“软件”是指由“贡献”构成的许可在“本许可证”下的程序和相关文档的集合。
+
+“贡献”是指由任一“贡献者”许可在“本许可证”下的受版权法保护的作品。
+
+“贡献者”是指将受版权法保护的作品许可在“本许可证”下的自然人或“法人实体”。
+
+“法人实体”是指提交贡献的机构及其“关联实体”。
+
+“关联实体”是指,对“本许可证”下的行为方而言,控制、受控制或与其共同受控制的机构,此处的控制是指有受控方或共同受控方至少50%直接或间接的投票权、资金或其他有价证券。
+
+1. 授予版权许可
+
+每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的版权许可,您可以复制、使用、修改、分发其“贡献”,不论修改与否。
+
+2. 授予专利许可
+
+每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的(根据本条规定撤销除外)专利许可,供您制造、委托制造、使用、许诺销售、销售、进口其“贡献”或以其他方式转移其“贡献”。前述专利许可仅限于“贡献者”现在或将来拥有或控制的其“贡献”本身或其“贡献”与许可“贡献”时的“软件”结合而将必然会侵犯的专利权利要求,不包括对“贡献”的修改或包含“贡献”的其他结合。如果您或您的“关联实体”直接或间接地,就“软件”或其中的“贡献”对任何人发起专利侵权诉讼(包括反诉或交叉诉讼)或其他专利维权行动,指控其侵犯专利权,则“本许可证”授予您对“软件”的专利许可自您提起诉讼或发起维权行动之日终止。
+
+3. 无商标许可
+
+“本许可证”不提供对“贡献者”的商品名称、商标、服务标志或产品名称的商标许可,但您为满足第4条规定的声明义务而必须使用除外。
+
+4. 分发限制
+
+您可以在任何媒介中将“软件”以源程序形式或可执行形式重新分发,不论修改与否,但您必须向接收者提供“本许可证”的副本,并保留“软件”中的版权、商标、专利及免责声明。
+
+5. 免责声明与责任限制
+
+“软件”及其中的“贡献”在提供时不带任何明示或默示的担保。在任何情况下,“贡献者”或版权所有者不对任何人因使用“软件”或其中的“贡献”而引发的任何直接或间接损失承担责任,不论因何种原因导致或者基于何种法律理论,即使其曾被建议有此种损失的可能性。
+
+6. 语言
+
+“本许可证”以中英文双语表述,中英文版本具有同等法律效力。如果中英文版本存在任何冲突不一致,以中文版为准。
+
+条款结束
+
+如何将木兰宽松许可证,第2版,应用到您的软件
+
+如果您希望将木兰宽松许可证,第2版,应用到您的新软件,为了方便接收者查阅,建议您完成如下三步:
+
+1,请您补充如下声明中的空白,包括软件名、软件的首次发表年份以及您作为版权人的名字;
+
+2,请您在软件包的一级目录下创建以“LICENSE”为名的文件,将整个许可证文本放入该文件中;
+
+3,请将如下声明文本放入每个源文件的头部注释中。
+
+Copyright (c) 2020 Luo Jia
+
+RustSBI is licensed under Mulan PSL v2. You can use this software according to the terms and conditions of the Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: <http://license.coscl.org.cn/MulanPSL2>
+
+THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. See the Mulan PSL v2 for more details.
+
+January 2020 <http://license.coscl.org.cn/MulanPSL2>
+
+Your reproduction, use, modification and distribution of the Software shall be subject to Mulan PSL v2 (this License) with the following terms and conditions:
+
+0. Definition
+
+Software means the program and related documents which are licensed under this License and comprise all Contribution(s).
+
+Contribution means the copyrightable work licensed by a particular Contributor under this License.
+
+Contributor means the Individual or Legal Entity who licenses its copyrightable work under this License.
+
+Legal Entity means the entity making a Contribution and all its Affiliates.
+
+Affiliates means entities that control, are controlled by, or are under common control with the acting entity under this License, ‘control’ means direct or indirect ownership of at least fifty percent (50%) of the voting power, capital or other securities of controlled or commonly controlled entity.
+
+1. Grant of Copyright License
+
+Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable copyright license to reproduce, use, modify, or distribute its Contribution, with modification or not.
+
+2. Grant of Patent License
+
+Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable (except for revocation under this Section) patent license to make, have made, use, offer for sale, sell, import or otherwise transfer its Contribution, where such patent license is only limited to the patent claims owned or controlled by such Contributor now or in future which will be necessarily infringed by its Contribution alone, or by combination of the Contribution with the Software to which the Contribution was contributed. The patent license shall not apply to any modification of the Contribution, and any other combination which includes the Contribution. If you or your Affiliates directly or indirectly institute patent litigation (including a cross claim or counterclaim in a litigation) or other patent enforcement activities against any individual or entity by alleging that the Software or any Contribution in it infringes patents, then any patent license granted to you under this License for the Software shall terminate as of the date such litigation or activity is filed or taken.
+
+3. No Trademark License
+
+No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, except as required to fulfill notice requirements in Section 4.
+
+4. Distribution Restriction
+
+You may distribute the Software in any medium with or without modification, whether in source or executable forms, provided that you provide recipients with a copy of this License and retain copyright, patent, trademark and disclaimer statements in the Software.
+
+5. Disclaimer of Warranty and Limitation of Liability
+
+THE SOFTWARE AND CONTRIBUTION IN IT ARE PROVIDED WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED. IN NO EVENT SHALL ANY CONTRIBUTOR OR COPYRIGHT HOLDER BE LIABLE TO YOU FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO ANY DIRECT, OR INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING FROM YOUR USE OR INABILITY TO USE THE SOFTWARE OR THE CONTRIBUTION IN IT, NO MATTER HOW IT’S CAUSED OR BASED ON WHICH LEGAL THEORY, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+6. Language
+
+THIS LICENSE IS WRITTEN IN BOTH CHINESE AND ENGLISH, AND THE CHINESE VERSION AND ENGLISH VERSION SHALL HAVE THE SAME LEGAL EFFECT. IN THE CASE OF DIVERGENCE BETWEEN THE CHINESE AND ENGLISH VERSIONS, THE CHINESE VERSION SHALL PREVAIL.
+
+END OF THE TERMS AND CONDITIONS

+ 21 - 0
sbi-testing/README.md

@@ -0,0 +1,21 @@
+# SBI 功能测试集
+
+[![CI](https://github.com/rustsbi/sbi-testing/actions/workflows/workflow.yml/badge.svg?branch=main)](https://github.com/rustsbi/sbi-testing/actions)
+[![Latest version](https://img.shields.io/crates/v/sbi-testing.svg)](https://crates.io/crates/sbi-testing)
+[![issue](https://img.shields.io/github/issues/rustsbi/sbi-testing)](https://github.com/rustsbi/sbi-testing/issues)
+[![Documentation](https://docs.rs/sbi-testing/badge.svg)](https://docs.rs/sbi-testing)
+![license](https://img.shields.io/github/license/rustsbi/sbi-testing)
+
+- [An English README](README_EN.md)
+
+这个库封装了一系列测试,供特权软件测试为自己服务的特权运行环境功能是否正常。
+
+SBI 1.0.0 标准各章节的实现情况:
+
+- [x] §4  Base
+- [x] §6  TIME
+- [x] §7  sPI
+- [ ] §8  RFNC
+- [x] §9  HSM
+- [ ] §10 SRST
+- [ ] §11 PMU

+ 21 - 0
sbi-testing/README_EN.md

@@ -0,0 +1,21 @@
+# SBI Testing
+
+[![CI](https://github.com/rustsbi/sbi-testing/actions/workflows/workflow.yml/badge.svg?branch=main)](https://github.com/rustsbi/sbi-testing/actions)
+[![Latest version](https://img.shields.io/crates/v/sbi-testing.svg)](https://crates.io/crates/sbi-testing)
+[![issue](https://img.shields.io/github/issues/rustsbi/sbi-testing)](https://github.com/rustsbi/sbi-testing/issues)
+[![Documentation](https://docs.rs/sbi-testing/badge.svg)](https://docs.rs/sbi-testing)
+![license](https://img.shields.io/github/license/rustsbi/sbi-testing)
+
+- [中文自述文件](README.md)
+
+This library provides a set of test cases for supervisors to verify functions of the supervisor executation environment.
+
+Characters implementation of SBI 1.0.0 specification:
+
+- [x] §4  Base
+- [x] §6  TIME
+- [x] §7  sPI
+- [ ] §8  RFNC
+- [x] §9  HSM
+- [ ] §10 SRST
+- [ ] §11 PMU

+ 5 - 0
sbi-testing/rust-toolchain.toml

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

+ 106 - 0
sbi-testing/src/base.rs

@@ -0,0 +1,106 @@
+//! RISC-V SBI Base extension test suite.
+
+use sbi::{ExtensionInfo, Version};
+use sbi_spec::base::impl_id;
+
+/// Base extension test cases.
+#[derive(Clone, Debug)]
+pub enum Case {
+    /// Can't procceed test for base extension does not exist.
+    NotExist,
+    /// Test begin
+    Begin,
+    /// Test process for getting SBI specification version.
+    GetSbiSpecVersion(Version),
+    /// Test process for getting SBI implementation ID.
+    GetSbiImplId(Result<&'static str, usize>),
+    /// Test process for getting version of SBI implementation.
+    GetSbiImplVersion(usize),
+    /// Test process for probe standard SBI extensions.
+    ProbeExtensions(Extensions),
+    /// Test process for getting vendor ID from RISC-V environment.
+    GetMVendorId(usize),
+    /// Test process for getting architecture ID from RISC-V environment.
+    GetMArchId(usize),
+    /// Test process for getting implementation ID from RISC-V environment.
+    GetMimpId(usize),
+    /// All test cases on base module finished.
+    Pass,
+}
+
+/// Information about all SBI standard extensions.
+#[derive(Clone, Debug)]
+pub struct Extensions {
+    /// Timer programmer extension.
+    pub time: ExtensionInfo,
+    /// Inter-processor Interrupt extension.
+    pub spi: ExtensionInfo,
+    /// Remote Fence extension.
+    pub rfnc: ExtensionInfo,
+    /// Hart State Monitor extension.
+    pub hsm: ExtensionInfo,
+    /// System Reset extension.
+    pub srst: ExtensionInfo,
+    /// Performance Monitor Unit extension.
+    pub pmu: ExtensionInfo,
+}
+
+impl core::fmt::Display for Extensions {
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        write!(f, "[Base")?;
+        if self.time.is_available() {
+            write!(f, ", TIME")?;
+        }
+        if self.spi.is_available() {
+            write!(f, ", sPI")?;
+        }
+        if self.rfnc.is_available() {
+            write!(f, ", RFNC")?;
+        }
+        if self.hsm.is_available() {
+            write!(f, ", HSM")?;
+        }
+        if self.srst.is_available() {
+            write!(f, ", SRST")?;
+        }
+        if self.pmu.is_available() {
+            write!(f, ", PMU")?;
+        }
+        write!(f, "]")
+    }
+}
+
+/// Test base extension.
+///
+/// The test case output would be handled in `f`.
+pub fn test(mut f: impl FnMut(Case)) {
+    if sbi::probe_extension(sbi::Base).is_unavailable() {
+        f(Case::NotExist);
+        return;
+    }
+    f(Case::Begin);
+    f(Case::GetSbiSpecVersion(sbi::get_spec_version()));
+    f(Case::GetSbiImplId(match sbi::get_sbi_impl_id() {
+        impl_id::BBL => Ok("BBL"),
+        impl_id::OPEN_SBI => Ok("OpenSBI"),
+        impl_id::XVISOR => Ok("Xvisor"),
+        impl_id::KVM => Ok("KVM"),
+        impl_id::RUST_SBI => Ok("RustSBI"),
+        impl_id::DIOSIX => Ok("Diosix"),
+        impl_id::COFFER => Ok("Coffer"),
+        unknown => Err(unknown),
+    }));
+    f(Case::GetSbiImplVersion(sbi::get_sbi_impl_version()));
+    f(Case::ProbeExtensions(Extensions {
+        time: sbi::probe_extension(sbi::Timer),
+        spi: sbi::probe_extension(sbi::Ipi),
+        rfnc: sbi::probe_extension(sbi::Fence),
+        hsm: sbi::probe_extension(sbi::Hsm),
+        srst: sbi::probe_extension(sbi::Reset),
+        pmu: sbi::probe_extension(sbi::Pmu),
+    }));
+    f(Case::GetMVendorId(sbi::get_mvendorid()));
+    f(Case::GetMArchId(sbi::get_marchid()));
+    f(Case::GetMimpId(sbi::get_mimpid()));
+    f(Case::Pass);
+}

+ 66 - 0
sbi-testing/src/dbcn.rs

@@ -0,0 +1,66 @@
+//! Debug console extension test suite.
+
+use sbi::SbiRet;
+use sbi_spec::binary::Physical;
+
+/// Debug console extension test cases.
+#[derive(Clone, Debug)]
+pub enum Case {
+    /// Can't procceed test for debug console extension does not exist.
+    NotExist,
+    /// Test begin.
+    Begin,
+    /// Test process for write a byte to console.
+    WriteByte,
+    /// Test failed for can't write byte.
+    WritingByteFailed(SbiRet),
+    /// Test process for write complete slice.
+    WriteSlice,
+    /// Test process for write partial slice.
+    WritingPartialSlice(usize),
+    /// Test failed for can't write slice.
+    WritingSliceFailed(SbiRet),
+    /// Test process for read some bytes from console.
+    Read(usize),
+    /// Test failed for can't read to buffer.
+    ReadingFailed(SbiRet),
+    /// All test cases on debug console extension has passed.
+    Pass,
+}
+
+/// Test debug console extension.
+pub fn test(mut f: impl FnMut(Case)) {
+    if sbi::probe_extension(sbi::Console).is_unavailable() {
+        f(Case::NotExist);
+        return;
+    }
+
+    f(Case::Begin);
+
+    let ret = sbi::console_write_byte(b'H');
+    if ret.is_ok() {
+        f(Case::WriteByte);
+    } else {
+        f(Case::WritingByteFailed(ret));
+    }
+    let words = b"ello, world!\r\n";
+    let ret = sbi::console_write(Physical::new(words.len(), words.as_ptr() as _, 0));
+    if let Some(len) = ret.ok() {
+        f(if len == words.len() {
+            Case::WriteSlice
+        } else {
+            Case::WritingPartialSlice(len)
+        });
+    } else {
+        f(Case::WritingSliceFailed(ret));
+    }
+    let mut buffer = [0u8; 16];
+    let ret = sbi::console_read(Physical::new(buffer.len(), buffer.as_mut_ptr() as _, 0));
+    if let Some(len) = ret.ok() {
+        f(Case::Read(len));
+    } else {
+        f(Case::ReadingFailed(ret));
+    }
+
+    f(Case::Pass);
+}

+ 276 - 0
sbi-testing/src/hsm.rs

@@ -0,0 +1,276 @@
+//! Hart state monitor extension test suite.
+
+use core::sync::atomic::{AtomicU32, Ordering};
+use sbi::SbiRet;
+use sbi_spec::hsm::hart_state;
+
+/// Hart state monitor extension test cases.
+#[derive(Clone, Debug)]
+pub enum Case<'a> {
+    /// Can't procceed test for Hart state monitor extension does not exist.
+    NotExist,
+    /// Test begin
+    Begin,
+    /// Test failed for hart started before test begin.
+    ///
+    /// The returned value includes which hart led to this test failure.
+    HartStartedBeforeTest(usize),
+    /// Test failed for no other harts are available to be tested.
+    NoStoppedHart,
+    /// Test process for begin test hart state monitor on one batch.
+    BatchBegin(&'a [usize]),
+    /// Test process for target hart to be tested has started.
+    HartStarted(usize),
+    /// Test failed for can't start target hart with [`SbiRet`] error.
+    HartStartFailed {
+        /// The target hart ID that has failed to start.
+        hartid: usize,
+        /// The `SbiRet` value for the failed hart start SBI call.
+        ret: SbiRet,
+    },
+    /// Test process for target hart to be tested has non-retentively suspended.
+    HartSuspendedNonretentive(usize),
+    /// Test process for target hart to be tested has resumed.
+    HartResumed(usize),
+    /// Test process for target hart to be tested has retentively suspended.
+    HartSuspendedRetentive(usize),
+    /// Test process for target hart to be tested has stopped.
+    HartStopped(usize),
+    /// Test process for harts on current batch has passed the tests.
+    BatchPass(&'a [usize]),
+    /// All test cases on hart state monitor module finished.
+    Pass,
+}
+
+/// Test hart state monitor extension on given harts.
+///
+/// The test case output is to be handled in `f`.
+pub fn test(
+    primary_hart_id: usize,
+    mut hart_mask: usize,
+    hart_mask_base: usize,
+    mut f: impl FnMut(Case),
+) {
+    // 不支持 HSM 扩展
+    if sbi::probe_extension(sbi::Hsm).is_unavailable() {
+        f(Case::NotExist);
+        return;
+    }
+    f(Case::Begin);
+    // 分批测试
+
+    let mut batch = [0usize; TEST_BATCH_SIZE];
+    let mut batch_count = 0;
+    let mut batch_size = 0;
+    let mut hartid = hart_mask_base;
+    while hart_mask != 0 {
+        if hartid != primary_hart_id {
+            // 副核在测试前必须处于停止状态
+            if sbi::hart_get_status(hartid) == STOPPED {
+                batch[batch_size] = hartid;
+                batch_size += 1;
+                // 收集一个批次,执行测试
+                if batch_size == TEST_BATCH_SIZE {
+                    if test_batch(&batch, &mut f) {
+                        batch_count += 1;
+                        batch_size = 0;
+                    } else {
+                        return;
+                    }
+                }
+            }
+            // 副核不在停止状态
+            else {
+                f(Case::HartStartedBeforeTest(hartid));
+            }
+        }
+        let distance = hart_mask.trailing_zeros() + 1;
+        hart_mask >>= distance;
+        hartid += distance as usize;
+    }
+    // 为不满一批次的核执行测试
+    if batch_size > 0 {
+        if test_batch(&batch[..batch_size], &mut f) {
+            f(Case::Pass);
+        }
+    }
+    // 所有批次通过测试
+    else if batch_count > 0 {
+        f(Case::Pass);
+    }
+    // 没有找到能参与测试的副核
+    else {
+        f(Case::NoStoppedHart)
+    }
+}
+
+const STARTED: SbiRet = SbiRet::success(hart_state::STARTED);
+const STOPPED: SbiRet = SbiRet::success(hart_state::STOPPED);
+const SUSPENDED: SbiRet = SbiRet::success(hart_state::SUSPENDED);
+
+const TEST_BATCH_SIZE: usize = 4;
+static mut STACK: [ItemPerHart; TEST_BATCH_SIZE] = [ItemPerHart::ZERO; TEST_BATCH_SIZE];
+
+#[repr(C, align(512))]
+struct ItemPerHart {
+    stage: AtomicU32,
+    signal: AtomicU32,
+    stack: [u8; 504],
+}
+
+const STAGE_IDLE: u32 = 0;
+const STAGE_STARTED: u32 = 1;
+const STAGE_RESUMED: u32 = 2;
+
+impl ItemPerHart {
+    #[allow(clippy::declare_interior_mutable_const)]
+    const ZERO: Self = Self {
+        stage: AtomicU32::new(STAGE_IDLE),
+        signal: AtomicU32::new(0),
+        stack: [0; 504],
+    };
+
+    #[inline]
+    fn reset(&mut self) -> *const ItemPerHart {
+        self.stage.store(STAGE_IDLE, Ordering::Relaxed);
+        self as _
+    }
+
+    #[inline]
+    fn wait_start(&self) {
+        while self.stage.load(Ordering::Relaxed) != STAGE_STARTED {
+            core::hint::spin_loop();
+        }
+    }
+
+    #[inline]
+    fn wait_resume(&self) {
+        while self.stage.load(Ordering::Relaxed) != STAGE_RESUMED {
+            core::hint::spin_loop();
+        }
+    }
+
+    #[inline]
+    fn send_signal(&self) {
+        self.signal.store(1, Ordering::Release);
+    }
+
+    #[inline]
+    fn wait_signal(&self) {
+        while self
+            .signal
+            .compare_exchange(1, 0, Ordering::Relaxed, Ordering::Relaxed)
+            .is_err()
+        {
+            core::hint::spin_loop();
+        }
+    }
+}
+
+/// 测试一批核
+fn test_batch(batch: &[usize], mut f: impl FnMut(Case)) -> bool {
+    f(Case::BatchBegin(batch));
+    // 初始这些核都是停止状态,测试 start
+    for (i, hartid) in batch.iter().copied().enumerate() {
+        let ptr = unsafe { STACK[i].reset() };
+        let ret = sbi::hart_start(hartid, test_entry as _, ptr as _);
+        if ret.is_err() {
+            f(Case::HartStartFailed { hartid, ret });
+            return false;
+        }
+    }
+    // 测试不可恢复休眠
+    for (i, hartid) in batch.iter().copied().enumerate() {
+        let item = unsafe { &mut STACK[i] };
+        // 等待完成启动
+        while sbi::hart_get_status(hartid) != STARTED {
+            core::hint::spin_loop();
+        }
+        f(Case::HartStarted(hartid));
+        // 等待信号
+        item.wait_start();
+        // 发出信号
+        item.send_signal();
+        // 等待完成休眠
+        while sbi::hart_get_status(hartid) != SUSPENDED {
+            core::hint::spin_loop();
+        }
+        f(Case::HartSuspendedNonretentive(hartid));
+    }
+    // 全部唤醒
+    let mut mask = 1usize;
+    for hartid in &batch[1..] {
+        mask |= 1 << (hartid - batch[0]);
+    }
+    sbi::send_ipi(sbi_spec::binary::HartMask::from_mask_base(mask, batch[0]));
+    // 测试可恢复休眠
+    for (i, hartid) in batch.iter().copied().enumerate() {
+        let item = unsafe { &mut STACK[i] };
+        // 等待完成恢复
+        while sbi::hart_get_status(hartid) != STARTED {
+            core::hint::spin_loop();
+        }
+        f(Case::HartResumed(hartid));
+        // 等待信号
+        item.wait_resume();
+        // 发出信号
+        item.send_signal();
+        // 等待完成休眠
+        while sbi::hart_get_status(hartid) != SUSPENDED {
+            core::hint::spin_loop();
+        }
+        f(Case::HartSuspendedRetentive(hartid));
+        // 单独恢复
+        sbi::send_ipi(sbi_spec::binary::HartMask::from_mask_base(1, hartid));
+        // 等待关闭
+        while sbi::hart_get_status(hartid) != STOPPED {
+            core::hint::spin_loop();
+        }
+        f(Case::HartStopped(hartid));
+    }
+    f(Case::BatchPass(batch));
+    true
+}
+
+/// 测试用启动入口
+#[naked]
+unsafe extern "C" fn test_entry(hartid: usize, opaque: *mut ItemPerHart) -> ! {
+    core::arch::asm!(
+        "csrw sie, zero",   // 关中断
+        "call {set_stack}", // 设置栈
+        "j    {rust_main}", // 进入 rust
+        set_stack = sym set_stack,
+        rust_main = sym rust_main,
+        options(noreturn),
+    )
+}
+
+#[naked]
+unsafe extern "C" fn set_stack(hart_id: usize, ptr: *const ItemPerHart) {
+    core::arch::asm!("addi sp, a1, 512", "ret", options(noreturn));
+}
+
+#[inline(never)]
+extern "C" fn rust_main(hart_id: usize, opaque: *mut ItemPerHart) -> ! {
+    let item = unsafe { &mut *opaque };
+    match item.stage.compare_exchange(
+        STAGE_IDLE,
+        STAGE_STARTED,
+        Ordering::AcqRel,
+        Ordering::Acquire,
+    ) {
+        Ok(_) => {
+            item.wait_signal();
+            let ret = sbi::hart_suspend(sbi::NonRetentive, test_entry as _, opaque as _);
+            unreachable!("suspend [{hart_id}] but {ret:?}")
+        }
+        Err(STAGE_STARTED) => {
+            item.stage.store(STAGE_RESUMED, Ordering::Release);
+            item.wait_signal();
+            let _ = sbi::hart_suspend(sbi::Retentive, test_entry as _, opaque as _);
+            let ret = sbi::hart_stop();
+            unreachable!("suspend [{hart_id}] but {ret:?}")
+        }
+        Err(_) => unreachable!(),
+    }
+}

+ 40 - 0
sbi-testing/src/lib.rs

@@ -0,0 +1,40 @@
+//! RISC-V Supervisor Binary Interface test suite
+
+#![no_std]
+#![deny(warnings, missing_docs)]
+#![feature(naked_functions, asm_const)]
+
+mod thread;
+
+pub extern crate sbi_rt as sbi;
+
+#[cfg(feature = "log")]
+pub extern crate log_crate as log;
+
+#[cfg(feature = "log")]
+mod log_test;
+
+#[cfg(feature = "log")]
+pub use log_test::Testing;
+
+// §4
+mod base;
+pub use base::{test as test_base, Case as BaseCase, Extensions};
+// §6
+mod time;
+pub use time::{test as test_timer, Case as TimerCase};
+// §7
+mod spi;
+pub use spi::{test as test_ipi, Case as IpiCase};
+// §8
+// pub mod rfnc;
+// §9
+mod hsm;
+pub use hsm::{test as test_hsm, Case as HsmCase};
+// §10
+// pub mod srst;
+// §11
+// pub mod pmu;
+// §12
+mod dbcn;
+pub use dbcn::{test as test_dbcn, Case as DbcnCase};

+ 167 - 0
sbi-testing/src/log_test.rs

@@ -0,0 +1,167 @@
+use crate::{base, dbcn, hsm, spi, time};
+use log_crate::*;
+
+/// Automatic SBI testing with logging enabled.
+pub struct Testing {
+    /// The hart ID to test most of single core extensions.
+    pub hartid: usize,
+    /// A list of harts to test Hart State Monitor extension.
+    pub hart_mask: usize,
+    /// Base of hart list to test Hart State Monitor extension.
+    pub hart_mask_base: usize,
+    /// Delay value to test Timer programmer extension.
+    pub delay: u64,
+}
+
+const TARGET: &str = "sbi-testing";
+
+impl Testing {
+    /// Start testing process of RISC-V SBI implementation.
+    pub fn test(self) -> bool {
+        let mut result = true;
+        base::test(|case| {
+            use base::Case::*;
+            match case {
+                NotExist => panic!("Sbi `Base` not exist"),
+                Begin => info!(target: TARGET, "Testing `Base`"),
+                Pass => info!(target: TARGET, "Sbi `Base` test pass"),
+                GetSbiSpecVersion(version) => {
+                    info!(target: TARGET, "sbi spec version = {version}");
+                }
+                GetSbiImplId(Ok(name)) => {
+                    info!(target: TARGET, "sbi impl = {name}");
+                }
+                GetSbiImplId(Err(unknown)) => {
+                    warn!(target: TARGET, "unknown sbi impl = {unknown:#x}");
+                }
+                GetSbiImplVersion(version) => {
+                    info!(target: TARGET, "sbi impl version = {version:#x}");
+                }
+                ProbeExtensions(exts) => {
+                    info!(target: TARGET, "sbi extensions = {exts}");
+                }
+                GetMVendorId(id) => {
+                    info!(target: TARGET, "mvendor id = {id:#x}");
+                }
+                GetMArchId(id) => {
+                    info!(target: TARGET, "march id = {id:#x}");
+                }
+                GetMimpId(id) => {
+                    info!(target: TARGET, "mimp id = {id:#x}");
+                }
+            }
+        });
+        time::test(self.delay, |case| {
+            use time::Case::*;
+            match case {
+                NotExist => {
+                    error!(target: TARGET, "Sbi `TIME` not exist");
+                    result = false;
+                }
+                Begin => info!(target: TARGET, "Testing `TIME`"),
+                Pass => info!(target: TARGET, "Sbi `TIME` test pass"),
+                Interval { begin: _, end: _ } => {
+                    info!(
+                        target: TARGET,
+                        "read time register successfuly, set timer +1s"
+                    );
+                }
+                ReadFailed => {
+                    error!(target: TARGET, "csrr time failed");
+                    result = false;
+                }
+                TimeDecreased { a, b } => {
+                    error!(target: TARGET, "time decreased: {a} -> {b}");
+                    result = false;
+                }
+                SetTimer => {
+                    info!(target: TARGET, "timer interrupt delegate successfuly");
+                }
+                UnexpectedTrap(trap) => {
+                    error!(
+                        target: TARGET,
+                        "expect trap at supervisor timer, but {trap:?} was caught"
+                    );
+                    result = false;
+                }
+            }
+        });
+        spi::test(self.hartid, |case| {
+            use spi::Case::*;
+            match case {
+                NotExist => {
+                    error!(target: TARGET, "Sbi `sPI` not exist");
+                    result = false;
+                }
+                Begin => info!(target: TARGET, "Testing `sPI`"),
+                Pass => info!(target: TARGET, "Sbi `sPI` test pass"),
+                SendIpi => info!(target: TARGET, "send ipi successfuly"),
+                UnexpectedTrap(trap) => {
+                    error!(
+                        target: TARGET,
+                        "expect trap at supervisor soft, but {trap:?} was caught"
+                    );
+                    result = false;
+                }
+            }
+        });
+        hsm::test(self.hartid, self.hart_mask, self.hart_mask_base, |case| {
+            use hsm::Case::*;
+            match case {
+                NotExist => {
+                    error!(target: TARGET, "Sbi `HSM` not exist");
+                    result = false;
+                }
+                Begin => info!(target: TARGET, "Testing `HSM`"),
+                Pass => info!(target: TARGET, "Sbi `HSM` test pass"),
+                HartStartedBeforeTest(id) => warn!(target: TARGET, "hart {id} already started"),
+                NoStoppedHart => warn!(target: TARGET, "no stopped hart"),
+                BatchBegin(batch) => info!(target: TARGET, "Testing harts: {batch:?}"),
+                HartStarted(id) => debug!(target: TARGET, "hart {id} started"),
+                HartStartFailed { hartid, ret } => {
+                    error!(target: TARGET, "hart {hartid} start failed: {ret:?}");
+                    result = false;
+                }
+                HartSuspendedNonretentive(id) => {
+                    debug!(target: TARGET, "hart {id} suspended nonretentive")
+                }
+                HartResumed(id) => debug!(target: TARGET, "hart {id} resumed"),
+                HartSuspendedRetentive(id) => {
+                    debug!(target: TARGET, "hart {id} suspended retentive")
+                }
+                HartStopped(id) => debug!(target: TARGET, "hart {id} stopped"),
+                BatchPass(batch) => info!(target: TARGET, "Testing Pass: {batch:?}"),
+            }
+        });
+        dbcn::test(|case| {
+            use dbcn::Case::*;
+            match case {
+                NotExist => {
+                    error!(target: TARGET, "Sbi `DBCN` not exist");
+                    result = false;
+                }
+                Begin => info!(target: TARGET, "Testing `DBCN`"),
+                Pass => info!(target: TARGET, "Sbi `DBCN` test pass"),
+                WriteByte => {}
+                WritingByteFailed(ret) => {
+                    error!(target: TARGET, "writing byte failed: {ret:?}");
+                    result = false;
+                }
+                WriteSlice => info!(target: TARGET, "writing slice successfuly"),
+                WritingPartialSlice(len) => {
+                    warn!(target: TARGET, "writing partial slice: {len} bytes written");
+                }
+                WritingSliceFailed(ret) => {
+                    error!(target: TARGET, "writing slice failed: {ret:?}");
+                    result = false;
+                }
+                Read(len) => info!(target: TARGET, "reading {len} bytes from console"),
+                ReadingFailed(ret) => {
+                    error!(target: TARGET, "reading failed: {ret:?}");
+                    result = false;
+                }
+            }
+        });
+        result
+    }
+}

+ 56 - 0
sbi-testing/src/spi.rs

@@ -0,0 +1,56 @@
+//! Inter-processor interrupt extension test suite.
+
+use crate::thread::Thread;
+use riscv::register::{
+    scause::Interrupt,
+    scause::{self, Trap},
+    sie,
+};
+
+/// Inter-processor Interrupt extension test cases.
+#[derive(Clone, Debug)]
+pub enum Case {
+    /// Can't procceed test for inter-processor interrupt extension does not exist.
+    NotExist,
+    /// Test begin.
+    Begin,
+    /// Test process for an inter-processor interrupt has been received.
+    SendIpi,
+    /// Test failed for unexpected trap occurred upon tests.
+    UnexpectedTrap(Trap),
+    /// All test cases on inter-processor interrupt extension has passed.
+    Pass,
+}
+
+/// Test inter-processor interrupt extension.
+pub fn test(hart_id: usize, mut f: impl FnMut(Case)) {
+    if sbi::probe_extension(sbi::Timer).is_unavailable() {
+        f(Case::NotExist);
+        return;
+    }
+
+    fn ipi(hart_id: usize) -> ! {
+        sbi::send_ipi(sbi_spec::binary::HartMask::from_mask_base(1 << hart_id, 0));
+        // 必须立即触发中断,即使是一个指令的延迟,也会触发另一个异常
+        unsafe { core::arch::asm!("unimp", options(noreturn, nomem)) };
+    }
+
+    f(Case::Begin);
+    let mut stack = [0usize; 32];
+    let mut thread = Thread::new(ipi as _);
+    *thread.sp_mut() = stack.as_mut_ptr_range().end as _;
+    *thread.a_mut(0) = hart_id;
+    unsafe {
+        sie::set_ssoft();
+        thread.execute();
+    }
+    match scause::read().cause() {
+        Trap::Interrupt(Interrupt::SupervisorSoft) => {
+            f(Case::SendIpi);
+            f(Case::Pass);
+        }
+        trap => {
+            f(Case::UnexpectedTrap(trap));
+        }
+    }
+}

+ 180 - 0
sbi-testing/src/thread.rs

@@ -0,0 +1,180 @@
+/// 线程上下文。
+#[repr(C)]
+pub struct Thread {
+    sctx: usize,
+    x: [usize; 31],
+    sepc: usize,
+}
+
+#[allow(unused)]
+impl Thread {
+    /// 创建空白上下文。
+    #[inline]
+    pub const fn new(sepc: usize) -> Self {
+        Self {
+            sctx: 0,
+            x: [0; 31],
+            sepc,
+        }
+    }
+
+    /// 读取通用寄存器。
+    #[inline]
+    pub fn x(&self, n: usize) -> usize {
+        self.x[n - 1]
+    }
+
+    /// 修改通用寄存器。
+    #[inline]
+    pub fn x_mut(&mut self, n: usize) -> &mut usize {
+        &mut self.x[n - 1]
+    }
+
+    /// 读取参数寄存器。
+    #[inline]
+    pub fn a(&self, n: usize) -> usize {
+        self.x(n + 10)
+    }
+
+    /// 修改参数寄存器。
+    #[inline]
+    pub fn a_mut(&mut self, n: usize) -> &mut usize {
+        self.x_mut(n + 10)
+    }
+
+    /// 读取栈指针。
+    #[inline]
+    pub fn sp(&self) -> usize {
+        self.x(2)
+    }
+
+    /// 修改栈指针。
+    #[inline]
+    pub fn sp_mut(&mut self) -> &mut usize {
+        self.x_mut(2)
+    }
+
+    /// 将 pc 移至下一条指令。
+    ///
+    /// # Notice
+    ///
+    /// 假设这一条指令不是压缩版本。
+    #[inline]
+    pub fn move_next(&mut self) {
+        self.sepc = self.sepc.wrapping_add(4);
+    }
+
+    /// 执行此线程,并返回 `sstatus`。
+    ///
+    /// # Safety
+    ///
+    /// 将修改 `sscratch`、`sepc`、`sstatus` 和 `stvec`。
+    #[inline]
+    pub unsafe fn execute(&mut self) -> usize {
+        // 设置线程仍在 S 态并打开中断
+        let mut sstatus: usize;
+        core::arch::asm!("csrr {}, sstatus", out(reg) sstatus);
+        const PREVILEGE_BIT: usize = 1 << 8;
+        const INTERRUPT_BIT: usize = 1 << 5;
+        sstatus |= PREVILEGE_BIT | INTERRUPT_BIT;
+        // 执行线程
+        core::arch::asm!(
+            "   csrw sscratch, {sscratch}
+                csrw sepc    , {sepc}
+                csrw sstatus , {sstatus}
+                addi sp, sp, -8
+                sd   ra, (sp)
+                call {execute_naked}
+                ld   ra, (sp)
+                addi sp, sp,  8
+                csrr {sepc}   , sepc
+                csrr {sstatus}, sstatus
+            ",
+            sscratch      = in(reg) self,
+            sepc          = inlateout(reg) self.sepc,
+            sstatus       = inlateout(reg) sstatus,
+            execute_naked = sym execute_naked,
+        );
+        sstatus
+    }
+}
+
+/// 线程切换核心部分。
+///
+/// 通用寄存器压栈,然后从预存在 `sscratch` 里的上下文指针恢复线程通用寄存器。
+///
+/// # Safety
+///
+/// 裸函数。
+#[naked]
+unsafe extern "C" fn execute_naked() {
+    core::arch::asm!(
+        r"  .altmacro
+            .macro SAVE n
+                sd x\n, \n*8(sp)
+            .endm
+            .macro SAVE_ALL
+                sd x1, 1*8(sp)
+                .set n, 3
+                .rept 29
+                    SAVE %n
+                    .set n, n+1
+                .endr
+            .endm
+
+            .macro LOAD n
+                ld x\n, \n*8(sp)
+            .endm
+            .macro LOAD_ALL
+                ld x1, 1*8(sp)
+                .set n, 3
+                .rept 29
+                    LOAD %n
+                    .set n, n+1
+                .endr
+            .endm
+        ",
+        // 位置无关加载
+        "   .option push
+            .option nopic
+        ",
+        // 保存调度上下文
+        "   addi sp, sp, -32*8
+            SAVE_ALL
+        ",
+        // 设置陷入入口
+        "   la   t0, 1f
+            csrw stvec, t0
+        ",
+        // 保存调度上下文地址并切换上下文
+        "   csrr t0, sscratch
+            sd   sp, (t0)
+            mv   sp, t0
+        ",
+        // 恢复线程上下文
+        "   LOAD_ALL
+            ld   sp, 2*8(sp)
+        ",
+        // 执行线程
+        "   sret",
+        // 陷入
+        "   .align 2",
+        // 切换上下文
+        "1: csrrw sp, sscratch, sp",
+        // 保存线程上下文
+        "   SAVE_ALL
+            csrrw t0, sscratch, sp
+            sd    t0, 2*8(sp)
+        ",
+        // 切换上下文
+        "   ld sp, (sp)",
+        // 恢复调度上下文
+        "   LOAD_ALL
+            addi sp, sp, 32*8
+        ",
+        // 返回调度
+        "   ret",
+        "   .option pop",
+        options(noreturn)
+    )
+}

+ 94 - 0
sbi-testing/src/time.rs

@@ -0,0 +1,94 @@
+//! Timer programmer extension test suite.
+
+use crate::thread::Thread;
+use riscv::register::scause::{self, Trap};
+
+/// Timer programmer extension test cases.
+#[derive(Clone, Debug)]
+pub enum Case {
+    /// Can't procceed test for Timer extension does not exist.
+    NotExist,
+    /// Test begin.
+    Begin,
+    /// Test process for time interval overhead between two reads.
+    Interval {
+        /// The time counter value for the first read.
+        begin: u64,
+        /// The time counter value for the second read.
+        end: u64,
+    },
+    /// Test failed for can't read `time` register.
+    ReadFailed,
+    /// Test failed for time counter has decreased during period of two reads.
+    TimeDecreased {
+        /// The time counter value for the first read.
+        a: u64,
+        /// The time counter value for the second read.
+        b: u64,
+    },
+    /// Test process for timer has been set.
+    SetTimer,
+    /// Test failed for unexpected trap during timer test.
+    UnexpectedTrap(Trap),
+    /// All test cases on timer extension has passed.
+    Pass,
+}
+
+/// Test timer extension.
+pub fn test(delay: u64, mut f: impl FnMut(Case)) {
+    use riscv::register::{scause::Interrupt, sie, time};
+
+    if sbi::probe_extension(sbi::Timer).is_unavailable() {
+        f(Case::NotExist);
+        return;
+    }
+    f(Case::Begin);
+    let begin: u64;
+    let end: u64;
+    let mut ok = 0xffusize;
+    unsafe {
+        core::arch::asm!(
+            "   la   {stvec}, 1f
+                csrw stvec,   {stvec}
+                csrr {begin}, time
+                csrr {end},   time
+                mv   {ok},    zero
+            .align 2
+            1:
+            ",
+            stvec = out(reg) _,
+            begin = out(reg) begin,
+            end   = out(reg) end,
+            ok    = inlateout(reg) ok,
+        );
+    }
+    if ok != 0 {
+        f(Case::ReadFailed);
+        return;
+    }
+    if begin >= end {
+        f(Case::TimeDecreased { a: begin, b: end });
+        return;
+    }
+    f(Case::Interval { begin, end });
+
+    let mut stack = [0usize; 32];
+    let mut thread = Thread::new(riscv::asm::wfi as _);
+    *thread.sp_mut() = stack.as_mut_ptr_range().end as _;
+
+    sbi::set_timer(time::read64() + delay);
+    unsafe {
+        sie::set_stimer();
+        thread.execute();
+    }
+    match scause::read().cause() {
+        Trap::Interrupt(Interrupt::SupervisorTimer) => {
+            sbi::set_timer(u64::MAX);
+            f(Case::SetTimer);
+            f(Case::Pass);
+        }
+        trap => {
+            f(Case::UnexpectedTrap(trap));
+        }
+    }
+}