Browse Source

feat: enhance documentation and generic support for SBI masks (#102)

* fix(prototyper): update dependency `riscv` to 0.12.1 for test and bench kernels

Signed-off-by: Zhouqi Jiang <[email protected]>

* feat(spec): add special constant `V1_0` and `V2_0` for structure `Version`

Signed-off-by: Zhouqi Jiang <[email protected]>

* feat(spec): make HartMask and CounterMask generic over SBI registers

Defaults to `usize` to keep comptability.

Signed-off-by: Zhouqi Jiang <[email protected]>

* doc(rt): update crate-level documentation to specify that all SBI call functions return `SbiRet<usize>`

Signed-off-by: Zhouqi Jiang <[email protected]>

* feat(spec): add an example on non-usize `HartMask` structure

Signed-off-by: Zhouqi Jiang <[email protected]>

* doc(rustsbi): alter link to Prototyper firmware in documentation

Signed-off-by: Zhouqi Jiang <[email protected]>

---------

Signed-off-by: Zhouqi Jiang <[email protected]>
Luo Jia / Zhouqi Jiang 1 month ago
parent
commit
9966a8e526

+ 1 - 0
library/rustsbi/CHANGELOG.md

@@ -21,6 +21,7 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 - Migrate sbi-rt crate to Rust 2024 edition.
 - susp: amend documentation on `system_suspend` function.
 - lib: replace map+unwrap_or with Option::map_or in impls
+- doc: lib: alter link to Prototyper firmware in documentation.
 
 ### Removed
 

+ 4 - 4
library/rustsbi/src/lib.rs

@@ -1,7 +1,7 @@
 //! A minimal RISC-V's SBI implementation library in Rust.
 //!
 //! *Note: If you are a user looking for binary distribution download for RustSBI, you may consider
-//! using the [RustSBI Prototyper](https://github.com/rustsbi/prototyper)
+//! using the [RustSBI Prototyper](https://github.com/rustsbi/rustsbi/tree/main/prototyper)
 //! which will provide binaries for each platform.
 //! If you are a vendor or contributor who wants to adapt RustSBI to your new product or board,
 //! you may consider adapting the Prototyper first to get your board adapted in a short period of time;
@@ -127,7 +127,7 @@
 //! It provides useful custom features such as Penglai TEE, DramForever's emulated hypervisor extension,
 //! and Raven the firmware debugger framework.
 //!
-//! You may find further documents on [RustSBI Prototyper repository](https://github.com/rustsbi/prototyper).
+//! You may find further documents on [RustSBI Prototyper module](https://github.com/rustsbi/rustsbi/tree/main/prototyper).
 //!
 //! ## Discrete RustSBI package on bare metal RISC-V hardware
 //!
@@ -488,8 +488,8 @@
 //! The RustSBI Prototyper is a universal support package provided by RustSBI ecosystem.
 //! It is designed to save development time while providing most SBI features possible.
 //! It also includes a simple test kernel to allow testing SBI implementations on current environment.
-//! Users may choose to download from [Prototyper repository](https://github.com/rustsbi/prototyper)
-//! to get various types of RustSBI packages for their boards.
+//! Users may choose to download the [RustSBI Prototyper](https://github.com/rustsbi/rustsbi/tree/main/prototyper)
+//! firmware to get various types of RustSBI packages for their boards.
 //! Vendors and contributors may find it easy to adapt new SoCs and boards into the Prototyper.
 //!
 //! Discrete SBI packages are SBI environment support packages specially designed for one board

+ 1 - 0
library/sbi-rt/CHANGELOG.md

@@ -17,6 +17,7 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
 - pmu: change `counter_idx_mask` and `counter_idx_base` parameters into `counter_idx` with type `CounterMask`.
 - Migrate sbi-rt crate to Rust 2024 edition.
+- doc: update crate-level documentation to specify that all SBI call functions return `SbiRet<usize>`.
 
 ### Fixed
 

+ 11 - 1
library/sbi-rt/src/lib.rs

@@ -1,4 +1,14 @@
-//! Simple RISC-V SBI runtime primitives.
+//! RISC-V SBI runtime primitives library.
+//!
+//! `sbi-rt` provides fundamental runtime primitives for the RISC-V Supervisor Binary
+//! Interface (SBI), wrapping low-level SBI calls in safe Rust interfaces that return
+//! `SbiRet` results.
+//!
+//! All the `SbiRet` types returned by SBI call functions of this crate are `SbiRet<usize>`,
+//! representing the pointer width of the current RISC-V SBI platform.
+//! Those calls only works at RISC-V targets when building supervisor software
+//! (e.g. kernels or hypervisors); it builds under non-RISC-V targets but for tests
+//! or `cargo fix` purposes only.
 #![no_std]
 #[cfg_attr(not(feature = "legacy"), deny(missing_docs))]
 // §3

+ 2 - 0
library/sbi-spec/CHANGELOG.md

@@ -20,6 +20,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
 - examples: simple RV128I emulator example
 - examples: an SBI version example for usage of the Version structure
 - base: add special constant `V1_0` and `V2_0` for structure `Version`
+- examples: add an example on non-usize `HartMask` structure
 
 ### Modified
 
@@ -27,6 +28,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
 - base: don't derive `PartialOrd` for `Version`, instead manually implement `Ord` and forward it into `PartialOrd`.
 - base: refactor `SbiRet` to be generic of registers and introduce the `SbiRegister` trait
 - base: implement `SbiRegister` for `i32`, `i64`, `i128` and `isize` primitive types
+- base: make HartMask and CounterMask generic over SBI registers
 
 ### Fixed
 

+ 149 - 0
library/sbi-spec/examples/sbi-hart-mask.rs

@@ -0,0 +1,149 @@
+//! This example demonstrates how the `HartMask` structure operates in a non-`usize` environment.
+//! It simulates a 128-bit RISC-V SBI environment where SBI calls only accept 128-bit parameters.
+//! To represent a 128-bit SBI hart mask, we use the `HartMask<u128>` type to complete the SBI call procedure.
+
+use sbi_spec::binary::HartMask;
+use std::{
+    sync::Mutex,
+    thread::{self, JoinHandle},
+    time::Duration,
+};
+
+/// Number of simulated hardware threads (harts) in the environment.
+const N_THREADS: usize = 8;
+
+/// Array of thread handles wrapped in a `Mutex` for safe concurrent access.
+/// Each element is an `Option<JoinHandle<()>>`, initialized to `None`.
+static THREADS: [Mutex<Option<JoinHandle<()>>>; N_THREADS] =
+    [const { Mutex::new(None) }; N_THREADS];
+
+fn main() {
+    emulation_init(); // Initialize the simulated SBI environment.
+    primary_hart_main(); // Execute the primary hart's logic.
+    emulation_finish(); // Clean up the emulation environment.
+}
+
+/// Simulates the main logic executed by the primary hart.
+fn primary_hart_main() {
+    println!("Primary hart is starting");
+
+    // Send an Inter-Processor Interrupt (IPI) to all secondary harts.
+    // On a 128-bit RISC-V SBI platform, the `send_ipi` function only accepts
+    // `hart_mask` parameters of type `HartMask<u128>`.
+    sbi::send_ipi(HartMask::all());
+
+    println!("Primary hart finished");
+}
+
+/// Simulates the main logic executed by a secondary hart.
+fn secondary_hart_main(hart_id: u128) {
+    println!("Secondary hart {} is waiting for interrupt", hart_id);
+
+    // Simulate the "Wait For Interrupt" (WFI) operation.
+    // In a real-world scenario, supervisor software might also use the SBI `hart_suspend` function instead.
+    wfi();
+
+    // If the secondary harts are woken up by the SBI `send_ipi` function, execution resumes here.
+    println!("Secondary hart {} received interrupt", hart_id);
+}
+
+/* -- Implementation of a mock SBI runtime -- */
+
+mod sbi {
+    use super::{N_THREADS, unpark_thread};
+    use sbi_spec::binary::{HartMask, SbiRet};
+
+    /// Mock function to send an IPI to harts specified in the `hart_mask`.
+    pub fn send_ipi(hart_mask: HartMask<u128>) -> SbiRet<u128> {
+        let (mask, base) = hart_mask.into_inner();
+
+        // If the `hart_mask` specifies all harts, wake up all threads.
+        if hart_mask == HartMask::all() {
+            for hart_id in 0..N_THREADS as u128 {
+                unpark_thread(hart_id);
+            }
+            return SbiRet::success(0);
+        }
+
+        // Or, iterate through each bit in the mask to determine which harts to wake up.
+        for bit_offset in 0..128 {
+            if (mask & (1 << bit_offset)) != 0 {
+                let hart_id = base + bit_offset;
+                println!("Hart id {}", hart_id);
+                if hart_id < N_THREADS as u128 {
+                    unpark_thread(hart_id);
+                }
+            }
+        }
+
+        SbiRet::success(0)
+    }
+}
+
+/// Initializes the emulation environment by spawning secondary hart threads.
+fn emulation_init() {
+    println!("Emulation start");
+
+    // Spawn a thread for each secondary hart.
+    for i in 0..N_THREADS {
+        *THREADS[i].lock().unwrap() = Some(thread::spawn(move || secondary_hart_main(i as u128)));
+    }
+
+    // Add a short delay to ensure all threads are properly initialized before the primary hart starts.
+    thread::sleep(Duration::from_micros(10));
+}
+
+/// Simulates the "Wait For Interrupt" (WFI) operation.
+fn wfi() {
+    thread::park(); // Blocks the current thread until it is unparked by another thread.
+}
+
+/// Cleans up the emulation environment by stopping all secondary harts.
+fn emulation_finish() {
+    // Add a short delay to ensure all threads have completed their tasks.
+    thread::sleep(Duration::from_micros(10));
+
+    // Iterate through all threads, stop them, and wait for their completion.
+    for (i, thread) in THREADS.iter().enumerate() {
+        if let Some(thread) = thread.lock().unwrap().take() {
+            println!("Hart {} stopped", i);
+            thread.join().unwrap(); // Wait for the thread to finish execution.
+        }
+    }
+    println!("All harts stopped, emulation finished");
+}
+
+/// Unparks (wakes up) a specific hart by its ID.
+fn unpark_thread(id: u128) {
+    assert!(id < N_THREADS as u128, "Invalid hart ID");
+
+    // Safely access the thread handle and unpark the thread if it exists.
+    if let Some(thread) = &*THREADS[id as usize].lock().unwrap() {
+        thread.thread().unpark(); // Resumes execution of the parked thread.
+    }
+}
+
+/* Code execution result analysis:
+   The primary hart sends an IPI to all secondary harts using `HartMask::all()`, which
+   represents a mask where all bits are set to 1. This triggers the `send_ipi` function
+   to wake up all secondary harts. As a result, the output will be:
+   - Primary hart is starting
+   - Secondary hart 0 is waiting for interrupt
+   - Secondary hart 1 is waiting for interrupt
+   - ...
+   - Secondary hart 7 is waiting for interrupt
+   - Secondary hart 0 received interrupt
+   - Secondary hart 1 received interrupt
+   - ...
+   - Secondary hart 7 received interrupt
+   - Primary hart finished
+   - Hart 0 stopped
+   - Hart 1 stopped
+   - ...
+   - Hart 7 stopped
+   - All harts stopped, emulation finished
+
+   To test a scenario where only specific harts receive the IPI, modify the `send_ipi`
+   call to use a custom `HartMask` with specific bits set. For example:
+   `HartMask::from_raw(0b1010, 0)` would wake up harts with IDs 1 and 3.
+*/

+ 2 - 2
library/sbi-spec/examples/sbi-version.rs

@@ -1,5 +1,5 @@
-/// This example illustrates how to use the `Version` structure which represents a valid
-/// RISC-V SBI version.
+//! This example illustrates how to use the `Version` structure which represents a valid
+//! RISC-V SBI version.
 
 /// Import the version type defined in the SBI specification, used for representing and
 /// manipulating SBI version numbers.

+ 2 - 2
library/sbi-spec/examples/simple-rv128i-emulator.rs

@@ -284,9 +284,9 @@ impl<const BASE: usize, const N_INSNS: usize> InstMemory<BASE, N_INSNS> {
     ///
     /// # Parameters
     /// - `offset`: The byte offset at which to place the ecall instruction.
-    pub fn ecall(&mut self, offset: usize) {
+    pub fn ecall(&mut self, idx: usize) {
         let word = 0b000000000000_00000_000_00000_1110011;
-        self.inner[offset / 4] = word;
+        self.inner[idx / 4] = word;
     }
 
     /// Retrieve an instruction word from instruction memory based on the given pointer.

+ 50 - 19
library/sbi-spec/src/binary.rs

@@ -60,7 +60,7 @@ pub const RET_ERR_IO: usize = <usize as SbiRegister>::RET_ERR_IO;
 /// # Examples
 ///
 /// Implemented automatically for all types that satisfy `Copy`, `Eq`, and `Debug`.
-pub trait SbiRegister: Copy + Eq + core::fmt::Debug {
+pub trait SbiRegister: Copy + Eq + Ord + core::fmt::Debug {
     /// SBI success state return value.
     const RET_SUCCESS: Self;
     /// Error for SBI call failed for unknown reasons.
@@ -92,6 +92,9 @@ pub trait SbiRegister: Copy + Eq + core::fmt::Debug {
 
     /// Zero value for this type; this is used on `value` fields once `SbiRet` returns an error.
     const ZERO: Self;
+    /// Full-ones value for this type; this is used on SBI mask structures like `CounterMask`
+    /// and `HartMask`.
+    const FULL_MASK: Self;
 
     /// Converts an `SbiRet` of this type to a `Result` of self and `Error`.
     fn into_result(ret: SbiRet<Self>) -> Result<Self, Error<Self>>;
@@ -115,6 +118,7 @@ macro_rules! impl_sbi_register {
             const RET_ERR_TIMEOUT: Self = -12 as $signed as $ty;
             const RET_ERR_IO: Self = -13 as $signed as $ty;
             const ZERO: Self = 0;
+            const FULL_MASK: Self = !0;
 
             fn into_result(ret: SbiRet<Self>) -> Result<Self, Error<Self>> {
                 match ret.error {
@@ -1111,18 +1115,18 @@ pub(crate) const fn has_bit(mask: usize, base: usize, ignore: usize, bit: usize)
 /// Hart mask structure in SBI function calls.
 #[repr(C)]
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub struct HartMask {
-    hart_mask: usize,
-    hart_mask_base: usize,
+pub struct HartMask<T = usize> {
+    hart_mask: T,
+    hart_mask_base: T,
 }
 
-impl HartMask {
+impl<T: SbiRegister> HartMask<T> {
     /// Special value to ignore the `mask`, and consider all `bit`s as set.
-    pub const IGNORE_MASK: usize = usize::MAX;
+    pub const IGNORE_MASK: T = T::FULL_MASK;
 
     /// Construct a [HartMask] from mask value and base hart id.
     #[inline]
-    pub const fn from_mask_base(hart_mask: usize, hart_mask_base: usize) -> Self {
+    pub const fn from_mask_base(hart_mask: T, hart_mask_base: T) -> Self {
         Self {
             hart_mask,
             hart_mask_base,
@@ -1138,23 +1142,27 @@ impl HartMask {
     #[inline]
     pub const fn all() -> Self {
         Self {
-            hart_mask: 0,
-            hart_mask_base: usize::MAX,
+            hart_mask: T::ZERO,
+            hart_mask_base: T::FULL_MASK,
         }
     }
 
     /// Gets the special value for ignoring the `mask` parameter.
     #[inline]
-    pub const fn ignore_mask(&self) -> usize {
+    pub const fn ignore_mask(&self) -> T {
         Self::IGNORE_MASK
     }
 
     /// Returns `mask` and `base` parameters from the [HartMask].
     #[inline]
-    pub const fn into_inner(self) -> (usize, usize) {
+    pub const fn into_inner(self) -> (T, T) {
         (self.hart_mask, self.hart_mask_base)
     }
+}
 
+// FIXME: implement for T: SbiRegister once we can implement this using const traits.
+// Ref: https://rust-lang.github.io/rust-project-goals/2024h2/const-traits.html
+impl HartMask<usize> {
     /// Returns whether the [HartMask] contains the provided `hart_id`.
     #[inline]
     pub const fn has_bit(self, hart_id: usize) -> bool {
@@ -1349,18 +1357,18 @@ pub enum MaskError {
 /// Counter index mask structure in SBI function calls for the `PMU` extension §11.
 #[repr(C)]
 #[derive(Debug, Copy, Clone, Eq, PartialEq)]
-pub struct CounterMask {
-    counter_idx_mask: usize,
-    counter_idx_base: usize,
+pub struct CounterMask<T = usize> {
+    counter_idx_mask: T,
+    counter_idx_base: T,
 }
 
-impl CounterMask {
+impl<T: SbiRegister> CounterMask<T> {
     /// Special value to ignore the `mask`, and consider all `bit`s as set.
-    pub const IGNORE_MASK: usize = usize::MAX;
+    pub const IGNORE_MASK: T = T::FULL_MASK;
 
     /// Construct a [CounterMask] from mask value and base counter index.
     #[inline]
-    pub const fn from_mask_base(counter_idx_mask: usize, counter_idx_base: usize) -> Self {
+    pub const fn from_mask_base(counter_idx_mask: T, counter_idx_base: T) -> Self {
         Self {
             counter_idx_mask,
             counter_idx_base,
@@ -1369,16 +1377,20 @@ impl CounterMask {
 
     /// Gets the special value for ignoring the `mask` parameter.
     #[inline]
-    pub const fn ignore_mask(&self) -> usize {
+    pub const fn ignore_mask(&self) -> T {
         Self::IGNORE_MASK
     }
 
     /// Returns `mask` and `base` parameters from the [CounterMask].
     #[inline]
-    pub const fn into_inner(self) -> (usize, usize) {
+    pub const fn into_inner(self) -> (T, T) {
         (self.counter_idx_mask, self.counter_idx_base)
     }
+}
 
+// FIXME: implement for T: SbiRegister once we can implement this using const traits.
+// Ref: https://rust-lang.github.io/rust-project-goals/2024h2/const-traits.html
+impl CounterMask<usize> {
     /// Returns whether the [CounterMask] contains the provided `counter`.
     #[inline]
     pub const fn has_bit(self, counter: usize) -> bool {
@@ -1861,4 +1873,23 @@ mod tests {
         });
         assert!(mask.has_bit(usize::MAX));
     }
+
+    #[test]
+    fn rustsbi_mask_non_usize() {
+        assert_eq!(CounterMask::<i32>::IGNORE_MASK, -1);
+        assert_eq!(CounterMask::<i64>::IGNORE_MASK, -1);
+        assert_eq!(CounterMask::<i128>::IGNORE_MASK, -1);
+        assert_eq!(CounterMask::<u32>::IGNORE_MASK, u32::MAX);
+        assert_eq!(CounterMask::<u64>::IGNORE_MASK, u64::MAX);
+        assert_eq!(CounterMask::<u128>::IGNORE_MASK, u128::MAX);
+
+        assert_eq!(HartMask::<i32>::IGNORE_MASK, -1);
+        assert_eq!(HartMask::<i64>::IGNORE_MASK, -1);
+        assert_eq!(HartMask::<i128>::IGNORE_MASK, -1);
+        assert_eq!(HartMask::<u32>::IGNORE_MASK, u32::MAX);
+        assert_eq!(HartMask::<u64>::IGNORE_MASK, u64::MAX);
+        assert_eq!(HartMask::<u128>::IGNORE_MASK, u128::MAX);
+
+        assert_eq!(HartMask::<i32>::all(), HartMask::from_mask_base(0, -1));
+    }
 }