Bläddra i källkod

Merge branch 'master' into add-peripheral

Román Cárdenas Rodríguez 4 månader sedan
förälder
incheckning
b2183c696b

+ 17 - 7
.github/workflows/changelog.yaml

@@ -19,12 +19,14 @@ jobs:
           filters: |
             riscv:
               - 'riscv/**'
-            riscv-rt:
-              - 'riscv-rt/**'
             riscv-pac:
               - 'riscv-pac/**'
             riscv-peripheral:
               - 'riscv-peripheral/**'
+            riscv-rt:
+              - 'riscv-rt/**'
+            riscv-semihosting:
+              - 'riscv-semihosting/**'
 
       - name: Check for CHANGELOG.md (riscv)
         if: steps.changes.outputs.riscv == 'true'
@@ -34,6 +36,14 @@ jobs:
           skipLabels: 'skip changelog'
           missingUpdateErrorMessage: 'Please add a changelog entry in the riscv/CHANGELOG.md file.'
 
+      - name: Check for CHANGELOG.md (riscv-pac)
+        if: steps.changes.outputs.riscv-pac == 'true'
+        uses: dangoslen/changelog-enforcer@v3
+        with:
+          changeLogPath: ./riscv-pac/CHANGELOG.md
+          skipLabels: 'skip changelog'
+          missingUpdateErrorMessage: 'Please add a changelog entry in the riscv-pac/CHANGELOG.md file.'
+      
       - name: Check for CHANGELOG.md (riscv-rt)
         if: steps.changes.outputs.riscv-rt == 'true'
         uses: dangoslen/changelog-enforcer@v3
@@ -42,14 +52,14 @@ jobs:
           skipLabels: 'skip changelog'
           missingUpdateErrorMessage: 'Please add a changelog entry in the riscv-rt/CHANGELOG.md file.'
       
-      - name: Check for CHANGELOG.md (riscv-pac)
-        if: steps.changes.outputs.riscv-pac == 'true'
+      - name: Check for CHANGELOG.md (riscv-semihosting)
+        if: steps.changes.outputs.riscv-semihosting == 'true'
         uses: dangoslen/changelog-enforcer@v3
         with:
-          changeLogPath: ./riscv-pac/CHANGELOG.md
+          changeLogPath: ./riscv-semihosting/CHANGELOG.md
           skipLabels: 'skip changelog'
-          missingUpdateErrorMessage: 'Please add a changelog entry in the riscv-pac/CHANGELOG.md file.'
-      
+          missingUpdateErrorMessage: 'Please add a changelog entry in the riscv-semihosting/CHANGELOG.md file.'
+
       - name: Check for CHANGELOG.md (riscv-peripheral)
         if: steps.changes.outputs.riscv-peripheral == 'true'
         uses: dangoslen/changelog-enforcer@v3

+ 3 - 3
.github/workflows/riscv-rt.yaml

@@ -35,11 +35,11 @@ jobs:
           targets: ${{ matrix.target }}
       - name: Build (no features)
         run: RUSTFLAGS="-C link-arg=-Triscv-rt/examples/device.x" cargo build --package riscv-rt --target ${{ matrix.target }} --example ${{ matrix.example }}
-      - name : Build example (s-mode)
+      - name : Build (s-mode)
         run: RUSTFLAGS="-C link-arg=-Triscv-rt/examples/device.x" cargo build --package riscv-rt --target ${{ matrix.target }} --example ${{ matrix.example }} --features=s-mode
-      - name : Build example (single-hart)
+      - name : Build (single-hart)
         run: RUSTFLAGS="-C link-arg=-Triscv-rt/examples/device.x" cargo build --package riscv-rt --target ${{ matrix.target }} --example ${{ matrix.example }} --features=single-hart
-      - name: Build example (all features)
+      - name: Build (all features)
         run: RUSTFLAGS="-C link-arg=-Triscv-rt/examples/device.x" cargo build --package riscv-rt --target ${{ matrix.target }} --example ${{ matrix.example }} --all-features
 
   # Job to check that all the builds succeeded

+ 63 - 0
.github/workflows/riscv-semihosting.yaml

@@ -0,0 +1,63 @@
+on:
+  push:
+    branches: [ master ]
+  pull_request:
+  merge_group:
+
+name: Build check (riscv-semihosting)
+
+jobs:
+  # We check that the crate builds and links for all the toolchains and targets.
+  build-riscv:
+    strategy:
+      matrix:
+        # All generated code should be running on stable now, MRSV is 1.60.0
+        toolchain: [ stable, nightly, 1.60.0 ]
+        target:
+          - riscv32i-unknown-none-elf
+          - riscv32imc-unknown-none-elf
+          - riscv32imac-unknown-none-elf
+          - riscv64imac-unknown-none-elf
+          - riscv64gc-unknown-none-elf
+        include:
+          # Nightly is only for reference and allowed to fail
+          - toolchain: nightly
+            experimental: true
+    runs-on: ubuntu-latest
+    continue-on-error: ${{ matrix.experimental || false }}
+    steps:
+    - uses: actions/checkout@v4
+    - uses: dtolnay/rust-toolchain@master
+      with:
+        toolchain: ${{ matrix.toolchain }}
+        targets: ${{ matrix.target }}
+    - name: Build (M-mode)
+      run: cargo build --package riscv-semihosting --target ${{ matrix.target }}
+    - name: Build (U-mode)
+      run: cargo build --package riscv-semihosting --target ${{ matrix.target }} --features=u-mode
+    - name: Build (no semihosting)
+      run: cargo build --package riscv-semihosting --target ${{ matrix.target }} --features=no-semihosting
+
+  # On MacOS, Ubuntu, and Windows, we at least make sure that the crate builds and links.
+  build-others:
+    strategy:
+      matrix:
+        os: [ macos-latest, ubuntu-latest, windows-latest ]
+    runs-on: ${{ matrix.os }}
+    steps:
+      - uses: actions/checkout@v3
+      - uses: dtolnay/rust-toolchain@stable
+      - name: Build (no features)
+        run: cargo build --package riscv-semihosting
+      - name: Build (all features)
+        run: cargo build --package riscv-semihosting --all-features
+
+  # Job to check that all the builds succeeded
+  build-check:
+    needs:
+    - build-riscv
+    - build-others
+    runs-on: ubuntu-latest
+    if: always()
+    steps:
+      - run: jq --exit-status 'all(.result == "success")' <<< '${{ toJson(needs) }}'

+ 5 - 1
.github/workflows/riscv.yaml

@@ -31,8 +31,12 @@ jobs:
       with:
         toolchain: ${{ matrix.toolchain }}
         targets: ${{ matrix.target }}
-    - name: Build (no features)
+    - name: Build (M-mode)
       run: cargo build --package riscv --target ${{ matrix.target }}
+    - name: Build (M-mode, critical section)
+      run: cargo build --package riscv --target ${{ matrix.target }} --features=critical-section-single-hart
+    - name: Build (S-mode)
+      run: cargo build --package riscv --target ${{ matrix.target }} --features=s-mode
     - name: Build (all features)
       run: cargo build --package riscv --target ${{ matrix.target }} --all-features
 

+ 1 - 0
Cargo.toml

@@ -5,4 +5,5 @@ members = [
     "riscv-pac",
     "riscv-peripheral",
     "riscv-rt",
+    "riscv-semihosting",
 ]

+ 2 - 1
README.md

@@ -6,7 +6,7 @@ This repository contains various crates useful for writing Rust programs on RISC
 * [`riscv-pac`]: Common traits to be implemented by RISC-V PACs
 * [`riscv-peripheral`]: Interfaces for standard RISC-V peripherals
 * [`riscv-rt`]: Startup code and interrupt handling
-
+* [`riscv-semihosting`]: Semihosting for RISC-V processors
 
 This project is developed and maintained by the [RISC-V team][team].
 
@@ -26,5 +26,6 @@ to intervene to uphold that code of conduct.
 [`riscv-pac`]: https://crates.io/crates/riscv-pac
 [`riscv-peripheral`]: https://crates.io/crates/riscv-peripheral
 [`riscv-rt`]: https://crates.io/crates/riscv-rt
+[`riscv-semihosting`]: https://crates.io/crates/riscv-semihosting
 [team]: https://github.com/rust-embedded/wg#the-risc-v-team
 [CoC]: CODE_OF_CONDUCT.md

+ 5 - 0
riscv-rt/CHANGELOG.md

@@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 
 ### Added
 
+- Add `links` field in `Cargo.toml`
+- Add FPU initialization
+- Static array for vectored-like handling of exceptions
 - New GitHub workflow for checking invalid labels in PRs
 - New GitHub workflow for checking modifications on CHANGELOG.md
 - New GitHub workflow for checking clippy lints in PRs
@@ -16,6 +19,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 
 ### Changed
 
