-
Notifications
You must be signed in to change notification settings - Fork 368
Fix S2 systimers #1979
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
Fix S2 systimers #1979
Changes from all commits
f4453ff
c38ddcc
ff551b6
804a040
d725936
7615979
29d9b19
e912eb0
01661d0
f78817f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -396,20 +396,20 @@ pub trait Comparator { | |
| fn set_enable(&self, enable: bool) { | ||
| let systimer = unsafe { &*SYSTIMER::ptr() }; | ||
|
|
||
| #[cfg(not(esp32s2))] | ||
| critical_section::with(|_| { | ||
| #[cfg(not(esp32s2))] | ||
| systimer.conf().modify(|_, w| match self.channel() { | ||
| 0 => w.target0_work_en().bit(enable), | ||
| 1 => w.target1_work_en().bit(enable), | ||
| 2 => w.target2_work_en().bit(enable), | ||
| _ => unreachable!(), | ||
| }); | ||
| }); | ||
|
|
||
| #[cfg(esp32s2)] | ||
| systimer | ||
| .target_conf(self.channel() as usize) | ||
| .modify(|_r, w| w.work_en().bit(enable)); | ||
| #[cfg(esp32s2)] | ||
| systimer | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why move this into the critical section? The register isn't shared on the S2.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The better question is, why wasn't this (and, like all other operations) in a critical section? Every method needs a shared reference only, what prevents users from shooting themselves in the foot by doing things in interrupts that they shouldn't?
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sharing with interrupts requires The shared reference thing let's you give multiple objects on the same "thread" (interrupt counts as a separate thread) access to the object.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Alarm is Sync as things currently stand :)
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah yeah that needs fixing. Either by adding critical sections in The latter is easier (and maybe also the right way) since
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why would you prefer
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think Side note there's also this part of the API GUIDELINES.
(Though I suppose pointing it out doesn't matter since we're questioning the guideline itself) |
||
| .target_conf(self.channel() as usize) | ||
| .modify(|_r, w| w.work_en().bit(enable)); | ||
| }); | ||
| } | ||
|
|
||
| /// Returns true if the comparator has been enabled. This means | ||
|
|
@@ -524,9 +524,48 @@ pub trait Comparator { | |
| 2 => Interrupt::SYSTIMER_TARGET2, | ||
| _ => unreachable!(), | ||
| }; | ||
|
|
||
| #[cfg(not(esp32s2))] | ||
| unsafe { | ||
| interrupt::bind_interrupt(interrupt, handler.handler()); | ||
| } | ||
|
|
||
| #[cfg(esp32s2)] | ||
| { | ||
| // ESP32-S2 Systimer interrupts are edge triggered. Our interrupt | ||
| // handler calls each of the handlers, regardless of which one triggered the | ||
| // interrupt. This mess registers an intermediate handler that | ||
| // checks if an interrupt is active before calling the associated | ||
| // handler functions. | ||
|
|
||
| static mut HANDLERS: [Option<extern "C" fn()>; 3] = [None, None, None]; | ||
|
|
||
| #[crate::prelude::ram] | ||
| unsafe extern "C" fn _handle_interrupt<const CH: u8>() { | ||
| if unsafe { &*SYSTIMER::PTR } | ||
| .int_raw() | ||
| .read() | ||
| .target(CH) | ||
| .bit_is_set() | ||
| { | ||
| let handler = unsafe { HANDLERS[CH as usize] }; | ||
| if let Some(handler) = handler { | ||
| handler(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| unsafe { | ||
| HANDLERS[self.channel() as usize] = Some(handler.handler()); | ||
| let handler = match self.channel() { | ||
| 0 => _handle_interrupt::<0>, | ||
| 1 => _handle_interrupt::<1>, | ||
| 2 => _handle_interrupt::<2>, | ||
| _ => unreachable!(), | ||
| }; | ||
| interrupt::bind_interrupt(interrupt, handler); | ||
| } | ||
| } | ||
| unwrap!(interrupt::enable(interrupt, handler.priority())); | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,195 @@ | ||
| //! System Timer Test | ||
|
|
||
| // esp32 disabled as it does not have a systimer | ||
| //% CHIPS: esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3 | ||
|
|
||
| #![no_std] | ||
| #![no_main] | ||
|
|
||
| use core::cell::RefCell; | ||
|
|
||
| use critical_section::Mutex; | ||
| use embedded_hal::delay::DelayNs; | ||
| use esp_hal::{ | ||
| clock::{ClockControl, Clocks}, | ||
| delay::Delay, | ||
| peripherals::Peripherals, | ||
| prelude::*, | ||
| system::SystemControl, | ||
| timer::systimer::{ | ||
| Alarm, | ||
| FrozenUnit, | ||
| Periodic, | ||
| SpecificComparator, | ||
| SpecificUnit, | ||
| SystemTimer, | ||
| Target, | ||
| }, | ||
| Blocking, | ||
| }; | ||
| use fugit::ExtU32; | ||
| use hil_test as _; | ||
| use portable_atomic::{AtomicUsize, Ordering}; | ||
| use static_cell::StaticCell; | ||
|
|
||
| type TestAlarm<M, const C: u8> = | ||
| Alarm<'static, M, Blocking, SpecificComparator<'static, C>, SpecificUnit<'static, 0>>; | ||
|
|
||
| static ALARM_TARGET: Mutex<RefCell<Option<TestAlarm<Target, 0>>>> = Mutex::new(RefCell::new(None)); | ||
| static ALARM_PERIODIC: Mutex<RefCell<Option<TestAlarm<Periodic, 1>>>> = | ||
| Mutex::new(RefCell::new(None)); | ||
|
|
||
| struct Context { | ||
| unit: FrozenUnit<'static, SpecificUnit<'static, 0>>, | ||
| comparator0: SpecificComparator<'static, 0>, | ||
| comparator1: SpecificComparator<'static, 1>, | ||
| clocks: Clocks<'static>, | ||
| } | ||
|
|
||
| impl Context { | ||
| pub fn init() -> Self { | ||
| let peripherals = Peripherals::take(); | ||
| let system = SystemControl::new(peripherals.SYSTEM); | ||
| let clocks = ClockControl::boot_defaults(system.clock_control).freeze(); | ||
|
|
||
| let systimer = SystemTimer::new(peripherals.SYSTIMER); | ||
| static UNIT0: StaticCell<SpecificUnit<'static, 0>> = StaticCell::new(); | ||
|
|
||
| let unit0 = UNIT0.init(systimer.unit0); | ||
| let frozen_unit = FrozenUnit::new(unit0); | ||
|
|
||
| Context { | ||
| clocks, | ||
| unit: frozen_unit, | ||
| comparator0: systimer.comparator0, | ||
| comparator1: systimer.comparator1, | ||
| } | ||
| } | ||
| } | ||
|
|
||
| #[handler(priority = esp_hal::interrupt::Priority::min())] | ||
| fn pass_test_if_called() { | ||
| critical_section::with(|cs| { | ||
| ALARM_TARGET | ||
| .borrow_ref_mut(cs) | ||
| .as_mut() | ||
| .unwrap() | ||
| .clear_interrupt() | ||
| }); | ||
| embedded_test::export::check_outcome(()); | ||
| } | ||
|
|
||
| #[handler(priority = esp_hal::interrupt::Priority::min())] | ||
| fn handle_periodic_interrupt() { | ||
| critical_section::with(|cs| { | ||
| ALARM_PERIODIC | ||
| .borrow_ref_mut(cs) | ||
| .as_mut() | ||
| .unwrap() | ||
| .clear_interrupt() | ||
| }); | ||
| } | ||
|
|
||
| static COUNTER: AtomicUsize = AtomicUsize::new(0); | ||
|
|
||
| #[handler(priority = esp_hal::interrupt::Priority::min())] | ||
| fn pass_test_if_called_twice() { | ||
| critical_section::with(|cs| { | ||
| ALARM_PERIODIC | ||
| .borrow_ref_mut(cs) | ||
| .as_mut() | ||
| .unwrap() | ||
| .clear_interrupt() | ||
| }); | ||
| COUNTER.fetch_add(1, Ordering::Relaxed); | ||
| if COUNTER.load(Ordering::Relaxed) == 2 { | ||
| embedded_test::export::check_outcome(()); | ||
| } | ||
| } | ||
|
|
||
| #[handler(priority = esp_hal::interrupt::Priority::min())] | ||
| fn target_fail_test_if_called_twice() { | ||
| critical_section::with(|cs| { | ||
| ALARM_TARGET | ||
| .borrow_ref_mut(cs) | ||
| .as_mut() | ||
| .unwrap() | ||
| .clear_interrupt() | ||
| }); | ||
| COUNTER.fetch_add(1, Ordering::Relaxed); | ||
| assert!(COUNTER.load(Ordering::Relaxed) != 2); | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| #[embedded_test::tests] | ||
| mod tests { | ||
| use super::*; | ||
|
|
||
| #[init] | ||
| fn init() -> Context { | ||
| Context::init() | ||
| } | ||
|
|
||
| #[test] | ||
| #[timeout(3)] | ||
| fn target_interrupt_is_handled(ctx: Context) { | ||
| let alarm0 = Alarm::new(ctx.comparator0, &ctx.unit); | ||
|
|
||
| critical_section::with(|cs| { | ||
| alarm0.set_interrupt_handler(pass_test_if_called); | ||
| alarm0.set_target(SystemTimer::now() + SystemTimer::TICKS_PER_SECOND / 10); | ||
| alarm0.enable_interrupt(true); | ||
|
|
||
| ALARM_TARGET.borrow_ref_mut(cs).replace(alarm0); | ||
| }); | ||
|
|
||
| // We'll end the test in the interrupt handler. | ||
| loop {} | ||
| } | ||
|
|
||
| #[test] | ||
| #[timeout(3)] | ||
| fn target_interrupt_is_handled_once(ctx: Context) { | ||
| let alarm0 = Alarm::new(ctx.comparator0, &ctx.unit); | ||
| let alarm1 = Alarm::new(ctx.comparator1, &ctx.unit); | ||
|
|
||
| COUNTER.store(0, Ordering::Relaxed); | ||
|
|
||
| critical_section::with(|cs| { | ||
| alarm0.set_interrupt_handler(target_fail_test_if_called_twice); | ||
| alarm0.set_target(SystemTimer::now() + SystemTimer::TICKS_PER_SECOND / 10); | ||
| alarm0.enable_interrupt(true); | ||
|
|
||
| let alarm1 = alarm1.into_periodic(); | ||
| alarm1.set_interrupt_handler(handle_periodic_interrupt); | ||
| alarm1.set_period(100u32.millis()); | ||
| alarm1.enable_interrupt(true); | ||
|
|
||
| ALARM_TARGET.borrow_ref_mut(cs).replace(alarm0); | ||
| ALARM_PERIODIC.borrow_ref_mut(cs).replace(alarm1); | ||
| }); | ||
|
|
||
| let mut delay = Delay::new(&ctx.clocks); | ||
| delay.delay_ms(300); | ||
| } | ||
|
|
||
| #[test] | ||
| #[timeout(3)] | ||
| fn periodic_interrupt_is_handled(ctx: Context) { | ||
| let alarm1 = Alarm::new(ctx.comparator1, &ctx.unit); | ||
|
|
||
| COUNTER.store(0, Ordering::Relaxed); | ||
|
|
||
| critical_section::with(|cs| { | ||
| let alarm1 = alarm1.into_periodic(); | ||
| alarm1.set_interrupt_handler(pass_test_if_called_twice); | ||
| alarm1.set_period(100u32.millis()); | ||
| alarm1.enable_interrupt(true); | ||
|
|
||
| ALARM_PERIODIC.borrow_ref_mut(cs).replace(alarm1); | ||
| }); | ||
|
|
||
| // We'll end the test in the interrupt handler. | ||
| loop {} | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.