diff --git a/hifive1-test/examples/clint.rs b/hifive1-test/examples/clint.rs index 0021420..b842ff9 100644 --- a/hifive1-test/examples/clint.rs +++ b/hifive1-test/examples/clint.rs @@ -101,7 +101,13 @@ fn main() -> ! { } //let mut delay = CLINT::delay(); loop { + // read stack pointer + let pc: usize; + unsafe { + core::arch::asm!("mv {}, sp", out(reg) pc); + } + sprintln!("Program counter: {:x}", pc); sprintln!("Going to sleep!"); - unsafe { riscv_slic::riscv::asm::wfi() }; + riscv_slic::riscv::asm::wfi(); } } diff --git a/hifive1-test/examples/expansion.rs b/hifive1-test/examples/expansion.rs deleted file mode 100644 index fab1fc8..0000000 --- a/hifive1-test/examples/expansion.rs +++ /dev/null @@ -1,162 +0,0 @@ -#![no_std] -#![no_main] -extern crate riscv_slic; - -// Recursive expansion of codegen! macro -// ====================================== - -pub mod slic { - use super::riscv_slic::*; - use riscv_slic::InterruptNumber; - #[doc = r" Returns the current priority threshold of the SLIC."] - #[doc = r""] - #[doc = r" # Safety"] - #[doc = r""] - #[doc = r" This function is only for `riscv-slic` internal use. Do not call it directly."] - #[inline] - #[no_mangle] - pub unsafe fn __riscv_slic_get_threshold() -> u8 { - critical_section::with(|cs| __SLIC.borrow_ref(cs).get_threshold()) - } - #[doc = r" Sets the priority threshold of the SLIC."] - #[doc = r""] - #[doc = r" # Safety"] - #[doc = r""] - #[doc = r" This function is only for `riscv-slic` internal use. Do not call it directly."] - #[inline] - #[no_mangle] - pub unsafe fn __riscv_slic_set_threshold(thresh: u8) { - critical_section::with(|cs| { - let mut slic = __SLIC.borrow_ref_mut(cs); - slic.set_threshold(thresh); - if slic.is_ready() { - __riscv_slic_swi_pend(); - } - }); - } - #[doc = r" Returns the interrupt priority of a given software interrupt source."] - #[doc = r""] - #[doc = r" # Safety"] - #[doc = r""] - #[doc = r" This function is only for `riscv-slic` internal use. Do not call it directly."] - #[inline] - #[no_mangle] - pub unsafe fn __riscv_slic_get_priority(interrupt: u16) -> u8 { - critical_section::with(|cs| __SLIC.borrow_ref(cs).get_priority(interrupt)) - } - #[doc = r" Sets the interrupt priority of a given software interrupt source in the SLIC."] - #[doc = r""] - #[doc = r" # Safety"] - #[doc = r""] - #[doc = r" This function is only for `riscv-slic` internal use. Do not call it directly."] - #[inline] - #[no_mangle] - pub unsafe fn __riscv_slic_set_priority(interrupt: u16, priority: u8) { - critical_section::with(|cs| __SLIC.borrow_ref_mut(cs).set_priority(interrupt, priority)); - } - #[doc = r" Marks a software interrupt as pending."] - #[doc = r""] - #[doc = r" # Safety"] - #[doc = r""] - #[doc = r" This function is only for `riscv-slic` internal use. Do not call it directly."] - #[inline] - #[no_mangle] - pub unsafe fn __riscv_slic_pend(interrupt: u16) { - critical_section::with(|cs| { - let mut slic = __SLIC.borrow_ref_mut(cs); - slic.pend(interrupt); - if slic.is_ready() { - __riscv_slic_swi_pend(); - } - }); - } - #[doc = r" Polls the SLIC for pending software interrupts and runs them."] - #[doc = r""] - #[doc = r" # Safety"] - #[doc = r""] - #[doc = r" This function is only for `riscv-slic` internal use. Do not call it directly."] - #[inline] - #[no_mangle] - pub unsafe fn __riscv_slic_run() { - loop { - let (priority, interrupt) = critical_section::with(|cs| { - let mut slic = __SLIC.borrow_ref_mut(cs); - match slic.pop() { - Some((priority, interrupt)) => { - slic.set_threshold(priority); - Some((priority, interrupt)) - } - None => None, - } - }); - if let Some((priority, interrupt)) = (priority, interrupt) { - riscv_slic::run(priority, || __SOFTWARE_INTERRUPTS[interrupt as usize]()); - } - } - } - #[doc = r" Triggers a machine software interrupt via the CLINT peripheral."] - #[doc = r""] - #[doc = r" # Safety"] - #[doc = r""] - #[doc = r" This function is only for `riscv-slic` internal use. Do not call it directly."] - #[inline] - #[no_mangle] - pub unsafe fn __riscv_slic_swi_pend() { - let msip = e310x::CLINT::mswi().msip(e310x::HartId::HART0); - msip.pend(); - } - #[doc = r" Clears the Machine Software Interrupt Pending bit via the CLINT peripheral."] - #[doc = r""] - #[doc = r" # Safety"] - #[doc = r""] - #[doc = r" This function is only for `riscv-slic` internal use. Do not call it directly."] - #[inline] - #[no_mangle] - pub unsafe fn __riscv_slic_swi_unpend() { - let msip = e310x::CLINT::mswi().msip(e310x::HartId::HART0); - msip.unpend(); - } - #[doc = r" Enumeration of software interrupts"] - #[derive(Clone, Copy, Debug, Eq, PartialEq)] - #[repr(u16)] - pub enum Interrupt { - SoftLow = 0, - SoftMedium = 1, - SoftHigh = 2, - } - unsafe impl InterruptNumber for Interrupt { - const MAX_INTERRUPT_NUMBER: u16 = 3usize as u16 - 1; - #[inline] - fn number(self) -> u16 { - self as _ - } - #[inline] - fn from_number(value: u16) -> Result { - if value > Self::MAX_INTERRUPT_NUMBER { - Err(value) - } else { - Ok(unsafe { core::mem::transmute(value) }) - } - } - } - extern "C" { - fn SoftLow(); - - fn SoftMedium(); - - fn SoftHigh(); - - } - #[doc = r" Array of software interrupt handlers in the order of the `Interrupt` enum."] - static __SOFTWARE_INTERRUPTS: [unsafe extern "C" fn(); 3usize] = - [SoftLow, SoftMedium, SoftHigh]; - #[doc = r" The static SLIC instance"] - static mut __SLIC: MutexSLIC<3usize> = new_slic(); - #[doc = r" Software interrupt handler to be used with the SLIC."] - #[no_mangle] - #[allow(non_snake_case)] - unsafe fn MachineSoft() { - __riscv_slic_swi_unpend(); - riscv::interrupt::nested(|| unsafe { __riscv_slic_run() }); - } -} diff --git a/riscv-slic-macros/src/api.rs b/riscv-slic-macros/src/api.rs index 9e95320..8b0eb79 100644 --- a/riscv-slic-macros/src/api.rs +++ b/riscv-slic-macros/src/api.rs @@ -19,18 +19,45 @@ pub fn api_mod() -> TokenStream { /// # Safety /// /// This function is only for `riscv-slic` internal use. Do not call it directly. + /// + /// Setting the priority threshold to a value lower than the current threshold + /// may lead to priority inversion. If you want to make sure that the threshold + /// is only raised, use the [`__riscv_slic_raise_threshold`] function instead. #[inline] #[no_mangle] pub unsafe fn __riscv_slic_set_threshold(thresh: u8) { critical_section::with(|cs| { let mut slic = __SLIC.borrow_ref_mut(cs); slic.set_threshold(thresh); + // trigger a software interrupt if the SLIC is still ready at this point if slic.is_ready() { __riscv_slic_swi_pend(); } }); } + /// Raises the priority threshold of the SLIC only if the new threshold is higher than the current one. + /// + /// # Safety + /// + /// This function is only for `riscv-slic` internal use. Do not call it directly. + /// + /// This function is thought to be used as a way to temporarily raise the priority threshold. + /// You must return the previous threshold to the SLIC after you are done. + #[inline] + #[no_mangle] + pub unsafe fn __riscv_slic_raise_threshold(priority: u8) -> Result { + critical_section::with(|cs| { + let mut slic = __SLIC.borrow_ref_mut(cs); + let res = slic.raise_threshold(priority); + // trigger a software interrupt if the SLIC is still ready at this point + if slic.is_ready() { + __riscv_slic_swi_pend(); + } + res + }) + } + /// Returns the interrupt priority of a given software interrupt source. /// /// # Safety @@ -79,9 +106,24 @@ pub fn api_mod() -> TokenStream { /// This function is only for `riscv-slic` internal use. Do not call it directly. #[inline] #[no_mangle] - pub unsafe fn __riscv_slic_run() { - if let Some((pri, int)) = critical_section::with(|cs| __SLIC.borrow_ref_mut(cs).pop()) { - run(pri, || __SOFTWARE_INTERRUPTS[int as usize]()); + pub unsafe fn __riscv_slic_pop() { + // We check if there are pending software interrupts and run them + // Note that we must raise the threshold within the same critical section + // to avoid corner cases where another interrupt is raised in between. + if let Some((prev, int)) = critical_section::with(|cs| { + let mut slic = __SLIC.borrow_ref_mut(cs); + match slic.pop() { + Some((priority, interrupt)) => { + // SAFETY: we restore the previous threshold after the function is done + let previous = unsafe { slic.raise_threshold(priority).unwrap() }; // must be Ok if pop returned Some! + Some((previous, interrupt)) + } + None => None, + } + }) { + __SOFTWARE_INTERRUPTS[int as usize](); + // SAFETY: we restore the previous threshold after the function is done + unsafe { __riscv_slic_set_threshold(prev) }; } } ) diff --git a/riscv-slic-macros/src/swi.rs b/riscv-slic-macros/src/swi.rs index db392e8..619e731 100644 --- a/riscv-slic-macros/src/swi.rs +++ b/riscv-slic-macros/src/swi.rs @@ -76,7 +76,7 @@ pub fn swi_mod(input: &CodegenInput) -> TokenStream { #[allow(non_snake_case)] #swi_handler_signature { __riscv_slic_swi_unpend(); - riscv::interrupt::nested(|| unsafe { __riscv_slic_run() }); + riscv::interrupt::nested(|| unsafe { __riscv_slic_pop() }); } )); quote!(#(#res)*) diff --git a/riscv-slic/src/api.rs b/riscv-slic/src/api.rs index 35a4741..036f7ed 100644 --- a/riscv-slic/src/api.rs +++ b/riscv-slic/src/api.rs @@ -7,8 +7,9 @@ use riscv::register::sie::{clear_ssoft as disable_swi, set_ssoft as enable_swi}; extern "Rust" { fn __riscv_slic_swi_unpend(); - fn __riscv_slic_set_threshold(priority: u8); fn __riscv_slic_get_threshold() -> u8; + fn __riscv_slic_set_threshold(priority: u8); + fn __riscv_slic_raise_threshold(priority: u8) -> Result; fn __riscv_slic_get_priority(interrupt: u16) -> u8; fn __riscv_slic_set_priority(interrupt: u16, priority: u8); fn __riscv_slic_pend(interrupt: u16); @@ -27,7 +28,7 @@ pub fn clear_interrupts() { unsafe { disable_swi(); __riscv_slic_swi_unpend(); - set_threshold(u8::MAX); + __riscv_slic_set_threshold(u8::MAX); } } @@ -44,20 +45,10 @@ pub fn clear_interrupts() { /// This function may break mask-based critical sections. #[inline] pub unsafe fn set_interrupts() { - set_threshold(0); + __riscv_slic_set_threshold(0); enable_swi(); } -/// Stabilized API for changing the threshold of the SLIC. -/// -/// # Safety -/// -/// Changing the priority threshold may break mask-based critical sections. -#[inline] -pub unsafe fn set_threshold(priority: u8) { - __riscv_slic_set_threshold(priority); -} - /// Stabilized API for getting the current threshold of the SLIC. #[inline] pub fn get_threshold() -> u8 { @@ -65,11 +56,16 @@ pub fn get_threshold() -> u8 { unsafe { __riscv_slic_get_threshold() } } -/// Stabilized API for getting the priority of a given software interrupt source. +/// Stabilized API for setting the threshold of the SLIC. +/// +/// # Safety +/// +/// Setting the priority threshold to a value lower than the current threshold +/// may lead to priority inversion. If you want to make sure that the threshold +/// is only raised, use the [`raise_threshold`] function instead. #[inline] -pub fn get_priority(interrupt: I) -> u8 { - // SAFETY: this read has no side effects. - unsafe { __riscv_slic_get_priority(interrupt.number()) } +pub unsafe fn set_threshold(priority: u8) { + __riscv_slic_set_threshold(priority); } /// Stabilized API for setting the priority of a software interrupt of the SLIC. @@ -85,21 +81,20 @@ pub unsafe fn set_priority(interrupt: I, priority: u8 /// Stabilized API for pending a software interrupt on the SLIC. #[inline] pub fn pend(interrupt: I) { - // SAFETY: TODO + // SAFETY: it is safe to pend a software interrupt unsafe { __riscv_slic_pend(interrupt.number()) }; } /// Runs a function with priority mask. -/// -/// # Safety -/// -/// If new priority is less than current priority, priority inversion may occur. #[inline] -pub unsafe fn run(priority: u8, f: F) { - let current = get_threshold(); - set_threshold(priority); +pub fn run(priority: u8, f: F) { + // SAFETY: we restore the previous threshold after the function is done + let previous = unsafe { __riscv_slic_raise_threshold(priority) }; f(); - set_threshold(current); + if let Ok(prev) = previous { + // SAFETY: we restore the previous threshold after the function is done + unsafe { __riscv_slic_set_threshold(prev) }; + } } /// Runs a function that takes a shared resource with a priority ceiling. @@ -107,15 +102,19 @@ pub unsafe fn run(priority: u8, f: F) { /// /// # Safety /// -/// If ceiling is less than current priority, priority inversion may occur. +/// Input argument `ptr` must be a valid pointer to a shared resource. #[inline] pub unsafe fn lock(ptr: *mut T, ceiling: u8, f: F) -> R where F: FnOnce(&mut T) -> R, { - let current = get_threshold(); - set_threshold(ceiling); + // SAFETY: we restore the previous threshold after the function is done + let previous = unsafe { __riscv_slic_raise_threshold(ceiling) }; + // SAFETY: provided that caller respects the safety requirements, this is safe let r = f(&mut *ptr); - set_threshold(current); + if let Ok(prev) = previous { + // SAFETY: we restore the previous threshold after the function is done + unsafe { __riscv_slic_set_threshold(prev) }; + } r } diff --git a/riscv-slic/src/slic.rs b/riscv-slic/src/slic.rs index 2f295b5..104566f 100644 --- a/riscv-slic/src/slic.rs +++ b/riscv-slic/src/slic.rs @@ -63,11 +63,30 @@ impl SLIC { } /// Sets the priority threshold of the controller. + /// + /// # Safety + /// + /// Setting the priority threshold to a value lower than the current threshold + /// may lead to priority inversion. If you want to make sure that the threshold + /// is raised, use the [`raise_threshold`] method instead. #[inline] - pub fn set_threshold(&mut self, priority: u8) { + pub unsafe fn set_threshold(&mut self, priority: u8) { self.threshold = priority; } + /// Sets the priority threshold only to a higher value than the current threshold. + /// When the threshold is raised, the function returns `Ok(prev_threshold)`. + /// Otherwise, the threshold is not changed and `Err(())` is returned. + pub fn raise_threshold(&mut self, priority: u8) -> Result { + if priority > self.threshold { + let prev = self.threshold; + self.threshold = priority; + Ok(prev) + } else { + Err(()) + } + } + /// Checks if a given interrupt is pending. #[inline] pub fn is_pending(&mut self, interrupt: u16) -> bool {