Browse Source

Merge pull request #94 from luojia65/feat/sbiret-example

feat(spec): add simple RV128I emulator example to illustrate example of non-usize SbiRet
Luo Jia / Zhouqi Jiang 1 month ago
parent
commit
7d75145fde

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

@@ -17,6 +17,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
 - binary: `impl From<Error> for SbiRet`, `impl IntoIterator for SbiRet`
 - binary: unsafe functions `SbiRet::{unwrap_unchecked, unwrap_err_unchecked}`
 - binary: internal unit tests for `SbiRet` constructors
+- examples: simple RV128I emulator example
+- examples: an SBI version example for usage of the Version structure
 
 ### Modified
 

+ 58 - 0
library/sbi-spec/examples/sbi-version.rs

@@ -0,0 +1,58 @@
+/// 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.
+use sbi_spec::base::Version;
+
+/// We mock a S-mode software (kernel, etc.) that runs on minimum SBI version of v1.0;
+/// it will detect from the environment and judge if it meets the minimum SBI version demand.
+fn main() {
+    // Create a version number representing RISC-V SBI v1.0.
+    let v1_0 = Version::from_raw(0x100_0000);
+
+    // Call the mock SBI runtime to obtain the current version number.
+    println!("Probing SBI version of current environment...");
+    let current_version = sbi::get_spec_version();
+
+    // Print the detected SBI version.
+    println!("Kernel running on SBI version {}", current_version);
+
+    // Version comparison: Check whether the current version meets the minimum
+    // requirement (v1.0 or higher).
+    if current_version >= v1_0 {
+        // The version meets the requirement, output success message.
+        println!("The SBI version meets minimum demand of RISC-V SBI v1.0.");
+        println!("✓ Test success!");
+    } else {
+        // The version is too low, output failure message.
+        println!("✗ Test failed, SBI version is too low.");
+    }
+}
+
+/* -- Implementation of a mock SBI runtime -- */
+
+/// Module simulating an SBI runtime for the test environment.
+mod sbi {
+    use sbi_spec::base::Version;
+
+    /// Mock function to retrieve the SBI specification version.
+    pub fn get_spec_version() -> Version {
+        // Return a hardcoded version number `0x0200_0000`.
+        // Using the parsing rule from the RISC-V SBI Specification, this represents major
+        // version 2, minor version 0 (i.e., 2.0).
+        // In a real environment, this function should interact with the SBI implementation
+        // via ECALL to obtain the actual version.
+        Version::from_raw(0x0200_0000)
+    }
+}
+
+/* Code execution result analysis:
+   The current simulated SBI version is 2.0, which is higher than the minimum requirement
+   of 1.0, so the output will be:
+   ✓ Test success!
+
+   To test a failure scenario, modify the mock return value to a version lower than 1.0,
+   for example:
+   `Version::from_raw(0x0000_0002)` represents 0.2.
+*/

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

