From 924a7bde8f0d7db352e54aa48acba0706abf8575 Mon Sep 17 00:00:00 2001 From: Zhiyao Ma Date: Thu, 10 Oct 2024 15:41:23 -0400 Subject: [PATCH] Add test for panic upon erroneous blocking. --- .github/workflows/actions/build/action.yaml | 14 ++ .github/workflows/interrupt-unwind.yaml | 14 ++ .github/workflows/task-unwind.yaml | 13 ++ Cargo.toml | 8 + .../interrupt/unwind/panic_upon_blocking.rs | 179 ++++++++++++++++++ .../interrupt/unwind/panic_upon_blocking.txt | 6 + .../task/unwind/block_while_sched_suspend.rs | 117 ++++++++++++ .../task/unwind/block_while_sched_suspend.txt | 6 + src/sync/channel.rs | 6 +- src/sync/condvar.rs | 6 +- src/sync/mailbox.rs | 6 +- src/sync/semaphore.rs | 6 +- src/time/mod.rs | 8 +- 13 files changed, 379 insertions(+), 10 deletions(-) create mode 100644 examples/tests/interrupt/unwind/panic_upon_blocking.rs create mode 100644 examples/tests/interrupt/unwind/panic_upon_blocking.txt create mode 100644 examples/tests/task/unwind/block_while_sched_suspend.rs create mode 100644 examples/tests/task/unwind/block_while_sched_suspend.txt diff --git a/.github/workflows/actions/build/action.yaml b/.github/workflows/actions/build/action.yaml index bfde8e0..8bc509f 100644 --- a/.github/workflows/actions/build/action.yaml +++ b/.github/workflows/actions/build/action.yaml @@ -296,6 +296,13 @@ runs: sub-category: unwind test-name: concurrent_restart + - name: Build test test-task-unwind-block_while_sched_suspend + uses: ./.github/workflows/actions/build-test + with: + category: task + sub-category: unwind + test-name: block_while_sched_suspend + # *** Tests for task - segmented stack *** - name: Build test test-task-segmented_stack-function_arguments @@ -337,6 +344,13 @@ runs: sub-category: unwind test-name: simple + - name: Build test test-interrupt-unwind-panic_upon_blocking + uses: ./.github/workflows/actions/build-test + with: + category: interrupt + sub-category: unwind + test-name: panic_upon_blocking + # *** Tests for debug - cpu load *** - name: Build test test-debug-cpu_load-load_40_percent diff --git a/.github/workflows/interrupt-unwind.yaml b/.github/workflows/interrupt-unwind.yaml index d1d1886..74481c5 100644 --- a/.github/workflows/interrupt-unwind.yaml +++ b/.github/workflows/interrupt-unwind.yaml @@ -19,3 +19,17 @@ jobs: sub-category: unwind test-name: simple timeout: 15s + + panic_upon_blocking: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Run test panic_upon_blocking + uses: ./.github/workflows/actions/run-test + with: + category: interrupt + sub-category: unwind + test-name: panic_upon_blocking + timeout: 15s diff --git a/.github/workflows/task-unwind.yaml b/.github/workflows/task-unwind.yaml index 33afd15..9014fd6 100644 --- a/.github/workflows/task-unwind.yaml +++ b/.github/workflows/task-unwind.yaml @@ -70,3 +70,16 @@ jobs: category: task sub-category: unwind test-name: concurrent_restart + + block_while_sched_suspend: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Run test block_while_sched_suspend + uses: ./.github/workflows/actions/run-test + with: + category: task + sub-category: unwind + test-name: block_while_sched_suspend diff --git a/Cargo.toml b/Cargo.toml index 399494b..3a3395c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -258,6 +258,10 @@ path = "examples/tests/task/unwind/concurrent_restart.rs" name = "test-task-unwind-failed_concurrent_restart" path = "examples/tests/task/unwind/failed_concurrent_restart.rs" +[[example]] +name = "test-task-unwind-block_while_sched_suspend" +path = "examples/tests/task/unwind/block_while_sched_suspend.rs" + # *** Tests for task - segmented stack *** [[example]] @@ -284,6 +288,10 @@ path = "examples/tests/task/context_switch/fp_registers.rs" name = "test-interrupt-unwind-simple" path = "examples/tests/interrupt/unwind/simple.rs" +[[example]] +name = "test-interrupt-unwind-panic_upon_blocking" +path = "examples/tests/interrupt/unwind/panic_upon_blocking.rs" + # *** Tests for debug - cpu load *** [[example]] diff --git a/examples/tests/interrupt/unwind/panic_upon_blocking.rs b/examples/tests/interrupt/unwind/panic_upon_blocking.rs new file mode 100644 index 0000000..c3dd0e2 --- /dev/null +++ b/examples/tests/interrupt/unwind/panic_upon_blocking.rs @@ -0,0 +1,179 @@ +//! Tests that panics can be caught in an interrupt handler. + +#![no_main] +#![no_std] +#![feature(naked_functions)] +#![feature(asm_const)] + +extern crate alloc; + +use core::sync::atomic::{AtomicUsize, Ordering}; +use hopter::{ + debug::semihosting::{self, dbg_println}, + interrupt::declare::{handler, irq}, + sync::{self, CondVar, Consumer, Mailbox, Mutex, Semaphore, SpinIrqSafe}, + task::main, + time, +}; +use stm32f4xx_hal::{ + pac::{Interrupt, Peripherals, TIM2}, + prelude::*, + timer::{CounterUs, Event}, +}; + +irq!(Tim2Irq, Interrupt::TIM2); +static TIMER: SpinIrqSafe>, Tim2Irq> = SpinIrqSafe::new(None); + +static CONS: SpinIrqSafe>, Tim2Irq> = SpinIrqSafe::new(None); + +#[main] +fn main(_cp: cortex_m::Peripherals) { + let (prod, cons) = sync::create_channel::<(), 4>(); + { + CONS.lock().replace(cons); + } + + // Prevent the channel from being dropped during test. + core::mem::forget(prod); + + let dp = unsafe { Peripherals::steal() }; + + // For unknown reason QEMU accepts only the following clock frequency. + let rcc = dp.RCC.constrain(); + + #[cfg(feature = "qemu")] + let clocks = rcc.cfgr.sysclk(16.MHz()).pclk1(8.MHz()).freeze(); + #[cfg(feature = "stm32f411")] + let clocks = rcc + .cfgr + .use_hse(8.MHz()) + .sysclk(100.MHz()) + .pclk1(25.MHz()) + .pclk2(50.MHz()) + .freeze(); + #[cfg(feature = "stm32f407")] + let clocks = rcc + .cfgr + .use_hse(8.MHz()) + .sysclk(168.MHz()) + .pclk1(42.MHz()) + .pclk2(84.MHz()) + .freeze(); + + let mut timer = dp.TIM2.counter(&clocks); + + // Generate an interrupt when the timer expires. + timer.listen(Event::Update); + + // Enable TIM2 interrupt. + unsafe { + cortex_m::peripheral::NVIC::unmask(Interrupt::TIM2); + } + + // Set the timer to expire every 1 second. + // Empirically when set to 62 seconds the interval is actually + // approximately 1 second. Weird QEMU. + #[cfg(feature = "qemu")] + timer.start(62.secs()).unwrap(); + #[cfg(not(feature = "qemu"))] + timer.start(1.secs()).unwrap(); + + // Move the timer into the global storage to prevent it from being dropped. + *TIMER.lock() = Some(timer); +} + +/// Get invoked approximately every 1 second. +#[handler(TIM2)] +fn tim2_handler() { + TIMER.lock().as_mut().unwrap().wait().unwrap(); + + static IRQ_CNT: AtomicUsize = AtomicUsize::new(0); + let prev_cnt = IRQ_CNT.fetch_add(1, Ordering::SeqCst); + + match prev_cnt { + 0 => panic_with_semaphore(), + 1 => panic_with_mutex(), + 2 => panic_with_channel(), + 3 => panic_with_condvar(), + 4 => panic_with_mailbox(), + 5 => panic_with_sleep(), + _ => {} + } + + if prev_cnt >= 6 { + #[cfg(feature = "qemu")] + semihosting::terminate(true); + #[cfg(not(feature = "qemu"))] + { + dbg_println!("test complete!"); + loop {} + } + } +} + +fn panic_with_semaphore() { + static SEM: Semaphore = Semaphore::new(1, 0); + + let pod = PrintOnDrop("Panicked with semaphore."); + SEM.down(); + + // Don't print if not panic. + core::mem::forget(pod); +} + +fn panic_with_mutex() { + static MTX: Mutex<()> = Mutex::new(()); + + let pod = PrintOnDrop("Panicked with mutex."); + + let _mtx_gurad = MTX.lock(); + MTX.lock(); + + // Don't print if not panic. + core::mem::forget(pod); +} + +fn panic_with_channel() { + let cons = { CONS.lock().take().unwrap() }; + let pod = PrintOnDrop("Panicked with channel."); + cons.consume(); + + // Don't print if not panic. + core::mem::forget(pod); +} + +fn panic_with_condvar() { + static CV: CondVar = CondVar::new(); + + let pod = PrintOnDrop("Panicked with condvar."); + CV.wait_without_lock_until(|| false); + + // Don't print if not panic. + core::mem::forget(pod); +} + +fn panic_with_mailbox() { + static MAILBOX: Mailbox = Mailbox::new(); + + let pod = PrintOnDrop("Panicked with mailbox."); + MAILBOX.wait(); + + // Don't print if not panic. + core::mem::forget(pod); +} + +fn panic_with_sleep() { + let pod = PrintOnDrop("Panicked with sleep."); + time::sleep_ms(1000).unwrap(); + + // Don't print if not panic. + core::mem::forget(pod); +} + +struct PrintOnDrop(&'static str); + +impl Drop for PrintOnDrop { + fn drop(&mut self) { + dbg_println!("{}", self.0); + } +} diff --git a/examples/tests/interrupt/unwind/panic_upon_blocking.txt b/examples/tests/interrupt/unwind/panic_upon_blocking.txt new file mode 100644 index 0000000..0893296 --- /dev/null +++ b/examples/tests/interrupt/unwind/panic_upon_blocking.txt @@ -0,0 +1,6 @@ +Panicked with semaphore. +Panicked with mutex. +Panicked with channel. +Panicked with condvar. +Panicked with mailbox. +Panicked with sleep. diff --git a/examples/tests/task/unwind/block_while_sched_suspend.rs b/examples/tests/task/unwind/block_while_sched_suspend.rs new file mode 100644 index 0000000..989238c --- /dev/null +++ b/examples/tests/task/unwind/block_while_sched_suspend.rs @@ -0,0 +1,117 @@ +//! A forced unwinding should occur when a task without dynamic stack extension +//! is going to overflow its stack. + +#![no_std] +#![no_main] + +extern crate alloc; +use hopter::{ + debug::semihosting::{self, dbg_println}, + interrupt::mask::{AllIrqExceptSvc, MaskableIrq}, + sync::{self, CondVar, Mailbox, Mutex, Semaphore}, + task::{self, main}, + time, +}; +use hopter_conf_params::DEFAULT_TASK_PRIORITY; + +#[main] +fn main(_: cortex_m::Peripherals) { + task::change_current_priority(DEFAULT_TASK_PRIORITY + 1).unwrap(); + + task::build() + .set_entry(panic_with_semaphore) + .spawn() + .unwrap(); + task::build().set_entry(panic_with_mutex).spawn().unwrap(); + task::build().set_entry(panic_with_channel).spawn().unwrap(); + task::build().set_entry(panic_with_condvar).spawn().unwrap(); + task::build().set_entry(panic_with_mailbox).spawn().unwrap(); + task::build().set_entry(panic_with_sleep).spawn().unwrap(); + + #[cfg(feature = "qemu")] + semihosting::terminate(true); + #[cfg(not(feature = "qemu"))] + { + dbg_println!("test complete!"); + loop {} + } +} + +fn panic_with_semaphore() { + static SEM: Semaphore = Semaphore::new(1, 0); + + let pod = PrintOnDrop("Panicked with semaphore."); + let _guard = AllIrqExceptSvc::mask(); + + SEM.down(); + + // Don't print if not panic. + core::mem::forget(pod); +} + +fn panic_with_mutex() { + static MTX: Mutex<()> = Mutex::new(()); + + let pod = PrintOnDrop("Panicked with mutex."); + let _guard = AllIrqExceptSvc::mask(); + + let _mtx_gurad = MTX.lock(); + MTX.lock(); + + // Don't print if not panic. + core::mem::forget(pod); +} + +fn panic_with_channel() { + let (_, cons) = sync::create_channel::<(), 4>(); + + let pod = PrintOnDrop("Panicked with channel."); + let _guard = AllIrqExceptSvc::mask(); + + cons.consume(); + + // Don't print if not panic. + core::mem::forget(pod); +} + +fn panic_with_condvar() { + static CV: CondVar = CondVar::new(); + + let pod = PrintOnDrop("Panicked with condvar."); + let _guard = AllIrqExceptSvc::mask(); + + CV.wait_without_lock_until(|| false); + + // Don't print if not panic. + core::mem::forget(pod); +} + +fn panic_with_mailbox() { + static MAILBOX: Mailbox = Mailbox::new(); + + let pod = PrintOnDrop("Panicked with mailbox."); + let _guard = AllIrqExceptSvc::mask(); + + MAILBOX.wait(); + + // Don't print if not panic. + core::mem::forget(pod); +} + +fn panic_with_sleep() { + let pod = PrintOnDrop("Panicked with sleep."); + let _guard = AllIrqExceptSvc::mask(); + + time::sleep_ms(1000).unwrap(); + + // Don't print if not panic. + core::mem::forget(pod); +} + +struct PrintOnDrop(&'static str); + +impl Drop for PrintOnDrop { + fn drop(&mut self) { + dbg_println!("{}", self.0); + } +} diff --git a/examples/tests/task/unwind/block_while_sched_suspend.txt b/examples/tests/task/unwind/block_while_sched_suspend.txt new file mode 100644 index 0000000..0893296 --- /dev/null +++ b/examples/tests/task/unwind/block_while_sched_suspend.txt @@ -0,0 +1,6 @@ +Panicked with semaphore. +Panicked with mutex. +Panicked with channel. +Panicked with condvar. +Panicked with mailbox. +Panicked with sleep. diff --git a/src/sync/channel.rs b/src/sync/channel.rs index 98501c8..dfc1e0f 100644 --- a/src/sync/channel.rs +++ b/src/sync/channel.rs @@ -98,7 +98,8 @@ impl Producer { /// Push an element into the corresponding channel. If the channel is /// already full, block until there is an empty slot and it can proceed. /// - /// Important: *must not* call this method in ISR context. + /// Important: *Must not* call this method in ISR context. An ISR + /// attempting to block will result in a panic. pub fn produce(&self, data: T) { self.channel.push(data) } @@ -122,7 +123,8 @@ impl Consumer { /// Pop an element from the corresponding channel. If the channel is /// empty, block until there is an element and it can proceed. /// - /// Important: *must not* call this method in ISR context. + /// Important: *Must not* call this method in ISR context. An ISR + /// attempting to block will result in a panic. pub fn consume(&self) -> T { self.channel.pop() } diff --git a/src/sync/condvar.rs b/src/sync/condvar.rs index 2e6815e..04b5f99 100644 --- a/src/sync/condvar.rs +++ b/src/sync/condvar.rs @@ -18,7 +18,8 @@ impl CondVar { /// Wait on the condition variable until notified and the condition is met. /// - /// Important: *must not* call this method in ISR context. + /// Important: *Must not* call this method in ISR context. An ISR + /// attempting to block will result in a panic. /// /// Important: the tasks waiting for nofitication follow first-in-first-out /// order. If the front task being notified does not have its condition met, @@ -39,7 +40,8 @@ impl CondVar { /// is met. When the task resumes, it will get back the lock guard from the /// method's return value. /// - /// Important: *must not* call this method in ISR context. + /// Important: *Must not* call this method in ISR context. An ISR + /// attempting to block will result in a panic. /// /// Important: the tasks waiting for nofitication follow first-in-first-out /// order. If the front task being notified does not have its condition met, diff --git a/src/sync/mailbox.rs b/src/sync/mailbox.rs index 0e406e5..bfb0a23 100644 --- a/src/sync/mailbox.rs +++ b/src/sync/mailbox.rs @@ -176,7 +176,8 @@ impl Mailbox { /// Otherwise, if the counter is currently positive, the calling task to /// this method decrements the counter and continues its execution. /// - /// NOTE: *must not* call this method in ISR context. + /// NOTE: *Must not* call this method in ISR context. An ISR attempting + /// to block will result in a panic. pub fn wait(&self) { // The application logic must have gone terribly wrong if the task // tries to block when the scheduler is suspended or if an ISR @@ -233,7 +234,8 @@ impl Mailbox { /// - `true` if the waiting task is woken up by notification, or `false` if /// by timeout. /// - /// NOTE: *must not* call this method in ISR context. + /// NOTE: *Must not* call this method in ISR context. An ISR attempting + /// to block will result in a panic. pub fn wait_until_timeout(&self, timeout_ms: u32) -> bool { // The application logic must have gone terribly wrong if the task // tries to block when the scheduler is suspended or if an ISR diff --git a/src/sync/semaphore.rs b/src/sync/semaphore.rs index 2e1dcfa..9cfd66f 100644 --- a/src/sync/semaphore.rs +++ b/src/sync/semaphore.rs @@ -47,7 +47,8 @@ impl Semaphore { /// Increment the counter value by 1. Block if the counter value is already /// at the maximum until it is decremented by someone else. /// - /// Important: *must not* call this method in ISR context. + /// Important: *must not* call this method in ISR context. An ISR + /// attempting to block will result in a panic. pub fn up(&self) { loop { // If the counter is already at the maximum, wait until it is not. @@ -122,7 +123,8 @@ impl Semaphore { /// Decrement the counter value by 1. Block if the counter value is already /// zero until it is incremented by someone else. /// - /// Important: *must not* call this method in ISR context. + /// Important: *Must not* call this method in ISR context. An ISR + /// attempting to block will result in a panic. pub fn down(&self) { loop { // If the counter is already at the zero, wait until it is not. diff --git a/src/time/mod.rs b/src/time/mod.rs index 79c1270..be81e28 100644 --- a/src/time/mod.rs +++ b/src/time/mod.rs @@ -129,6 +129,9 @@ pub(crate) fn wake_sleeping_tasks() { } /// Block the task for the given number of milliseconds. +/// +/// Important: *Must not* call this method in ISR context. An ISR attempting to +/// block will result in a panic. #[inline] pub fn sleep_ms(ms: u32) -> Result<(), SleepError> { // See `tick_cmp` for the reason of limitation. @@ -222,8 +225,9 @@ impl IntervalBarrier { /// Block the current task until the interval is elapsed since the last time /// the task was resumed. - /// FIXME: fix the case when the task cannot keep up with the interval of the - /// barrier. + /// + /// Important: *Must not* call this method in ISR context. An ISR attempting to + /// block will result in a panic. pub fn wait(&mut self) { let cur_tick = TICKS.load(Ordering::SeqCst); if let CmpOrdering::Less = tick_cmp(cur_tick, self.next_tick_to_wake) {