Skip to content

Commit

Permalink
Merge pull request #169 from rust-embedded/add-ecall
Browse files Browse the repository at this point in the history
`riscv`: ECALL and nested interrupts
  • Loading branch information
romancardenas authored Dec 31, 2023
2 parents 0a021ea + c6231ce commit 0f8305b
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 2 deletions.
2 changes: 2 additions & 0 deletions riscv/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ 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
Expand Down
22 changes: 22 additions & 0 deletions riscv/src/asm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
87 changes: 85 additions & 2 deletions riscv/src/interrupt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
// NOTE: Adapted from cortex-m/src/interrupt.rs

pub mod machine {
use crate::register::mstatus;
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() }
}

Expand Down Expand Up @@ -49,13 +50,55 @@ pub mod machine {

r
}

/// 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();

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::sstatus;
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() }
}

Expand Down Expand Up @@ -97,6 +140,46 @@ pub mod supervisor {

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"))]
Expand Down

0 comments on commit 0f8305b

Please sign in to comment.