Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

riscv: ECALL and nested interrupts #169

Merged
merged 2 commits into from
Dec 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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