@@ -0,0 +1,548 @@
+/// This example illustrates how to use non-usize SBI register values on emulators.
+///
+/// We take RISC-V RV128I as an example, since as of now (2025 CE) there are almost no common host
+/// platforms that support 128-bit pointer width. This example shows how to write an emulator whose
+/// pointer width differs from that of the host platform.
+///
+/// The emulator starts in S-mode, allowing the emulated supervisor software to make SBI calls via
+/// the `ecall` instruction.
+use XReg::*;
+use core::ops::ControlFlow;
+use sbi_spec::binary::SbiRet;
+
+/// Represents a simple RV128I hardware thread (hart) emulator.
+///
+/// The hart contains:
+/// - A set of 32 general-purpose registers (each 128-bit wide)
+/// - A program counter (PC) also 128-bit wide
+/// - An instruction memory that holds the binary instructions to execute
+pub struct SimpleRv128IHart {
+    xregs: [u128; 32],
+    pc: u128,
+    inst_memory: InstMemory<0x2000_0000, { 0x4000 / 4 }>,
+}
+
+/// The main function that sets up the instruction memory, runs the emulator, and prints the result.
+fn main() {
+    // Create a new instruction memory with all instructions unimplemented (zeroed out)
+    let mut memory = InstMemory::new_unimp();
+
+    // --- Build a simple program in instruction memory ---
+    // Call SBI probe_extension to probe the BASE extension which always exists
+    memory.li(0x0, A0, 0x10);
+    memory.li(0x4, A6, 3);
+    memory.li(0x8, A7, 0x10);
+    memory.ecall(0xC);
+    // Judge if the SBI call result is zero; if non-zero, jump to emulation failed.
+    memory.beqz(0x10, A1, 0x1C);
+    // Emulation success, call SBI system_reset to shutdown emulation
+    memory.li(0x14, A0, 0x0);
+    memory.li(0x18, A1, 0x0);
+    memory.li(0x1C, A6, 0x0);
+    memory.li(0x20, A7, 0x53525354);
+    memory.ecall(0x28);
+    // Emulation failed, call SBI system_reset with SYSTEM_FAILURE to shutdown emulation
+    memory.li(0x2C, A0, 0x0);
+    memory.li(0x30, A1, 0x1);
+    memory.li(0x34, A6, 0x0);
+    memory.li(0x38, A7, 0x53525354);
+    memory.ecall(0x44);
+
+    // --- Initialize the emulator hart ---
+    let mut hart = SimpleRv128IHart::new(memory);
+    println!("Starting SimpleRv128IHart...");
+
+    // Run the emulation loop, executing one instruction at a time.
+    // The loop breaks when an SBI call requests to shutdown the emulator (returns a special error value).
+    let emulation_result = loop {
+        match hart.stepi() {
+            Ok(()) => {} // Instruction executed normally; continue to the next one.
+            Err(Exception::SupervisorEcall) => match handle_ecall(&mut hart) {
+                // If the SBI call indicates a shutdown (with a special error value), break out of the loop.
+                ControlFlow::Break(value) => break value,
+                // Otherwise, continue execution.
+                ControlFlow::Continue(()) => continue,
+            },
+            // Any other exception is considered unexpected, so we print an error and terminate.
+            Err(e) => {
+                println!("Emulation failed for unexpected exception: {:?}", e);
+                return;
+            }
+        }
+    };
+
+    // Print the final result of the emulation.
+    println!("Emulation finished. Result: {}", emulation_result);
+    if emulation_result == 0 {
+        println!("✓ Test success!");
+    } else {
+        println!("✗ Test failed, emulator returns {}", emulation_result);
+    }
+}
+
+/// Handle an SBI call given the extension and function numbers, along with its parameters.
+///
+/// This is a simple SBI implementation (without using RustSBI) that supports a few functions:
+/// - BASE probe_extension: checks if an SBI extension exists.
+/// - SRST system_reset: performs a system reset (shutdown).
+///
+/// Note that the returned `SbiRet<u128>` represents an `SbiRet` with `u128` as the SBI register
+/// type.
+///
+/// # Parameters
+/// - `extension`: The SBI extension identifier (from register A7).
+/// - `function`: The SBI function number (from register A6).
+/// - `param`: An array containing SBI call parameters (from registers A0-A5).
+///
+/// # Returns
+/// An `SbiRet` structure containing the error and return values.
+fn handle_sbi_call(extension: u128, function: u128, param: [u128; 6]) -> SbiRet<u128> {
+    match (extension, function) {
+        // BASE probe_extension: if the parameter matches the BASE extension identifier, return 1.
+        (0x10, 3) => {
+            if param[0] == 0x10 {
+                SbiRet::success(1)
+            } else {
+                SbiRet::success(0)
+            }
+        }
+        // SRST system_reset: perform a system reset if the reset type is shutdown.
+        (0x53525354, 0) => {
+            let (reset_type, reset_reason) = (param[0], param[1]);
+            if reset_type == sbi_spec::srst::RESET_TYPE_SHUTDOWN as u128 {
+                // Use a special SBI error value (0x114514) to signal platform shutdown.
+                SbiRet {
+                    value: reset_reason,
+                    error: 0x114514,
+                }
+            } else {
+                SbiRet::not_supported()
+            }
+        }
+        // All other SBI calls are not supported.
+        _ => SbiRet::not_supported(),
+    }
+}
+
+/* -- Implementations of SimpleRv128Platform -- */
+
+/// Handle the supervisor call (ecall) exception by performing an SBI call.
+///
+/// This function extracts the parameters from the hart's registers, performs the SBI call, and
+/// then updates the hart's registers and program counter with the results.
+///
+/// # Parameters
+/// - `hart`: A mutable reference to the RV128I hart emulator.
+///
+/// # Returns
+/// - `ControlFlow::Break(value)` if the SBI call indicates that the platform should shutdown.
+/// - `ControlFlow::Continue(())` if the emulation should continue.
+fn handle_ecall(hart: &mut SimpleRv128IHart) -> ControlFlow<u128> {
+    println!("Handle ecall, registers: {:x?}", hart.xregs);
+    // Extract SBI call parameters from registers A0-A5.
+    let param = [
+        hart.xregs[A0 as usize],
+        hart.xregs[A1 as usize],
+        hart.xregs[A2 as usize],
+        hart.xregs[A3 as usize],
+        hart.xregs[A4 as usize],
+        hart.xregs[A5 as usize],
+    ];
+    // Call the SBI handler with the extension and function numbers from registers A7 and A6.
+    let ret = handle_sbi_call(hart.xregs[A7 as usize], hart.xregs[A6 as usize], param);
+    println!("SbiRet: {:?}", ret);
+    // If the SBI call returns the special error value (0x114514), signal shutdown.
+    if ret.error == 0x114514 {
+        return ControlFlow::Break(ret.value);
+    }
+    // Otherwise, store the error and return values into registers A0 and A1, respectively.
+    hart.xregs[A0 as usize] = ret.error;
+    hart.xregs[A1 as usize] = ret.value;
+    // Advance the program counter past the ecall instruction.
+    hart.pc = hart.pc.wrapping_add(4);
+    ControlFlow::Continue(())
+}
+
+/// An instruction memory implementation that holds a fixed number of instructions.
+///
+/// `BASE` defines the starting memory address, and `N_INSNS` is the number of 32-bit words.
+pub struct InstMemory<const BASE: usize, const N_INSNS: usize> {
+    inner: [u32; N_INSNS],
+}
+
+/// Opcode and function constant definitions for a simplified RISC-V subset.
+const OPCODE_OP_IMM: u32 = 0b001_0011;
+const OPCODE_LUI: u32 = 0b011_0111;
+const OPCODE_BRANCH: u32 = 0b110_0011;
+const FUNCT3_OP_ADD_SUB: u32 = 0b000;
+const FUNCT3_BRANCH_BEQ: u32 = 0b000;
+
+impl<const BASE: usize, const N_INSNS: usize> InstMemory<BASE, N_INSNS> {
+    /// Creates a new instance of instruction memory with all instructions set to unimplemented (zero).
+    pub fn new_unimp() -> Self {
+        Self {
+            inner: [0; N_INSNS],
+        }
+    }
+
+    /// Assemble an ADDI instruction and store it at the given memory index.
+    ///
+    /// # Parameters
+    /// - `idx`: The byte offset at which to place the instruction.
+    /// - `rd`: The destination register.
+    /// - `rs`: The source register.
+    /// - `simm12`: The 12-bit signed immediate.
+    pub fn addi(&mut self, idx: usize, rd: XReg, rs: XReg, simm12: impl Into<Simm12>) {
+        let funct3 = FUNCT3_OP_ADD_SUB;
+        let opcode = OPCODE_OP_IMM;
+        let word = (u32::from(simm12.into().0) << 20)
+            | ((rs as u32) << 15)
+            | (funct3 << 12)
+            | ((rd as u32) << 7)
+            | opcode;
+        self.inner[idx / 4] = word;
+    }
+
+    /// Assemble a LUI (Load Upper Immediate) instruction and store it at the given memory index.
+    ///
+    /// # Parameters
+    /// - `idx`: The byte offset at which to place the instruction.
+    /// - `rd`: The destination register.
+    /// - `simm20`: The 20-bit immediate value.
+    pub fn lui(&mut self, idx: usize, rd: XReg, simm20: impl Into<Simm20>) {
+        let opcode = OPCODE_LUI;
+        let word = (u32::from(simm20.into().0) << 12) | ((rd as u32) << 7) | opcode;
+        self.inner[idx / 4] = word;
+    }
+
+    /// Load an immediate value into a register.
+    ///
+    /// This function will generate either a single ADDI instruction (if the upper 20 bits are zero)
+    /// or a LUI followed by an ADDI instruction.
+    ///
+    /// # Parameters
+    /// - `idx`: The byte offset at which to place the instructions.
+    /// - `rd`: The destination register.
+    /// - `imm`: The immediate value (128-bit).
+    pub fn li(&mut self, idx: usize, rd: XReg, imm: u128) {
+        assert!(
+            imm <= 0xFFFFFFFF,
+            "in this example `li` only supports immediate values less than 0xFFFFFFFF"
+        );
+        let imm = imm as u32;
+        let (simm20, simm12) = (imm >> 12, imm & 0xFFF);
+        if simm20 != 0 {
+            self.lui(idx, rd, simm20);
+            self.addi(idx + 4, rd, rd, simm12);
+        } else {
+            self.addi(idx, rd, XReg::Zero, simm12);
+        }
+    }
+
+    /// Assemble a BEQ (branch if equal) instruction and store it at the given memory index.
+    ///
+    /// # Parameters
+    /// - `idx`: The byte offset at which to place the instruction.
+    /// - `rs1`: The first source register.
+    /// - `rs2`: The second source register.
+    /// - `offset`: The branch offset.
+    pub fn beq(&mut self, idx: usize, rs1: XReg, rs2: XReg, offset: impl Into<Offset>) {
+        let opcode = OPCODE_BRANCH;
+        let funct3 = FUNCT3_BRANCH_BEQ;
+        // Convert offset into the proper bit segments for the instruction encoding.
+        let offset_u32 = u32::from_ne_bytes(i32::to_ne_bytes(offset.into().0));
+        let simm12_12 = (offset_u32 & 0b1_0000_0000_0000) >> 12;
+        let simm12_11 = (offset_u32 & 0b1000_0000_0000) >> 11;
+        let simm12_10_5 = (offset_u32 & 0b111_1110_0000) >> 5;
+        let simm12_4_1 = (offset_u32 & 0b1_1110) >> 1;
+        let word = simm12_12 << 31
+            | simm12_10_5 << 25
+            | ((rs2 as u32) << 20)
+            | ((rs1 as u32) << 15)
+            | (funct3 << 12)
+            | simm12_4_1 << 8
+            | simm12_11 << 7
+            | opcode;
+        self.inner[idx / 4] = word;
+    }
+
+    /// Assemble a BEQZ (branch if equal to zero) instruction.
+    ///
+    /// This is a special case of BEQ where the second register is hardwired to zero.
+    ///
+    /// # Parameters
+    /// - `idx`: The byte offset at which to place the instruction.
+    /// - `rs`: The register to test for zero.
+    /// - `offset`: The branch offset.
+    pub fn beqz(&mut self, idx: usize, rs: XReg, offset: impl Into<Offset>) {
+        self.beq(idx, rs, Zero, offset);
+    }
+
+    /// Assemble an ECALL instruction at the given offset.
+    ///
+    /// This instruction triggers a supervisor call exception.
+    ///
+    /// # Parameters
+    /// - `offset`: The byte offset at which to place the ecall instruction.
+    pub fn ecall(&mut self, offset: usize) {
+        let word = 0b000000000000_00000_000_00000_1110011;
+        self.inner[offset / 4] = word;
+    }
+
+    /// Retrieve an instruction word from instruction memory based on the given pointer.
+    ///
+    /// Returns `None` if the pointer is not aligned or outside the allocated memory range.
+    ///
+    /// # Parameters
+    /// - `ptr`: The 128-bit address from which to fetch the instruction.
+    pub fn get(&mut self, ptr: u128) -> Option<u32> {
+        if ptr % 4 != 0 || ptr >= (BASE + 4 * N_INSNS) as u128 {
+            return None;
+        }
+        Some(self.inner[(ptr as usize - BASE) / 4])
+    }
+}
+
+impl SimpleRv128IHart {
+    /// Creates a new RV128I hart emulator with the given instruction memory.
+    ///
+    /// The hart is initialized with all registers set to zero and the program counter
+    /// set to the base address of the instruction memory.
+    pub fn new(inst_memory: InstMemory<0x2000_0000, { 0x4000 / 4 }>) -> Self {
+        Self {
+            xregs: [0; 32],
+            pc: 0x2000_0000,
+            inst_memory,
+        }
+    }
+
+    /// Execute one instruction step.
+    ///
+    /// Fetches, decodes, and executes the instruction at the current program counter (PC),
+    /// updating the PC accordingly.
+    ///
+    /// # Returns
+    /// - `Ok(())` if executed normally.
+    /// - `Err(Exception::SupervisorEcall)` if an ecall instruction was encountered.
+    /// - `Err(e)` for other exceptions.
+    pub fn stepi(&mut self) -> Result<(), Exception> {
+        let raw_insn = self
+            .inst_memory
+            .get(self.pc)
+            .ok_or(Exception::InstructionAccessFault)?;
+
+        println!("Insn at 0x{:x}: 0x{:x}", self.pc, raw_insn);
+
+        // Attempt to decode the raw instruction into one of the supported instruction variants.
+        let parsed_insn =
+            Instruction::try_from(raw_insn).map_err(|_| Exception::IllegalInstruction)?;
+
+        match parsed_insn {
+            Instruction::Addi(rd, rs, simm12) => {
+                self.xregs[rd as usize] = self.xregs[rs as usize] + simm12.0 as u128;
+                self.pc = self.pc.wrapping_add(4);
+            }
+            Instruction::Lui(rd, simm20) => {
+                self.xregs[rd as usize] = (simm20.0 as u128) << 12;
+                self.pc = self.pc.wrapping_add(4);
+            }
+            Instruction::Beq(rs1, rs2, offset) => {
+                if self.xregs[rs1 as usize] == self.xregs[rs2 as usize] {
+                    self.pc = self.pc.wrapping_add_signed(offset.0 as i128);
+                } else {
+                    self.pc = self.pc.wrapping_add(4);
+                }
+            }
+            Instruction::Ecall => return Err(Exception::SupervisorEcall),
+        }
+
+        Ok(())
+    }
+}
+
+/* -- RISC-V ISA enumerations and structures -- */
+
+/// RISC-V exceptions that may occur during emulation.
+#[derive(Debug)]
+pub enum Exception {
+    /// The instruction is illegal or not supported.
+    IllegalInstruction,
+    /// The instruction memory access failed (e.g., due to an out-of-bound address).
+    InstructionAccessFault,
+    /// An ecall was executed in supervisor mode.
+    SupervisorEcall,
+}
+
+/// Enum representing the supported instructions in our simplified RV128I emulator.
+#[derive(Debug)]
+pub enum Instruction {
+    /// ADDI instruction: rd = rs + immediate.
+    Addi(XReg, XReg, Simm12),
+    /// LUI instruction: rd = immediate << 12.
+    Lui(XReg, Simm20),
+    /// BEQ instruction: if (rs1 == rs2) branch to PC + offset.
+    Beq(XReg, XReg, Offset),
+    /// ECALL instruction to trigger a supervisor call.
+    Ecall,
+}
+
+impl TryFrom<u32> for Instruction {
+    type Error = ();
+
+    /// Attempts to decode a 32-bit word into a supported Instruction.
+    ///
+    /// Returns an error if the instruction encoding does not match any known pattern.
+    fn try_from(value: u32) -> Result<Self, Self::Error> {
+        let opcode = value & 0x7F;
+        let rd = ((value >> 7) & 0x1F).try_into().unwrap();
+        let rs1 = ((value >> 15) & 0x1F).try_into().unwrap();
+        let rs2 = ((value >> 20) & 0x1F).try_into().unwrap();
+        let funct3 = (value >> 12) & 0b111;
+        let simm12 = (value >> 20).into();
+        let simm20 = (value >> 12).into();
+        // Decode the branch offset from its scattered bit fields.
+        let offset = {
+            let offset12 = value >> 31;
+            let offset10_5 = (value >> 25) & 0x3F;
+            let offset4_1 = (value >> 8) & 0xF;
+            let offset11 = (value >> 7) & 0x1;
+            let value = (offset4_1 << 1) | (offset10_5 << 5) | (offset11 << 11) | (offset12 << 12);
+            value.into()
+        };
+        if opcode == OPCODE_OP_IMM && funct3 == FUNCT3_OP_ADD_SUB {
+            Ok(Self::Addi(rd, rs1, simm12))
+        } else if opcode == OPCODE_LUI {
+            Ok(Self::Lui(rd, simm20))
+        } else if opcode == OPCODE_BRANCH && funct3 == FUNCT3_BRANCH_BEQ {
+            Ok(Self::Beq(rs1, rs2, offset))
+        } else if value == 0b000000000000_00000_000_00000_1110011 {
+            Ok(Self::Ecall)
+        } else {
+            Err(())
+        }
+    }
+}
+
+/// Enumeration of RISC-V registers.
+///
+/// Each variant corresponds to a register name and its associated register number.
+#[derive(Clone, Copy, Debug)]
+pub enum XReg {
+    Zero = 0,
+    Ra = 1,
+    Sp = 2,
+    Gp = 3,
+    Tp = 4,
+    T0 = 5,
+    T1 = 6,
+    T2 = 7,
+    S0 = 8,
+    S1 = 9,
+    A0 = 10,
+    A1 = 11,
+    A2 = 12,
+    A3 = 13,
+    A4 = 14,
+    A5 = 15,
+    A6 = 16,
+    A7 = 17,
+    S2 = 18,
+    S3 = 19,
+    S4 = 20,
+    S5 = 21,
+    S6 = 22,
+    S7 = 23,
+    S8 = 24,
+    S9 = 25,
+    S10 = 26,
+    S11 = 27,
+    T3 = 28,
+    T4 = 29,
+    T5 = 30,
+    T6 = 31,
+}
+
+impl TryFrom<u32> for XReg {
+    type Error = ();
+
+    /// Convert a u32 into an XReg.
+    /// Returns an error if the value does not correspond to a valid register number.
+    fn try_from(value: u32) -> Result<Self, Self::Error> {
+        Ok(match value {
+            0 => Zero,
+            1 => Ra,
+            2 => Sp,
+            3 => Gp,
+            4 => Tp,
+            5 => T0,
+            6 => T1,
+            7 => T2,
+            8 => S0,
+            9 => S1,
+            10 => A0,
+            11 => A1,
+            12 => A2,
+            13 => A3,
+            14 => A4,
+            15 => A5,
+            16 => A6,
+            17 => A7,
+            18 => S2,
+            19 => S3,
+            20 => S4,
+            21 => S5,
+            22 => S6,
+            23 => S7,
+            24 => S8,
+            25 => S9,
+            26 => S10,
+            27 => S11,
+            28 => T3,
+            29 => T4,
+            30 => T5,
+            31 => T6,
+            _ => return Err(()),
+        })
+    }
+}
+
+/// A 12-bit signed immediate value used in instructions such as ADDI.
+#[derive(Clone, Copy, Debug)]
+pub struct Simm12(u16);
+
+impl From<u32> for Simm12 {
+    fn from(value: u32) -> Self {
+        Self((value & 0x0FFF) as u16)
+    }
+}
+
+/// A 20-bit immediate value used in instructions such as LUI.
+#[derive(Clone, Copy, Debug)]
+pub struct Simm20(u32);
+
+impl From<u32> for Simm20 {
+    fn from(value: u32) -> Self {
+        Self(value & 0xFFFFF)
+    }
+}
+
+/// A branch offset used in branch instructions.
+#[derive(Clone, Copy, Debug)]
+pub struct Offset(i32);
+
+impl From<i32> for Offset {
+    fn from(value: i32) -> Self {
+        Self(value & 0x1FFE)
+    }
+}
+
+impl From<u32> for Offset {
+    fn from(mut value: u32) -> Self {
+        value = value & 0x1FFE;
+        if value & 0x1000 != 0 {
+            value |= 0xFFFFE000;
+        }
+        let ans = i32::from_ne_bytes(u32::to_ne_bytes(value));
+        Self(ans)
+    }
+}