+- Removed U-mode interrupts to align with latest RISC-V specification
+- Changed `Vector` union. Now, it uses `Option<fn>`, which is more idiomatic in Rust
 - Removed riscv-target dependency for build
 - Upgrade rust-version to 1.60
 - Cargo workspace for riscv and riscv-rt

+ 1 - 0
riscv-rt/Cargo.toml

@@ -10,6 +10,7 @@ documentation = "https://docs.rs/riscv-rt"
 keywords = ["riscv", "runtime", "startup"]
 license = "ISC"
 edition = "2021"
+links = "riscv-rt" # Prevent multiple versions of riscv-rt being linked
 
 [features]
 s-mode = []

+ 30 - 2
riscv-rt/build.rs

@@ -18,7 +18,7 @@ fn add_linker_script(arch_width: u32) -> io::Result<()> {
 }
 
 /// Parse the target RISC-V architecture and returns its bit width and the extension set
-fn parse_target(target: &str) -> (u32, HashSet<char>) {
+fn parse_target(target: &str, cargo_flags: &str) -> (u32, HashSet<char>) {
     // isolate bit width and extensions from the rest of the target information
     let arch = target
         .trim_start_matches("riscv")
@@ -43,18 +43,46 @@ fn parse_target(target: &str) -> (u32, HashSet<char>) {
         extensions.insert('d');
     }
 
+    let cargo_flags = cargo_flags
+        .split(0x1fu8 as char)
+        .filter(|arg| !arg.is_empty());
+
+    cargo_flags
+        .filter(|k| k.starts_with("target-feature="))
+        .flat_map(|str| {
+            let flags = str.split('=').collect::<Vec<&str>>()[1];
+            flags.split(',')
+        })
+        .for_each(|feature| {
+            let chars = feature.chars().collect::<Vec<char>>();
+            match chars[0] {
+                '+' => {
+                    extensions.insert(chars[1]);
+                }
+                '-' => {
+                    extensions.remove(&chars[1]);
+                }
+                _ => {
+                    panic!("Unsupported target feature operation");
+                }
+            }
+        });
+
     (bits, extensions)
 }
 
 fn main() {
     let target = env::var("TARGET").unwrap();
+    let cargo_flags = env::var("CARGO_ENCODED_RUSTFLAGS").unwrap();
     let _name = env::var("CARGO_PKG_NAME").unwrap();
 
     // set configuration flags depending on the target
     if target.starts_with("riscv") {
         println!("cargo:rustc-cfg=riscv");
 
-        let (bits, extensions) = parse_target(&target);
+        // This is required until target_arch & target_feature risc-v work is
+        // stable and in-use (rust 1.75.0)
+        let (bits, extensions) = parse_target(&target, &cargo_flags);
 
         // generate the linker script and expose the ISA width
         let arch_width = match bits {

+ 15 - 3
riscv-rt/link.x.in

@@ -28,13 +28,25 @@ PROVIDE(_max_hart_id = 0);
 PROVIDE(_hart_stack_size = 2K);
 PROVIDE(_heap_size = 0);
 
-PROVIDE(UserSoft = DefaultHandler);
+PROVIDE(InstructionMisaligned = ExceptionHandler);
+PROVIDE(InstructionFault = ExceptionHandler);
+PROVIDE(IllegalInstruction = ExceptionHandler);
+PROVIDE(Breakpoint = ExceptionHandler);
+PROVIDE(LoadMisaligned = ExceptionHandler);
+PROVIDE(LoadFault = ExceptionHandler);
+PROVIDE(StoreMisaligned = ExceptionHandler);
+PROVIDE(StoreFault = ExceptionHandler);;
+PROVIDE(UserEnvCall = ExceptionHandler);
+PROVIDE(SupervisorEnvCall = ExceptionHandler);
+PROVIDE(MachineEnvCall = ExceptionHandler);
+PROVIDE(InstructionPageFault = ExceptionHandler);
+PROVIDE(LoadPageFault = ExceptionHandler);
+PROVIDE(StorePageFault = ExceptionHandler);
+
 PROVIDE(SupervisorSoft = DefaultHandler);
 PROVIDE(MachineSoft = DefaultHandler);
-PROVIDE(UserTimer = DefaultHandler);
 PROVIDE(SupervisorTimer = DefaultHandler);
 PROVIDE(MachineTimer = DefaultHandler);
-PROVIDE(UserExternal = DefaultHandler);
 PROVIDE(SupervisorExternal = DefaultHandler);
 PROVIDE(MachineExternal = DefaultHandler);
 

+ 57 - 1
riscv-rt/macros/src/lib.rs

@@ -9,7 +9,11 @@ extern crate proc_macro2;
 extern crate syn;
 
 use proc_macro2::Span;
-use syn::{parse, spanned::Spanned, FnArg, ItemFn, PathArguments, ReturnType, Type, Visibility};
+use syn::{
+    parse::{self, Parse},
+    spanned::Spanned,
+    FnArg, ItemFn, LitInt, LitStr, PathArguments, ReturnType, Type, Visibility,
+};
 
 use proc_macro::TokenStream;
 
@@ -205,3 +209,55 @@ pub fn pre_init(args: TokenStream, input: TokenStream) -> TokenStream {
     )
     .into()
 }
+
+struct AsmLoopArgs {
+    asm_template: String,
+    count: usize,
+}
+
+impl Parse for AsmLoopArgs {
+    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
+        let template: LitStr = input.parse().unwrap();
+        _ = input.parse::<Token![,]>().unwrap();
+        let count: LitInt = input.parse().unwrap();
+
+        Ok(Self {
+            asm_template: template.value(),
+            count: count.base10_parse().unwrap(),
+        })
+    }
+}
+
+/// Loops an asm expression n times.
+///
+/// `loop_asm!` takes 2 arguments, the first is a string literal and the second is a number literal
+/// See [the formatting syntax documentation in `std::fmt`](../std/fmt/index.html)
+/// for details.
+///
+/// Argument 1 is an assembly expression, all "{}" in this assembly expression will be replaced with the
+/// current loop index.
+///
+/// Argument 2 is the number of loops to do with the provided expression.
+///
+/// # Examples
+///
+/// ```
+/// # use riscv_rt_macros::loop_asm;
+/// unsafe {
+///     loop_asm!("fmv.w.x f{}, x0", 32); // => core::arch::asm!("fmv.w.x f0, x0") ... core::arch::asm!("fmv.w.x f31, x0")
+/// }
+/// ```
+#[proc_macro]
+pub fn loop_asm(input: TokenStream) -> TokenStream {
+    let args = parse_macro_input!(input as AsmLoopArgs);
+
+    let tokens = (0..args.count)
+        .map(|i| {
+            let i = i.to_string();
+            let asm = args.asm_template.replace("{}", &i);
+            format!("core::arch::asm!(\"{}\");", asm)
+        })
+        .collect::<Vec<String>>()
+        .join("\n");
+    tokens.parse().unwrap()
+}

+ 136 - 60
riscv-rt/src/lib.rs

@@ -245,9 +245,47 @@
 //!
 //! Default implementation of this function wakes hart 0 and busy-loops all the other harts.
 //!
+//!
+//! ### Core exception handlers
+//!
+//! This functions are called when corresponding exception occurs.
+//! You can define an exception handler with one of the following names:
+//! * `InstructionMisaligned`
+//! * `InstructionFault`
+//! * `IllegalInstruction`
+//! * `Breakpoint`
+//! * `LoadMisaligned`
+//! * `LoadFault`
+//! * `StoreMisaligned`
+//! * `StoreFault`
+//! * `UserEnvCall`
+//! * `SupervisorEnvCall`
+//! * `MachineEnvCall`
+//! * `InstructionPageFault`
+//! * `LoadPageFault`
+//! * `StorePageFault`
+//!
+//! For example:
+//! ``` no_run
+//! #[export_name = "MachineEnvCall"]
+//! fn custom_menv_call_handler(trap_frame: &riscv_rt::TrapFrame) {
+//!     // ...
+//! }
+//! ```
+//! or
+//! ``` no_run
+//! #[no_mangle]
+//! fn MachineEnvCall(trap_frame: &riscv_rt::TrapFrame) -> ! {
+//!     // ...
+//! }
+//! ```
+//!
+//! If exception handler is not explicitly defined, `ExceptionHandler` is called.
+//!
 //! ### `ExceptionHandler`
 //!
-//! This function is called when exception is occured. The exception reason can be decoded from the
+//! This function is called when exception without defined exception handler is occured.
+//! The exception reason can be decoded from the
 //! `mcause`/`scause` register.
 //!
 //! This function can be redefined in the following way:
@@ -377,8 +415,18 @@ use riscv::register::{mcause as xcause, mtvec as xtvec, mtvec::TrapMode as xTrap
 #[cfg(all(not(feature = "single-hart"), not(feature = "s-mode")))]
 use riscv::register::mhartid;
 
+#[cfg(all(feature = "s-mode", any(riscvf, riscvd)))]
+use riscv::register::sstatus as xstatus;
+
+#[cfg(all(not(feature = "s-mode"), any(riscvf, riscvd)))]
+use riscv::register::mstatus as xstatus;
+
 pub use riscv_rt_macros::{entry, pre_init};
 
+/// We export this static with an informative name so that if an application attempts to link
+/// two copies of riscv-rt together, linking will fail. We also declare a links key in
+/// Cargo.toml which is the more modern way to solve the same problem, but we have to keep
+/// __ONCE__ around to prevent linking with versions before the links key was added.
 #[export_name = "error: riscv-rt appears more than once in the dependency graph"]
 #[doc(hidden)]
 pub static __ONCE__: () = ();
@@ -512,7 +560,21 @@ pub unsafe extern "C" fn start_rust(a0: usize, a1: usize, a2: usize) -> ! {
         compiler_fence(Ordering::SeqCst);
     }
 
-    // TODO: Enable FPU when available
+    #[cfg(any(riscvf, riscvd))]
+    {
+        xstatus::set_fs(xstatus::FS::Initial); // Enable fpu in xstatus
+        core::arch::asm!("fscsr x0"); // Zero out fcsr register csrrw x0, fcsr, x0
+
+        // Zero out floating point registers
+        #[cfg(all(target_arch = "riscv32", riscvd))]
+        riscv_rt_macros::loop_asm!("fcvt.d.w f{}, x0", 32);
+
+        #[cfg(all(target_arch = "riscv64", riscvd))]
+        riscv_rt_macros::loop_asm!("fmv.d.x f{}, x0", 32);
+
+        #[cfg(not(riscvd))]
+        riscv_rt_macros::loop_asm!("fmv.w.x f{}, x0", 32);
+    }
 
     _setup_interrupts();
 
@@ -561,15 +623,27 @@ pub unsafe extern "C" fn start_trap_rust(trap_frame: *const TrapFrame) {
     }
 
     let cause = xcause::read();
+    let code = cause.code();
 
     if cause.is_exception() {
-        ExceptionHandler(&*trap_frame)
-    } else if cause.code() < __INTERRUPTS.len() {
-        let h = &__INTERRUPTS[cause.code()];
-        if h.reserved == 0 {
-            DefaultHandler();
+        let trap_frame = &*trap_frame;
+        if code < __EXCEPTIONS.len() {
+            let h = &__EXCEPTIONS[code];
+            if let Some(handler) = h {
+                handler(trap_frame);
+            } else {
+                ExceptionHandler(trap_frame);
+            }
         } else {
-            (h.handler)();
+            ExceptionHandler(trap_frame);
+        }
+        ExceptionHandler(trap_frame)
+    } else if code < __INTERRUPTS.len() {
+        let h = &__INTERRUPTS[code];
+        if let Some(handler) = h {
+            handler();
+        } else {
+            DefaultHandler();
         }
     } else {
         DefaultHandler();
@@ -589,7 +663,7 @@ pub fn DefaultExceptionHandler(trap_frame: &TrapFrame) -> ! {
 
 #[doc(hidden)]
 #[no_mangle]
-#[allow(unused_variables, non_snake_case)]
+#[allow(non_snake_case)]
 pub fn DefaultInterruptHandler() {
     loop {
         // Prevent this from turning into a UDF instruction
@@ -598,76 +672,78 @@ pub fn DefaultInterruptHandler() {
     }
 }
 
-/* Interrupts */
-#[doc(hidden)]
-pub enum Interrupt {
-    UserSoft,
-    SupervisorSoft,
-    MachineSoft,
-    UserTimer,
-    SupervisorTimer,
-    MachineTimer,
-    UserExternal,
-    SupervisorExternal,
-    MachineExternal,
+extern "C" {
+    fn InstructionMisaligned(trap_frame: &TrapFrame);
+    fn InstructionFault(trap_frame: &TrapFrame);
+    fn IllegalInstruction(trap_frame: &TrapFrame);
+    fn Breakpoint(trap_frame: &TrapFrame);
+    fn LoadMisaligned(trap_frame: &TrapFrame);
+    fn LoadFault(trap_frame: &TrapFrame);
+    fn StoreMisaligned(trap_frame: &TrapFrame);
+    fn StoreFault(trap_frame: &TrapFrame);
+    fn UserEnvCall(trap_frame: &TrapFrame);
+    fn SupervisorEnvCall(trap_frame: &TrapFrame);
+    fn MachineEnvCall(trap_frame: &TrapFrame);
+    fn InstructionPageFault(trap_frame: &TrapFrame);
+    fn LoadPageFault(trap_frame: &TrapFrame);
+    fn StorePageFault(trap_frame: &TrapFrame);
 }
 
-pub use self::Interrupt as interrupt;
+#[doc(hidden)]
+#[no_mangle]
+pub static __EXCEPTIONS: [Option<unsafe extern "C" fn(&TrapFrame)>; 16] = [
+    Some(InstructionMisaligned),
+    Some(InstructionFault),
+    Some(IllegalInstruction),
+    Some(Breakpoint),
+    Some(LoadMisaligned),
+    Some(LoadFault),
+    Some(StoreMisaligned),
+    Some(StoreFault),
+    Some(UserEnvCall),
+    Some(SupervisorEnvCall),
+    None,
+    Some(MachineEnvCall),
+    Some(InstructionPageFault),
+    Some(LoadPageFault),
+    None,
+    Some(StorePageFault),
+];
 
 extern "C" {
-    fn UserSoft();
     fn SupervisorSoft();
     fn MachineSoft();
-    fn UserTimer();
     fn SupervisorTimer();
     fn MachineTimer();
-    fn UserExternal();
     fn SupervisorExternal();
     fn MachineExternal();
 }
 
-#[doc(hidden)]
-pub union Vector {
-    pub handler: unsafe extern "C" fn(),
-    pub reserved: usize,
-}
-
 #[doc(hidden)]
 #[no_mangle]
-pub static __INTERRUPTS: [Vector; 12] = [
-    Vector { handler: UserSoft },
-    Vector {
-        handler: SupervisorSoft,
-    },
-    Vector { reserved: 0 },
-    Vector {
-        handler: MachineSoft,
-    },
-    Vector { handler: UserTimer },
-    Vector {
-        handler: SupervisorTimer,
-    },
-    Vector { reserved: 0 },
-    Vector {
-        handler: MachineTimer,
-    },
-    Vector {
-        handler: UserExternal,
-    },
-    Vector {
-        handler: SupervisorExternal,
-    },
-    Vector { reserved: 0 },
-    Vector {
-        handler: MachineExternal,
-    },
+pub static __INTERRUPTS: [Option<unsafe extern "C" fn()>; 12] = [
+    None,
+    Some(SupervisorSoft),
+    None,
+    Some(MachineSoft),
+    None,
+    Some(SupervisorTimer),
+    None,
+    Some(MachineTimer),
+    None,
+    Some(SupervisorExternal),
+    None,
+    Some(MachineExternal),
 ];
 
+/// Default implementation of `_pre_init` does nothing.
+/// Users can override this function with the [`#[pre_init]`] macro.
 #[doc(hidden)]
 #[no_mangle]
 #[rustfmt::skip]
-pub unsafe extern "Rust" fn default_pre_init() {}
+pub extern "Rust" fn default_pre_init() {}
 
+/// Default implementation of `_mp_hook` wakes hart 0 and busy-loops all the other harts.
 #[doc(hidden)]
 #[no_mangle]
 #[rustfmt::skip]
@@ -681,7 +757,7 @@ pub extern "Rust" fn default_mp_hook(hartid: usize) -> bool {
     }
 }
 
-/// Default implementation of `_setup_interrupts` that sets `mtvec`/`stvec` to a trap handler address.
+/// Default implementation of `_setup_interrupts` sets `mtvec`/`stvec` to the address of `_start_trap`.
 #[doc(hidden)]
 #[no_mangle]
 #[rustfmt::skip]

+ 35 - 0
riscv-semihosting/CHANGELOG.md

@@ -0,0 +1,35 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+This project adheres to [Semantic Versioning](http://semver.org/).
+
+## [Unreleased]
+
+- Add recommendation for `semihosting` in README.md.
+- Bug fixes
+- Moved to the `riscv` Cargo workspace
+- Bring in API changes from
+  [cortex-m-semihosting](https://github.com/rust-embedded/cortex-m/tree/master/cortex-m-semihosting),
+  including:
+    - Addition of the `hprint`, `hprintln`, `heprint`, `heprintln`, and `dbg`
+      macros.
+        - `hprint` and `heprintln` print to host stdout without and with a
+          newline, respectively.
+        - `heprint` and `heprintln` do the same, except to host stderr.
+        - `dbg` works exactly like
+          [`std::dbg`](https://doc.rust-lang.org/std/macro.dbg.html).
+    - `HStdout` and `HStderr` have been combined into `HostStream`.
+    - `inline-asm` feature removed, switched to stabilized inline asm and MSRV
+      bumped to 1.59.0
+- Clean up documentation, removing unnecessary references to
+  cortex-m-semihosting and improving clarity.
+- Added GitHub Actions CI
+- Add features to select the privilege level the semihosting operations will be
+  started from
+
+## [v0.0.1] - 2018-02-27
+
+- Initial release
+
+[Unreleased]: https://github.com/riscv-rust/riscv-semihosting/compare/cb1afe4002d576b87bfd4c199f42a43815984ce4..HEAD
+[v0.0.1]: https://github.com/riscv-rust/riscv-semihosting/tree/cb1afe4002d576b87bfd4c199f42a43815984ce4

+ 27 - 0
riscv-semihosting/Cargo.toml

@@ -0,0 +1,27 @@
+[package]
+authors = [
+    "The Cortex-M Team <cortex-m@teams.rust-embedded.org>",
+    "Jorge Aparicio <japaricious@gmail.com>",
+    "The RISC-V Team <risc-v@teams.rust-embedded.org>",
+]
+description = "Semihosting for RISCV processors"
+documentation = "https://docs.rs/riscv-semihosting"
+keywords = ["semihosting", "riscv"]
+categories = ["no-std", "embedded"]
+license = "MIT OR Apache-2.0"
+name = "riscv-semihosting"
+readme = "README.md"
+repository = "https://github.com/riscv-rust/riscv"
+version = "0.0.1"
+edition = "2021"
+rust-version = "1.60.0"
+
+[features]
+u-mode = []
+jlink-quirks = []
+no-semihosting = []
+default = ["jlink-quirks"]
+
+[dependencies]
+critical-section = "1.0.0"
+riscv = { path = "../riscv", version = "0.10.1" }

+ 72 - 0
riscv-semihosting/README.md

@@ -0,0 +1,72 @@
+[![crates.io](https://img.shields.io/crates/d/riscv-semihosting.svg)](https://crates.io/crates/riscv-semihosting)
+[![crates.io](https://img.shields.io/crates/v/riscv-semihosting.svg)](https://crates.io/crates/riscv-semihosting)
+
+# `riscv-semihosting`
+
+> Simple semihosting for RISC-V processors
+
+This is a fork of the
+[`cortex-m-semihosting`] crate with changes
+to support the RISC-V Semihosting Specification as documented
+[here](https://github.com/riscv/riscv-semihosting-spec/blob/main/riscv-semihosting-spec.adoc)
+
+This crate can (almost) be used in exactly the same way as cortex-m-semihosting,
+simply by changing calls to `cortex_m_semihosting::*` to `riscv_semihosting::*`.
+Given this, the
+[`cortex-m-semihosting documentation`](https://docs.rs/cortex-m-semihosting) is
+generally sufficient for using this library.
+
+A major difference between this library and `cortex-m-semihosting` is that there
+are mandatory features to choose the privilege level at which the semihosting
+calls are executed. The *machine-mode (M-mode)* feature will cause the macros in `export`
+to execute the semihosting operation in an interrupt-free context, while
+*user-mode (U-mode)* causes them to just execute the operation.
+By default, M-mode is used. You can activate the U-mode via the `u-mode` feature.
+
+# About the [`semihosting`] crate
+
+`riscv-semihosting` provides a simple semihosting API that matches [`cortex-m-semihosting`].
+This allows a simple port from Cortex-M applications to RISC-V applications.
+However, the [`semihosting`] crate presents a more advanced interface that is compatible
+for RISC-V as well as other architectures (e.g., ARM or MIPS).
+While `riscv-semihosting` is a good starting point for developing semihosted applications,
+**we recommend using the [`semihosting`] crate.**
+
+
+# Minimum Supported Rust Version (MSRV)
+
+This crate is guaranteed to compile on stable Rust 1.60.0 and up. It **won't**
+compile with older versions.
+
+## License
+
+Copyright 2018-2023 [RISC-V team][team]
+
+Permission to use, copy, modify, and/or distribute this software for any purpose
+with or without fee is hereby granted, provided that the above copyright notice
+and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+THIS SOFTWARE.
+
+### Contribution
+
+Unless you explicitly state otherwise, any contribution intentionally submitted
+for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
+dual licensed as above, without any additional terms or conditions.
+
+## Code of Conduct
+
+Contribution to this crate is organized under the terms of the [Rust Code of
+Conduct][CoC], the maintainer of this crate, the [RISC-V team][team], promises
+to intervene to uphold that code of conduct.
+
+[CoC]: ../CODE_OF_CONDUCT.md
+[team]: https://github.com/rust-embedded/wg#the-risc-v-team
+[`semihosting`]: https://crates.io/crates/semihosting
+[`cortex-m-semihosting`]: https://docs.rs/cortex-m-semihosting

+ 9 - 0
riscv-semihosting/build.rs

@@ -0,0 +1,9 @@
+use std::env;
+
+fn main() {
+    let target = env::var("TARGET").unwrap();
+
+    if target.starts_with("riscv") {
+        println!("cargo:rustc-cfg=riscv");
+    }
+}

+ 94 - 0
riscv-semihosting/src/debug.rs

@@ -0,0 +1,94 @@
+//! Interacting with debugging agent
+//!
+//! # Example
+//!
+//! This example will show how to terminate the QEMU session. The program
+//! should be running under QEMU with semihosting enabled
+//! (use `-semihosting` flag).
+//!
+//! Target program:
+//!
+//! ```no_run
+//! use riscv_semihosting::debug::{self, EXIT_SUCCESS, EXIT_FAILURE};
+//!
+//! if 2 == 2 {
+//!     // report success
+//!     debug::exit(EXIT_SUCCESS);
+//! } else {
+//!     // report failure
+//!     debug::exit(EXIT_FAILURE);
+//! }
+//!```
+
+/// This values are taken from section 5.5.2 of
+/// ADS Debug Target Guide (DUI0058).
+// TODO document
+#[allow(missing_docs)]
+pub enum Exception {
+    // Hardware reason codes
+    BranchThroughZero = 0x20000,
+    UndefinedInstr = 0x20001,
+    SoftwareInterrupt = 0x20002,
+    PrefetchAbort = 0x20003,
+    DataAbort = 0x20004,
+    AddressException = 0x20005,
+    IRQ = 0x20006,
+    FIQ = 0x20007,
+    // Software reason codes
+    BreakPoint = 0x20020,
+    WatchPoint = 0x20021,
+    StepComplete = 0x20022,
+    RunTimeErrorUnknown = 0x20023,
+    InternalError = 0x20024,
+    UserInterruption = 0x20025,
+    ApplicationExit = 0x20026,
+    StackOverflow = 0x20027,
+    DivisionByZero = 0x20028,
+    OSSpecific = 0x20029,
+}
+
+/// Status enum for `exit` syscall.
+pub type ExitStatus = Result<(), ()>;
+
+/// Successful execution of a program.
+pub const EXIT_SUCCESS: ExitStatus = Ok(());
+
+/// Unsuccessful execution of a program.
+pub const EXIT_FAILURE: ExitStatus = Err(());
+
+/// Reports to the debugger that the execution has completed.
+///
+/// This call can be used to terminate QEMU session and report back success
+/// or failure. If you need to pass more than one type of error, consider
+/// using `report_exception` syscall instead.
+///
+/// This call should not return. However, it is possible for the debugger
+/// to request that the application continue. In that case this call
+/// returns normally.
+///
+pub fn exit(status: ExitStatus) {
+    match status {
+        EXIT_SUCCESS => report_exception(Exception::ApplicationExit),
+        EXIT_FAILURE => report_exception(Exception::RunTimeErrorUnknown),
+    }
+}
+
+/// Report an exception to the debugger directly.
+///
+/// Exception handlers can use this SWI at the end of handler chains
+/// as the default action, to indicate that the exception has not been handled.
+///
+/// This call should not return. However, it is possible for the debugger
+/// to request that the application continue. In that case this call
+/// returns normally.
+///
+/// # Arguments
+///
+/// * `reason` - A reason code reported back to the debugger.
+///
+pub fn report_exception(reason: Exception) {
+    let code = reason as usize;
+    unsafe {
+        syscall1!(REPORT_EXCEPTION, code);
+    }
+}

+ 101 - 0
riscv-semihosting/src/export.rs

@@ -0,0 +1,101 @@
+//! IMPLEMENTATION DETAILS USED BY MACROS
+
+use crate::hio::{self, HostStream};
+use core::fmt::{self, Write};
+
+static mut HSTDOUT: Option<HostStream> = None;
+static mut HSTDERR: Option<HostStream> = None;
+
+#[cfg(not(feature = "u-mode"))]
+mod machine {
+    use super::*;
+
+    pub fn hstdout_str(s: &str) {
+        let _result = critical_section::with(|_| unsafe {
+            if HSTDOUT.is_none() {
+                HSTDOUT = Some(hio::hstdout()?);
+            }
+
+            HSTDOUT.as_mut().unwrap().write_str(s).map_err(drop)
+        });
+    }
+
+    pub fn hstdout_fmt(args: fmt::Arguments) {
+        let _result = critical_section::with(|_| unsafe {
+            if HSTDOUT.is_none() {
+                HSTDOUT = Some(hio::hstdout()?);
+            }
+
+            HSTDOUT.as_mut().unwrap().write_fmt(args).map_err(drop)
+        });
+    }
+
+    pub fn hstderr_str(s: &str) {
+        let _result = critical_section::with(|_| unsafe {
+            if HSTDERR.is_none() {
+                HSTDERR = Some(hio::hstderr()?);
+            }
+
+            HSTDERR.as_mut().unwrap().write_str(s).map_err(drop)
+        });
+    }
+
+    pub fn hstderr_fmt(args: fmt::Arguments) {
+        let _result = critical_section::with(|_| unsafe {
+            if HSTDERR.is_none() {
+                HSTDERR = Some(hio::hstderr()?);
+            }
+
+            HSTDERR.as_mut().unwrap().write_fmt(args).map_err(drop)
+        });
+    }
+}
+#[cfg(not(feature = "u-mode"))]
+pub use machine::*;
+
+#[cfg(feature = "u-mode")]
+mod user {
+    use super::*;
+
+    pub fn hstdout_str(s: &str) {
+        let _result = unsafe {
+            if HSTDOUT.is_none() {
+                HSTDOUT = Some(hio::hstdout().unwrap());
+            }
+
+            HSTDOUT.as_mut().unwrap().write_str(s).map_err(drop)
+        };
+    }
+
+    pub fn hstdout_fmt(args: fmt::Arguments) {
+        let _result = unsafe {
+            if HSTDOUT.is_none() {
+                HSTDOUT = Some(hio::hstdout().unwrap());
+            }
+
+            HSTDOUT.as_mut().unwrap().write_fmt(args).map_err(drop)
+        };
+    }
+
+    pub fn hstderr_str(s: &str) {
+        let _result = unsafe {
+            if HSTDERR.is_none() {
+                HSTDERR = Some(hio::hstderr().unwrap());
+            }
+
+            HSTDERR.as_mut().unwrap().write_str(s).map_err(drop)
+        };
+    }
+
+    pub fn hstderr_fmt(args: fmt::Arguments) {
+        let _result = unsafe {
+            if HSTDERR.is_none() {
+                HSTDERR = Some(hio::hstderr().unwrap());
+            }
+
+            HSTDERR.as_mut().unwrap().write_fmt(args).map_err(drop)
+        };
+    }
+}
+#[cfg(feature = "u-mode")]
+pub use user::*;

+ 69 - 0
riscv-semihosting/src/hio.rs

@@ -0,0 +1,69 @@
+//! Host I/O
+
+// Fixing this lint requires a breaking change that does not add much value
+#![allow(clippy::result_unit_err)]
+
+use crate::nr;
+use core::{fmt, slice};
+
+/// A byte stream to the host (e.g., host's stdout or stderr).
+#[derive(Clone, Copy)]
+pub struct HostStream {
+    fd: usize,
+}
+
+impl HostStream {
+    /// Attempts to write an entire `buffer` into this sink
+    pub fn write_all(&mut self, buffer: &[u8]) -> Result<(), ()> {
+        write_all(self.fd, buffer)
+    }
+}
+
+impl fmt::Write for HostStream {
+    fn write_str(&mut self, s: &str) -> fmt::Result {
+        self.write_all(s.as_bytes()).map_err(|_| fmt::Error)
+    }
+}
+
+/// Construct a new handle to the host's standard error.
+pub fn hstderr() -> Result<HostStream, ()> {
+    // There is actually no stderr access in ARM Semihosting documentation. Use
+    // convention used in libgloss.
+    // See: libgloss/arm/syscalls.c, line 139.
+    // https://sourceware.org/git/gitweb.cgi?p=newlib-cygwin.git;a=blob;f=libgloss/arm/syscalls.c#l139
+    open(":tt\0", nr::open::W_APPEND)
+}
+
+/// Construct a new handle to the host's standard output.
+pub fn hstdout() -> Result<HostStream, ()> {
+    open(":tt\0", nr::open::W_TRUNC)
+}
+
+fn open(name: &str, mode: usize) -> Result<HostStream, ()> {
+    let name = name.as_bytes();
+    match unsafe { syscall!(OPEN, name.as_ptr(), mode, name.len() - 1) } as isize {
+        -1 => Err(()),
+        fd => Ok(HostStream { fd: fd as usize }),
+    }
+}
+
+fn write_all(fd: usize, mut buffer: &[u8]) -> Result<(), ()> {
+    while !buffer.is_empty() {
+        match unsafe { syscall!(WRITE, fd, buffer.as_ptr(), buffer.len()) } {
+            // Done
+            0 => return Ok(()),
+            // `n` bytes were not written
+            n if n <= buffer.len() => {
+                let offset = (buffer.len() - n) as isize;
+                buffer = unsafe { slice::from_raw_parts(buffer.as_ptr().offset(offset), n) }
+            }
+            #[cfg(feature = "jlink-quirks")]
+            // Error (-1) - should be an error but JLink can return -1, -2, -3,...
+            // For good measure, we allow up to negative 15.
+            n if n > 0xfffffff0 => return Ok(()),
+            // Error
+            _ => return Err(()),
+        }
+    }
+    Ok(())
+}

+ 244 - 0
riscv-semihosting/src/lib.rs

@@ -0,0 +1,244 @@
+//! Semihosting for RISCV processors
+//!
+//! # What is semihosting?
+//!
+//! "Semihosting is a technique where an application running in a debug or
+//! simulation environment can access elements of the system hosting the
+//! debugger or simulator including console, file system, time and other
+//! functions. This allows for diagnostics, interaction and measurement of a
+//! target system without requiring significant infrastructure to exist in that
+//! target environment." - RISC-V Semihosting Spec
+//!
+//! # Interface
+//!
+//! This crate provides implementations of
+//! [`core::fmt::Write`](https://doc.rust-lang.org/core/fmt/trait.Write.html),
+//! so you can use it, in conjunction with
+//! [`core::format_args!`](https://doc.rust-lang.org/core/macro.format_args.html)
+//! or the [`write!` macro](https://doc.rust-lang.org/core/macro.write.html),
+//! for user-friendly construction and printing of formatted strings.
+//!
+//! Since semihosting operations are modeled as [system calls][sc], this crate
+//! exposes an untyped `syscall!` interface just like the [`sc`] crate does.
+//!
+//! [sc]: https://en.wikipedia.org/wiki/System_call
+//! [`sc`]: https://crates.io/crates/sc
+//!
+//! # Forewarning
+//!
+//! Semihosting operations are *very* slow. Like, each WRITE operation can take
+//! hundreds of milliseconds.
+//!
+//! # Example
+//!
+//! ## Using `hio::hstdout`
+//!
+//! This example will demonstrate how to print formatted strings.
+//!
+//! ```no_run
+//! use riscv_semihosting::hio;
+//! use core::fmt::Write;
+//!
+//! // This function will be called by the application
+//! fn print() -> Result<(), core::fmt::Error> {
+//!     let mut stdout = hio::hstdout().map_err(|_| core::fmt::Error)?;
+//!     let language = "Rust";
+//!     let ranking = 1;
+//!
+//!     write!(stdout, "{} on embedded is #{}!", language, ranking)?;
+//!
+//!     Ok(())
+//! }
+//! ```
+//!
+//! On the host side:
+//!
+//! ``` text
+//! $ openocd -f $INTERFACE -f $TARGET -l /tmp/openocd.log
+//! Open On-Chip Debugger 0.9.0 (2016-04-27-23:18)
+//! Licensed under GNU GPL v2
+//! For bug reports, read
+//!         http://openocd.org/doc/doxygen/bugs.html
+//! # the command will block at this point
+//! ```
+//!
+//! The OpenOCD logs will be redirected to `/tmp/openocd.log`. You can view
+//! those logs in "real time" using `tail`
+//!
+//! ``` text
+//! $ tail -f /tmp/openocd.log
+//! Info : Unable to match requested speed 1000 kHz, using 950 kHz
+//! Info : Unable to match requested speed 1000 kHz, using 950 kHz
+//! Info : clock speed 950 kHz
+//! Info : STLINK v1 JTAG v11 API v2 SWIM v0 VID 0x0483 PID 0x3744
+//! Info : using stlink api v2
+//! Info : nrf51.cpu: hardware has 4 breakpoints, 2 watchpoints
+//! ```
+//!
+//! Alternatively you could omit the `-l` flag from the `openocd` call, and the
+//! `tail -f` command but the OpenOCD output will have intermingled in it logs
+//! from its normal operation.
+//!
+//! Then, we run the program:
+//!
+//! ``` text
+//! $ arm-none-eabi-gdb hello-world
+//! (gdb) # Connect to OpenOCD
+//! (gdb) target remote :3333
+//!
+//! (gdb) # Enable OpenOCD's semihosting support
+//! (gdb) monitor arm semihosting enable
+//!
+//! (gdb) # Flash the program
+//! (gdb) load
+//!
+//! (gdb) # Run the program
+//! (gdb) continue
+//! ```
+//!
+//! And you'll see the output under OpenOCD's terminal
+//!
+//! ``` text
+//! # openocd -f $INTERFACE -f $TARGET -l /tmp/openocd.log
+//! (..)
+//! Rust on embedded is #1!
+//! ```
+//! ## Using the syscall interface
+//!
+//! This example will show how to print "Hello, world!" on the host.
+//!
+//! Target program:
+//!
+//! ```no_run
+//! use riscv_semihosting::syscall;
+//!
+//! // This function will be called by the application
+//! fn print() {
+//!     // File descriptor (on the host)
+//!     const STDOUT: usize = 1; // NOTE the host stdout may not always be fd 1
+//!     static MSG: &'static [u8] = b"Hello, world!\n";
+//!
+//!     // Signature: fn write(fd: usize, ptr: *const u8, len: usize) -> usize
+//!     let r = unsafe { syscall!(WRITE, STDOUT, MSG.as_ptr(), MSG.len()) };
+//! }
+//! ```
+//! Output and monitoring proceed as in the above example.
+//!
+//! ## The `dbg!` macro
+//!
+//! Analogous to [`std::dbg`](https://doc.rust-lang.org/std/macro.dbg.html) the
+//! macro `dbg!` returns a given expression and prints it using `heprintln!`
+//! including context for quick and dirty debugging.
+//!
+//! Panics if `heprintln!` returns an error.
+//!
+//! Example:
+//!
+//! ```no_run
+//! const UUID: *mut u32 = 0x0009_FC70 as *mut u32;
+//! dbg!(UUID);
+//! let mut uuid: [u32; 4] = [0; 4];
+//! for i in 0..4 {
+//!     dbg!(i);
+//!     uuid[i] = unsafe { dbg!(UUID.offset(i as isize).read_volatile()) };
+//! }
+//! ```
+//! outputs
+//! ```text
+//! [examples/semihosting.rs:37] UUID = 0x0009fc70
+//! [examples/semihosting.rs:40] i = 0
+//! [examples/semihosting.rs:41] UUID.offset(i as isize).read_volatile() = 3370045464
+//! [examples/semihosting.rs:40] i = 1
+//! [examples/semihosting.rs:41] UUID.offset(i as isize).read_volatile() = 1426218275
+//! [examples/semihosting.rs:40] i = 2
+//! [examples/semihosting.rs:41] UUID.offset(i as isize).read_volatile() = 2422621116
+//! [examples/semihosting.rs:40] i = 3
+//! [examples/semihosting.rs:41] UUID.offset(i as isize).read_volatile() = 1044138593
+//! ```
+//!
+//! # Optional features
+//!
+//! ## `jlink-quirks`
+//!
+//! When this feature is enabled, return values above `0xfffffff0` from
+//! semihosting operation `SYS_WRITE` (0x05) are interpreted as if the entire
+//! buffer had been written. The current latest version 6.48b of J-Link exhibits
+//! such behaviour, causing a panic if this feature is not enabled.
+//!
+//! ## `no-semihosting`
+//!
+//! When this feature is enabled, the underlying system calls are patched out.
+//!
+//! # Reference
+//!
+//! For documentation about the semihosting operations, check
+//! ['Semihosting for AArch32 and AArch64'](https://github.com/ARM-software/abi-aa/blob/main/semihosting/semihosting.rst).
+//! The RISC-V Semihosting spec is identical to Arm's with the exception of the
+//! assembly sequence necessary to trigger a semihosting call, so their
+//! documentation is sufficient.
+
+#![deny(missing_docs)]
+#![no_std]
+
+#[cfg(all(riscv, not(feature = "no-semihosting")))]
+use core::arch::asm;
+
+#[macro_use]
+mod macros;
+
+pub mod debug;
+#[doc(hidden)]
+pub mod export;
+pub mod hio;
+pub mod nr;
+
+/// Performs a semihosting operation, takes a pointer to an argument block
+///
+/// # Safety
+///
+/// The syscall number must be a valid [semihosting operation],
+/// and the arguments must be valid for the associated operation.
+///
+/// [semihosting operation]: https://developer.arm.com/documentation/dui0471/i/semihosting/semihosting-operations?lang=en
+#[inline(always)]
+pub unsafe fn syscall<T>(nr: usize, arg: &T) -> usize {
+    syscall1(nr, arg as *const T as usize)
+}
+
+/// Performs a semihosting operation, takes one integer as an argument
+///
+/// # Safety
+///
+/// Same as [`syscall`].
+#[inline(always)]
+pub unsafe fn syscall1(_nr: usize, _arg: usize) -> usize {
+    match () {
+        #[cfg(all(riscv, not(feature = "no-semihosting")))]
+        () => {
+            let mut nr = _nr;
+            let mut arg = _arg;
+            // The instructions below must always be uncompressed, otherwise
+            // it will be treated as a regular break, hence the norvc option.
+            //
+            // See https://github.com/riscv/riscv-semihosting-spec for more details.
+            asm!("
+                .balign 16
+                .option push
+                .option norvc
+                slli x0, x0, 0x1f
+                ebreak
+                srai x0, x0, 0x7
+                .option pop
+            ",
+            inout("a0") nr,
+            inout("a1") arg => _,
+            options(nostack, preserves_flags),
+            );
+            nr
+        }
+        #[cfg(all(riscv, feature = "no-semihosting"))]
+        () => 0,
+        #[cfg(not(riscv))]
+        () => unimplemented!(),
+    }
+}

+ 120 - 0
riscv-semihosting/src/macros.rs

@@ -0,0 +1,120 @@
+/// Variable argument version of `syscall`
+#[macro_export]
+macro_rules! syscall {
+    ($nr:ident) => {
+        $crate::syscall1($crate::nr::$nr, 0)
+    };
+    ($nr:ident, $a1:expr) => {
+        $crate::syscall($crate::nr::$nr, &[$a1 as usize])
+    };
+    ($nr:ident, $a1:expr, $a2:expr) => {
+        $crate::syscall($crate::nr::$nr, &[$a1 as usize, $a2 as usize])
+    };
+    ($nr:ident, $a1:expr, $a2:expr, $a3:expr) => {
+        $crate::syscall($crate::nr::$nr, &[$a1 as usize, $a2 as usize, $a3 as usize])
+    };
+    ($nr:ident, $a1:expr, $a2:expr, $a3:expr, $a4:expr) => {
+        $crate::syscall(
+            $crate::nr::$nr,
+            &[$a1 as usize, $a2 as usize, $a3 as usize, $a4 as usize],
+        )
+    };
+}
+
+/// Macro version of `syscall1`.
+#[macro_export]
+macro_rules! syscall1 {
+    ($nr:ident, $a1:expr) => {
+        $crate::syscall1($crate::nr::$nr, $a1 as usize)
+    };
+}
+
+/// Macro for printing to the HOST standard output.
+///
+/// This is similar to the `print!` macro in the standard library. Both will panic on any failure to
+/// print.
+#[macro_export]
+macro_rules! hprint {
+    ($s:expr) => {
+        $crate::export::hstdout_str($s)
+    };
+    ($($tt:tt)*) => {
+        $crate::export::hstdout_fmt(format_args!($($tt)*))
+    };
+}
+
+/// Macro for printing to the HOST standard output, with a newline.
+///
+/// This is similar to the `println!` macro in the standard library. Both will panic on any failure to
+/// print.
+#[macro_export]
+macro_rules! hprintln {
+    () => {
+        $crate::export::hstdout_str("\n")
+    };
+    ($s:expr) => {
+        $crate::export::hstdout_str(concat!($s, "\n"))
+    };
+    ($s:expr, $($tt:tt)*) => {
+        $crate::export::hstdout_fmt(format_args!(concat!($s, "\n"), $($tt)*))
+    };
+}
+
+/// Macro for printing to the HOST standard error.
+///
+/// This is similar to the `eprint!` macro in the standard library. Both will panic on any failure
+/// to print.
+#[macro_export]
+macro_rules! heprint {
+    ($s:expr) => {
+        $crate::export::hstderr_str($s)
+    };
+    ($($tt:tt)*) => {
+        $crate::export::hstderr_fmt(format_args!($($tt)*))
+    };
+}
+
+/// Macro for printing to the HOST standard error, with a newline.
+///
+/// This is similar to the `eprintln!` macro in the standard library. Both will panic on any failure
+/// to print.
+#[macro_export]
+macro_rules! heprintln {
+    () => {
+        $crate::export::hstderr_str("\n")
+    };
+    ($s:expr) => {
+        $crate::export::hstderr_str(concat!($s, "\n"))
+    };
+    ($s:expr, $($tt:tt)*) => {
+        $crate::export::hstderr_fmt(format_args!(concat!($s, "\n"), $($tt)*))
+    };
+}
+
+/// Macro that prints and returns the value of a given expression for quick and
+/// dirty debugging.
+///
+/// Works exactly like `dbg!` in the standard library, replacing `eprintln!`
+/// with `heprintln!`.
+#[macro_export]
+macro_rules! dbg {
+    () => {
+        $crate::heprintln!("[{}:{}]", file!(), line!());
+    };
+    ($val:expr) => {
+        // Use of `match` here is intentional because it affects the lifetimes
+        // of temporaries - https://stackoverflow.com/a/48732525/1063961
+        match $val {
+            tmp => {
+                $crate::heprintln!("[{}:{}] {} = {:#?}",
+                    file!(), line!(), stringify!($val), &tmp);
+                tmp
+            }
+        }
+    };
+    // Trailing comma with single argument is ignored
+    ($val:expr,) => { $crate::dbg!($val) };
+    ($($val:expr),+ $(,)?) => {
+        ($($crate::dbg!($val)),+,)
+    };
+}

+ 61 - 0
riscv-semihosting/src/nr.rs

@@ -0,0 +1,61 @@
+//! Semihosting operations
+//!
+//! The details of what each operation does can be found in the
+//! [ARM Semihosting Specification](https://github.com/ARM-software/abi-aa/blob/main/semihosting/semihosting.rst#semihosting-operations).
+//! The RISC-V Semihosting operations are identiacal to ARM's, so their
+//! documentation is sufficient.
+
+#![allow(missing_docs)]
+
+pub const CLOCK: usize = 0x10;
+pub const CLOSE: usize = 0x02;
+pub const ELAPSED: usize = 0x30;
+pub const ERRNO: usize = 0x13;
+pub const FLEN: usize = 0x0c;
+pub const GET_CMDLINE: usize = 0x15;
+pub const HEAPINFO: usize = 0x16;
+pub const ISERROR: usize = 0x08;
+pub const ISTTY: usize = 0x09;
+pub const OPEN: usize = 0x01;
+pub const READ: usize = 0x06;
+pub const READC: usize = 0x07;
+pub const REMOVE: usize = 0x0e;
+pub const RENAME: usize = 0x0f;
+pub const SEEK: usize = 0x0a;
+pub const SYSTEM: usize = 0x12;
+pub const TICKFREQ: usize = 0x31;
+pub const TIME: usize = 0x11;
+pub const TMPNAM: usize = 0x0d;
+pub const WRITE0: usize = 0x04;
+pub const WRITE: usize = 0x05;
+pub const WRITEC: usize = 0x03;
+pub const ENTER_SVC: usize = 0x17;
+pub const REPORT_EXCEPTION: usize = 0x18;
+
+/// Values for the mode parameter of the OPEN syscall.
+pub mod open {
+    /// Mode corresponding to fopen "r" mode.
+    pub const R: usize = 0;
+    /// Mode corresponding to fopen "rb" mode.
+    pub const R_BINARY: usize = 1;
+    /// Mode corresponding to fopen "r+" mode.
+    pub const RW: usize = 2;
+    /// Mode corresponding to fopen "r+b" mode.
+    pub const RW_BINARY: usize = 3;
+    /// Mode corresponding to fopen "w" mode.
+    pub const W_TRUNC: usize = 4;
+    /// Mode corresponding to fopen "wb" mode.
+    pub const W_TRUNC_BINARY: usize = 5;
+    /// Mode corresponding to fopen "w+" mode.
+    pub const RW_TRUNC: usize = 6;
+    /// Mode corresponding to fopen "w+b" mode.
+    pub const RW_TRUNC_BINARY: usize = 7;
+    /// Mode corresponding to fopen "a" mode.
+    pub const W_APPEND: usize = 8;
+    /// Mode corresponding to fopen "ab" mode.
+    pub const W_APPEND_BINARY: usize = 9;
+    /// Mode corresponding to fopen "a+" mode.
+    pub const RW_APPEND: usize = 10;
+    /// Mode corresponding to fopen "a+b" mode.
+    pub const RW_APPEND_BINARY: usize = 11;
+}

+ 5 - 1
riscv/CHANGELOG.md

@@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 
 ### Added
 
+- Add `asm::ecall()`, a wrapper for implementing an `ecall` instruction
+- Add `nested` function for nested ISRs in `interrupt::machine` and `interrupt::supervisor`
+- `s-mode` feature for reexporting `interrupt::machine` or `interrupt::supervisor` to `interrupt`
+- Support for supervisor-level interrupts in `interrupt::supervisor`
 - Add CI workflow to check that CHANGELOG.md file has been modified in PRs
 - Add `read_csr_as_rv32`, `set_rv32`, and `clear_rv32` macros
 - Add `mstatus::uxl` and `mstatus::sxl`
@@ -21,7 +25,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 ### Changed
 
 - Cargo workspace for riscv and riscv-rt
-- Update `embedded-hal` dependency to v1.0.0-rc.2 (bumps MSRV to 1.60)
+- Update `embedded-hal` dependency to v1.0.0 (bumps MSRV to 1.60)
 - `misa::MXL` renamed to `misa::XLEN`
 - Removed `bit_field` dependency
 - CI actions updated. They now use `checkout@v3` and `dtolnay/rust-toolchain`.

+ 2 - 1
riscv/Cargo.toml

@@ -20,8 +20,9 @@ targets = [
 ]
 
 [features]
+s-mode = []
 critical-section-single-hart = ["critical-section/restore-state-bool"]
 
 [dependencies]
 critical-section = "1.1.2"
-embedded-hal = "1.0.0-rc.2"
+embedded-hal = "1.0.0"

+ 22 - 0
riscv/src/asm.rs

@@ -110,6 +110,28 @@ pub unsafe fn sfence_vma(asid: usize, addr: usize) {
     }
 }
 
+/// `ECALL` instruction wrapper
+///
+/// Generates an exception for a service request to the execution environment.
+/// When executed in U-mode, S-mode, or M-mode, it generates an environment-call-from-U-mode
+/// exception, environment-call-from-S-mode exception, or environment-call-from-M-mode exception,
+/// respectively, and performs no other operation.
+///
+/// # Note
+///
+/// The ECALL instruction will **NOT** save and restore the stack pointer, as it triggers an exception.
+/// The stack pointer must be saved and restored accordingly by the exception handler.
+#[inline]
+pub unsafe fn ecall() {
+    match () {
+        #[cfg(riscv)]
+        () => core::arch::asm!("ecall", options(nostack)),
+
+        #[cfg(not(riscv))]
+        () => unimplemented!(),
+    }
+}
+
 /// Blocks the program for *at least* `cycles` CPU cycles.
 ///
 /// This is implemented in assembly so its execution time is independent of the optimization

+ 9 - 2
riscv/src/critical_section.rs

@@ -1,16 +1,23 @@
 use critical_section::{set_impl, Impl, RawRestoreState};
 
 use crate::interrupt;
-use crate::register::mstatus;
 
 struct SingleHartCriticalSection;
 set_impl!(SingleHartCriticalSection);
 
 unsafe impl Impl for SingleHartCriticalSection {
+    #[cfg(not(feature = "s-mode"))]
     unsafe fn acquire() -> RawRestoreState {
         let mut mstatus: usize;
         core::arch::asm!("csrrci {}, mstatus, 0b1000", out(reg) mstatus);
-        core::mem::transmute::<_, mstatus::Mstatus>(mstatus).mie()
+        core::mem::transmute::<_, crate::register::mstatus::Mstatus>(mstatus).mie()
+    }
+
+    #[cfg(feature = "s-mode")]
+    unsafe fn acquire() -> RawRestoreState {
+        let mut sstatus: usize;
+        core::arch::asm!("csrrci {}, sstatus, 0b0010", out(reg) sstatus);
+        core::mem::transmute::<_, crate::register::sstatus::Sstatus>(sstatus).sie()
     }
 
     unsafe fn release(was_active: RawRestoreState) {

+ 172 - 47
riscv/src/interrupt.rs

@@ -1,63 +1,188 @@
 //! Interrupts
 
 // NOTE: Adapted from cortex-m/src/interrupt.rs
-use crate::register::mstatus;
 
-/// Disables all interrupts in the current hart.
-#[inline]
-pub unsafe fn disable() {
-    match () {
-        #[cfg(riscv)]
-        () => mstatus::clear_mie(),
-        #[cfg(not(riscv))]
-        () => unimplemented!(),
+pub mod machine {
+    use crate::register::{mepc, mstatus};
+
+    /// Disables all interrupts in the current hart (machine mode).
+    #[inline]
+    pub fn disable() {
+        // SAFETY: It is safe to disable interrupts
+        unsafe { mstatus::clear_mie() }
     }
-}
 
-/// Enables all the interrupts in the current hart.
-///
-/// # Safety
-///
-/// - Do not call this function inside a critical section.
-#[inline]
-pub unsafe fn enable() {
-    match () {
-        #[cfg(riscv)]
-        () => mstatus::set_mie(),
-        #[cfg(not(riscv))]
-        () => unimplemented!(),
+    /// Enables all the interrupts in the current hart (machine mode).
+    ///
+    /// # Safety
+    ///
+    /// Do not call this function inside a critical section.
+    #[inline]
+    pub unsafe fn enable() {
+        mstatus::set_mie()
     }
-}
 
-/// Execute closure `f` with interrupts disabled in the current hart.
-///
-/// This method does not synchronise multiple harts, so it is not suitable for
-/// using as a critical section. See the `critical-section` crate for a cross-platform
-/// way to enter a critical section which provides a `CriticalSection` token.
-///
-/// This crate provides an implementation for `critical-section` suitable for single-hart systems,
-/// based on disabling all interrupts. It can be enabled with the `critical-section-single-hart` feature.
-#[inline]
-pub fn free<F, R>(f: F) -> R
-where
-    F: FnOnce() -> R,
-{
-    let mstatus = mstatus::read();
-
-    // disable interrupts
-    unsafe {
+    /// Execute closure `f` with interrupts disabled in the current hart (machine mode).
+    ///
+    /// This method does not synchronise multiple harts, so it is not suitable for
+    /// using as a critical section. See the `critical-section` crate for a cross-platform
+    /// way to enter a critical section which provides a `CriticalSection` token.
+    ///
+    /// This crate provides an implementation for `critical-section` suitable for single-hart systems,
+    /// based on disabling all interrupts. It can be enabled with the `critical-section-single-hart` feature.
+    #[inline]
+    pub fn free<F, R>(f: F) -> R
+    where
+        F: FnOnce() -> R,
+    {
+        let mstatus = mstatus::read();
+
+        // disable interrupts
         disable();
+
+        let r = f();
+
+        // If the interrupts were active before our `disable` call, then re-enable
+        // them. Otherwise, keep them disabled
+        if mstatus.mie() {
+            unsafe { enable() };
+        }
+
+        r
     }
 
-    let r = f();
+    /// Execute closure `f` with interrupts enabled in the current hart (machine mode).
+    ///
+    /// This method is assumed to be called within an interrupt handler, and allows
+    /// nested interrupts to occur. After the closure `f` is executed, the [`mstatus`]
+    /// and [`mepc`] registers are properly restored to their previous values.
+    ///
+    /// # Safety
+    ///
+    /// - Do not call this function inside a critical section.
+    /// - This method is assumed to be called within an interrupt handler.
+    /// - Make sure to clear the interrupt flag that caused the interrupt before calling
+    /// this method. Otherwise, the interrupt will be re-triggered before executing `f`.
+    #[inline]
+    pub unsafe fn nested<F, R>(f: F) -> R
+    where
+        F: FnOnce() -> R,
+    {
+        let mstatus = mstatus::read();
+        let mepc = mepc::read();
+
+        // enable interrupts to allow nested interrupts
+        enable();
 
-    // If the interrupts were active before our `disable` call, then re-enable
-    // them. Otherwise, keep them disabled
-    if mstatus.mie() {
-        unsafe {
-            enable();
+        let r = f();
+
+        // If the interrupts were inactive before our `enable` call, then re-disable
+        // them. Otherwise, keep them enabled
+        if !mstatus.mie() {
+            disable();
+        }
+
+        // Restore MSTATUS.PIE, MSTATUS.MPP, and SEPC
+        if mstatus.mpie() {
+            mstatus::set_mpie();
         }
+        mstatus::set_mpp(mstatus.mpp());
+        mepc::write(mepc);
+
+        r
+    }
+}
+pub mod supervisor {
+    use crate::register::{sepc, sstatus};
+
+    /// Disables all interrupts in the current hart (supervisor mode).
+    #[inline]
+    pub fn disable() {
+        // SAFETY: It is safe to disable interrupts
+        unsafe { sstatus::clear_sie() }
+    }
+
+    /// Enables all the interrupts in the current hart (supervisor mode).
+    ///
+    /// # Safety
+    ///
+    /// Do not call this function inside a critical section.
+    #[inline]
+    pub unsafe fn enable() {
+        sstatus::set_sie()
     }
 
-    r
+    /// Execute closure `f` with interrupts disabled in the current hart (supervisor mode).
+    ///
+    /// This method does not synchronise multiple harts, so it is not suitable for
+    /// using as a critical section. See the `critical-section` crate for a cross-platform
+    /// way to enter a critical section which provides a `CriticalSection` token.
+    ///
+    /// This crate provides an implementation for `critical-section` suitable for single-hart systems,
+    /// based on disabling all interrupts. It can be enabled with the `critical-section-single-hart` feature.
+    #[inline]
+    pub fn free<F, R>(f: F) -> R
+    where
+        F: FnOnce() -> R,
+    {
+        let sstatus = sstatus::read();
+
+        // disable interrupts
+        disable();
+
+        let r = f();
+
+        // If the interrupts were active before our `disable` call, then re-enable
+        // them. Otherwise, keep them disabled
+        if sstatus.sie() {
+            unsafe { enable() };
+        }
+
+        r
+    }
+
+    /// Execute closure `f` with interrupts enabled in the current hart (supervisor mode).
+    /// This method is assumed to be called within an interrupt handler, and allows
+    /// nested interrupts to occur. After the closure `f` is executed, the [`sstatus`]
+    /// and [`sepc`] registers are properly restored to their previous values.
+    ///
+    /// # Safety
+    ///
+    /// - Do not call this function inside a critical section.
+    /// - This method is assumed to be called within an interrupt handler.
+    /// - Make sure to clear the interrupt flag that caused the interrupt before calling
+    /// this method. Otherwise, the interrupt will be re-triggered before executing `f`.
+    #[inline]
+    pub unsafe fn nested<F, R>(f: F) -> R
+    where
+        F: FnOnce() -> R,
+    {
+        let sstatus = sstatus::read();
+        let sepc = sepc::read();
+
+        // enable interrupts to allow nested interrupts
+        enable();
+
+        let r = f();
+
+        // If the interrupts were inactive before our `enable` call, then re-disable
+        // them. Otherwise, keep them enabled
+        if !sstatus.sie() {
+            disable();
+        }
+
+        // Restore SSTATUS.SPIE, SSTATUS.SPP, and SEPC
+        if sstatus.spie() {
+            sstatus::set_spie();
+        }
+        sstatus::set_spp(sstatus.spp());
+        sepc::write(sepc);
+
+        r
+    }
 }
+
+#[cfg(not(feature = "s-mode"))]
+pub use machine::*;
+#[cfg(feature = "s-mode")]
+pub use supervisor::*;

+ 7 - 0
riscv/src/lib.rs

@@ -15,10 +15,17 @@
 //!
 //! # Optional features
 //!
+//! ## `s-mode`
+//!
+//! This feature re-exports in `interrupt` S-mode interrupt functions defined in `interrupt::supervisor`.
+//! By default, the crate assumes that the target is running in M-mode.
+//! Thus, `interrupt` re-exports the M-mode functions defined in `interrupt::machine`.
+//!
 //! ## `critical-section-single-hart`
 //!
 //! This feature enables a [`critical-section`](https://github.com/rust-embedded/critical-section)
 //! implementation suitable for single-hart targets, based on disabling interrupts globally.
+//! This feature uses S-mode interrupt handling if the `s-mode` feature is enabled, and M-mode otherwise.
 //!
 //! It is **unsound** to enable it on multi-hart targets,
 //! and may cause functional problems in systems where some interrupts must NOT be disabled

+ 2 - 2
riscv/src/register/misa.rs

@@ -46,8 +46,8 @@ impl Misa {
     ///
     /// # Example
     ///
-    /// ``` no_run
-    /// let misa = unsafe { riscv::register::misa::read() };
+    /// ```no_run
+    /// let misa = unsafe { riscv::register::misa::read() }.unwrap();
     /// assert!(misa.has_extension('A')); // panics if atomic extension is not implemented
     /// ```
     #[inline]