From 149d917c910d056449b8571fa84682c51a69f407 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas?= Date: Thu, 28 Dec 2023 12:57:08 +0100 Subject: [PATCH 1/2] ECALL and nested interrupts --- riscv/CHANGELOG.md | 2 + riscv/src/asm.rs | 22 +++++++++++ riscv/src/interrupt.rs | 87 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 109 insertions(+), 2 deletions(-) diff --git a/riscv/CHANGELOG.md b/riscv/CHANGELOG.md index 5208bac7..271f7b2e 100644 --- a/riscv/CHANGELOG.md +++ b/riscv/CHANGELOG.md @@ -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 diff --git a/riscv/src/asm.rs b/riscv/src/asm.rs index 37530c9d..408467b7 100644 --- a/riscv/src/asm.rs +++ b/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 diff --git a/riscv/src/interrupt.rs b/riscv/src/interrupt.rs index f83558b8..324983e4 100644 --- a/riscv/src/interrupt.rs +++ b/riscv/src/interrupt.rs @@ -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() } } @@ -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: 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() } } @@ -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: 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"))] From c6231cedbc897f736278a8f42d3e8e58fee583fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas?= Date: Fri, 29 Dec 2023 13:55:19 +0100 Subject: [PATCH 2/2] bug fix --- riscv/src/interrupt.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/riscv/src/interrupt.rs b/riscv/src/interrupt.rs index 324983e4..fefdb14b 100644 --- a/riscv/src/interrupt.rs +++ b/riscv/src/interrupt.rs @@ -83,7 +83,7 @@ pub mod machine { } // Restore MSTATUS.PIE, MSTATUS.MPP, and SEPC - if !mstatus.mpie() { + if mstatus.mpie() { mstatus::set_mpie(); } mstatus::set_mpp(mstatus.mpp()); @@ -172,7 +172,7 @@ pub mod supervisor { } // Restore SSTATUS.SPIE, SSTATUS.SPP, and SEPC - if !sstatus.spie() { + if sstatus.spie() { sstatus::set_spie(); } sstatus::set_spp(sstatus.spp());