+ 5 - 0
prototyper/.cargo/config.toml

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

+ 49 - 0
prototyper/.github/workflows/workflow.yml

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

+ 2 - 0
prototyper/.gitignore

@@ -0,0 +1,2 @@
+/target
+.idea/*

+ 788 - 0
prototyper/Cargo.lock

@@ -0,0 +1,788 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "aclint"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8cc30f3f60fd3106787fa9b540e64372dd4793813c400ba12d113506e94dcb8c"
+
+[[package]]
+name = "anstream"
+version = "0.6.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "is_terminal_polyfill",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
+
+[[package]]
+name = "anstyle-parse"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
+dependencies = [
+ "windows-sys",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e"
+dependencies = [
+ "anstyle",
+ "once_cell",
+ "windows-sys",
+]
+
+[[package]]
+name = "as-slice"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516"
+dependencies = [
+ "stable_deref_trait",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
+
+[[package]]
+name = "bit_field"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61"
+
+[[package]]
+name = "bitflags"
+version = "2.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36"
+
+[[package]]
+name = "bouffalo-hal"
+version = "0.0.0"
+source = "git+https://github.com/rustsbi/bouffalo-hal?rev=968b949#968b949466adeb6773f7ca3c1052e3a400533ed9"
+dependencies = [
+ "as-slice",
+ "cfg-if",
+ "embedded-hal 0.2.7",
+ "embedded-hal 1.0.0",
+ "embedded-hal-nb",
+ "embedded-io",
+ "embedded-time",
+ "nb 1.1.0",
+ "volatile-register",
+]
+
+[[package]]
+name = "buddy_system_allocator"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1a0108968a3a2dab95b089c0fc3f1afa7759aa5ebe6f1d86d206d6f7ba726eb"
+dependencies = [
+ "spin",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "clap"
+version = "4.5.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e77c3243bd94243c03672cb5154667347c457ca271254724f9f393aee1c05ff"
+dependencies = [
+ "clap_builder",
+ "clap_derive",
+]
+
+[[package]]
+name = "clap-verbosity-flag"
+version = "3.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2678fade3b77aa3a8ff3aae87e9c008d3fb00473a41c71fbf74e91c8c7b37e84"
+dependencies = [
+ "clap",
+ "log",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.5.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b26884eb4b57140e4d2d93652abfa49498b938b3c9179f9fc487b0acc3edad7"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "clap_lex",
+ "strsim",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.5.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
+
+[[package]]
+name = "colorchoice"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
+
+[[package]]
+name = "critical-section"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
+
+[[package]]
+name = "dtb-walker"
+version = "0.2.0-alpha.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9404d41caa1aa659f7be44d5a902e318c0672900822fe9ca41d9e38c14b52332"
+
+[[package]]
+name = "embedded-hal"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff"
+dependencies = [
+ "nb 0.1.3",
+ "void",
+]
+
+[[package]]
+name = "embedded-hal"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89"
+
+[[package]]
+name = "embedded-hal-nb"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fba4268c14288c828995299e59b12babdbe170f6c6d73731af1b4648142e8605"
+dependencies = [
+ "embedded-hal 1.0.0",
+ "nb 1.1.0",
+]
+
+[[package]]
+name = "embedded-io"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d"
+
+[[package]]
+name = "embedded-time"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7a4b4d10ac48d08bfe3db7688c402baadb244721f30a77ce360bd24c3dffe58"
+dependencies = [
+ "num",
+]
+
+[[package]]
+name = "fast-trap"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46da95e6fcc7619a12d05594693e48591c0b574aef6fe5d7a7e765e6763a2cb2"
+
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
+[[package]]
+name = "is_terminal_polyfill"
+version = "1.70.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
+
+[[package]]
+name = "lock_api"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
+dependencies = [
+ "autocfg",
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
+
+[[package]]
+name = "nb"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f"
+dependencies = [
+ "nb 1.1.0",
+]
+
+[[package]]
+name = "nb"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d"
+
+[[package]]
+name = "num"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b7a8e9be5e039e2ff869df49155f1c06bd01ade2117ec783e56ab0932b67a8f"
+dependencies = [
+ "num-complex",
+ "num-integer",
+ "num-iter",
+ "num-rational",
+ "num-traits",
+]
+
+[[package]]
+name = "num-complex"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "747d632c0c558b87dbabbe6a82f3b4ae03720d0646ac5b7b4dae89394be5f2c5"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-iter"
+version = "0.1.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-rational"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.20.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e"
+
+[[package]]
+name = "panic-halt"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a513e167849a384b7f9b746e517604398518590a9142f4846a32e3c2a4de7b11"
+
+[[package]]
+name = "paste"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
+
+[[package]]
+name = "plic"
+version = "0.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ad606bf31d67b0e10a161b7df7d6a97dda7be22ce4bebcff889476e867c9b7a"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.93"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.38"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rcore-console"
+version = "0.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63aae49a6d2e6fd69821507a979b5871e4c47dc3abc9066347fa5c4a51a73dd6"
+dependencies = [
+ "log",
+ "spin",
+]
+
+[[package]]
+name = "riscv"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f5c1b8bf41ea746266cdee443d1d1e9125c86ce1447e1a2615abd34330d33a9"
+dependencies = [
+ "critical-section",
+ "embedded-hal 1.0.0",
+]
+
+[[package]]
+name = "riscv"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ea8ff73d3720bdd0a97925f0bf79ad2744b6da8ff36be3840c48ac81191d7a7"
+dependencies = [
+ "critical-section",
+ "embedded-hal 1.0.0",
+ "paste",
+ "riscv-macros",
+ "riscv-pac",
+]
+
+[[package]]
+name = "riscv-decode"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf8b4cfb0da0528321d22daee4299a23a8c5ac8848623d716e898d2a9eec0694"
+
+[[package]]
+name = "riscv-macros"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f265be5d634272320a7de94cea15c22a3bfdd4eb42eb43edc528415f066a1f25"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "riscv-pac"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8188909339ccc0c68cfb5a04648313f09621e8b87dc03095454f1a11f6c5d436"
+
+[[package]]
+name = "rustsbi"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44c13763120794ed11d64bac885fb31d384ae385c3287b0697711b97affbf8ab"
+dependencies = [
+ "riscv 0.11.1",
+ "rustsbi-macros",
+ "sbi-spec 0.0.7",
+]
+
+[[package]]
+name = "rustsbi-bench-kernel"
+version = "0.0.0"
+dependencies = [
+ "log",
+ "rcore-console",
+ "riscv 0.11.1",
+ "sbi-spec 0.0.8 (git+https://github.com/rustsbi/rustsbi?rev=4821073)",
+ "sbi-testing 0.0.3-alpha.2 (git+https://github.com/rustsbi/rustsbi?rev=4821073)",
+ "serde",
+ "serde-device-tree",
+ "spin",
+ "uart16550",
+]
+
+[[package]]
+name = "rustsbi-macros"
+version = "0.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a71347da9582cc6b6f3652c7d2c06516c9555690b3738ecdff7e84297f4e17fc"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "rustsbi-prototyper"
+version = "0.0.0"
+dependencies = [
+ "aclint",
+ "bouffalo-hal",
+ "buddy_system_allocator",
+ "cfg-if",
+ "fast-trap",
+ "log",
+ "panic-halt",
+ "riscv 0.12.1",
+ "riscv-decode",
+ "rustsbi",
+ "sbi-spec 0.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde",
+ "serde-device-tree",
+ "sifive-test-device",
+ "spin",
+ "uart16550",
+ "uart_xilinx",
+ "xuantie-riscv",
+]
+
+[[package]]
+name = "rustsbi-test-kernel"
+version = "0.0.0"
+dependencies = [
+ "dtb-walker",
+ "log",
+ "rcore-console",
+ "riscv 0.11.1",
+ "sbi-testing 0.0.3-alpha.2 (git+https://github.com/rustsbi/rustsbi)",
+ "spin",
+ "uart16550",
+]
+
+[[package]]
+name = "sbi-rt"
+version = "0.0.3"
+source = "git+https://github.com/rustsbi/rustsbi?rev=4821073#4821073b56a7223781c11a49aba743785d89d3ea"
+dependencies = [
+ "sbi-spec 0.0.8 (git+https://github.com/rustsbi/rustsbi?rev=4821073)",
+]
+
+[[package]]
+name = "sbi-rt"
+version = "0.0.3"
+source = "git+https://github.com/rustsbi/rustsbi#99f4177fbed12c96c2c62121d51953b1bfa0ff43"
+dependencies = [
+ "sbi-spec 0.0.8 (git+https://github.com/rustsbi/rustsbi)",
+]
+
+[[package]]
+name = "sbi-spec"
+version = "0.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6e36312fb5ddc10d08ecdc65187402baba4ac34585cb9d1b78522ae2358d890"
+
+[[package]]
+name = "sbi-spec"
+version = "0.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8309630ab2b300d4fe52b6757e53a7cbb6672f55aa08b50e28b1952c06dd994d"
+
+[[package]]
+name = "sbi-spec"
+version = "0.0.8"
+source = "git+https://github.com/rustsbi/rustsbi?rev=4821073#4821073b56a7223781c11a49aba743785d89d3ea"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "sbi-spec"
+version = "0.0.8"
+source = "git+https://github.com/rustsbi/rustsbi#99f4177fbed12c96c2c62121d51953b1bfa0ff43"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "sbi-testing"
+version = "0.0.3-alpha.2"
+source = "git+https://github.com/rustsbi/rustsbi?rev=4821073#4821073b56a7223781c11a49aba743785d89d3ea"
+dependencies = [
+ "log",
+ "riscv 0.12.1",
+ "sbi-rt 0.0.3 (git+https://github.com/rustsbi/rustsbi?rev=4821073)",
+ "sbi-spec 0.0.8 (git+https://github.com/rustsbi/rustsbi?rev=4821073)",
+]
+
+[[package]]
+name = "sbi-testing"
+version = "0.0.3-alpha.2"
+source = "git+https://github.com/rustsbi/rustsbi#99f4177fbed12c96c2c62121d51953b1bfa0ff43"
+dependencies = [
+ "log",
+ "riscv 0.12.1",
+ "sbi-rt 0.0.3 (git+https://github.com/rustsbi/rustsbi)",
+ "sbi-spec 0.0.8 (git+https://github.com/rustsbi/rustsbi)",
+]
+
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "serde"
+version = "1.0.217"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde-device-tree"
+version = "0.0.1"
+source = "git+https://github.com/rustsbi/serde-device-tree#e7f9404fc07bc3f8cce4e7a833be6a0fff93b5c4"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.217"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "sifive-test-device"
+version = "0.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba50a6fd7cb5cdb2645fb93fb2bbae7d8d78390677a889bdcfaf13c3d29286d0"
+
+[[package]]
+name = "spin"
+version = "0.9.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
+dependencies = [
+ "lock_api",
+]
+
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
+
+[[package]]
+name = "strsim"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+
+[[package]]
+name = "syn"
+version = "2.0.98"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "uart16550"
+version = "0.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "939f6f9ccad815fe3efca8fd06f2ec1620c0387fb1bca2b231b61ce710bffb9b"
+
+[[package]]
+name = "uart_xilinx"
+version = "0.2.0"
+source = "git+https://github.com/duskmoon314/uart-rs/#12be91421ad140f2a4bf4179578fd7a8fbc7ff5c"
+dependencies = [
+ "bitflags",
+ "volatile-register",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034"
+
+[[package]]
+name = "utf8parse"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
+
+[[package]]
+name = "vcell"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002"
+
+[[package]]
+name = "void"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
+
+[[package]]
+name = "volatile-register"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de437e2a6208b014ab52972a27e59b33fa2920d3e00fe05026167a1c509d19cc"
+dependencies = [
+ "vcell",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.59.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+
+[[package]]
+name = "xtask"
+version = "0.1.0"
+dependencies = [
+ "clap",
+ "clap-verbosity-flag",
+ "log",
+]
+
+[[package]]
+name = "xuantie-riscv"
+version = "0.0.0"
+source = "git+https://github.com/rustsbi/xuantie#7a521c0400dc7edb7a3ee103206dd8246c78d542"
+dependencies = [
+ "bit_field",
+ "bitflags",
+ "plic",
+ "volatile-register",
+]

+ 11 - 0
prototyper/Cargo.toml

@@ -0,0 +1,11 @@
+[workspace]
+resolver = "3"
+members = ["prototyper", "bench-kernel", "test-kernel", "xtask"]
+
+[workspace.package]
+edition = "2024"
+license = "MulanPSL-2.0 OR MIT"
+repository = "https://github.com/rustsbi/prototyper"
+
+[profile.release]
+debug = true

+ 7 - 0
prototyper/LICENSE-MIT

@@ -0,0 +1,7 @@
+Copyright (C) 2024 RustSBI Organization
+
+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.

+ 131 - 0
prototyper/LICENSE-MULAN

@@ -0,0 +1,131 @@
+木兰宽松许可证, 第2版
+
+木兰宽松许可证, 第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) 2024 RustSBI Organization
+
+RustSBI Prototyper 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.
+
+Mulan Permissive Software License,Version 2
+
+Mulan Permissive Software License,Version 2 (Mulan PSL v2)
+
+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
+
+How to Apply the Mulan Permissive Software License,Version 2 (Mulan PSL v2) to Your Software
+
+To apply the Mulan PSL v2 to your work, for easy identification by recipients, you are suggested to complete following three steps:
+
+    i. Fill in the blanks in following statement, including insert your software name, the year of the first publication of your software, and your name identified as the copyright owner;
+    ii. Create a file named "LICENSE" which contains the whole context of this License in the first directory of your software package;
+    iii. Attach the statement to the appropriate annotated syntax at the beginning of each source file.
+
+Copyright (c) 2024 RustSBI Organization
+
+RustSBI Prototyper 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.

+ 5 - 0
prototyper/rust-toolchain.toml

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

+ 11 - 0
prototyper/xtask/Cargo.toml

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

+ 79 - 0
prototyper/xtask/src/bench.rs

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

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

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

+ 56 - 0
prototyper/xtask/src/main.rs

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

+ 120 - 0
prototyper/xtask/src/prototyper.rs

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

+ 79 - 0
prototyper/xtask/src/test.rs

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

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

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

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

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

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

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