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

Implement embedded_hal_async::delay::DelayNs for TIMGx timers #2084

Open
wants to merge 10 commits into
base: main
Choose a base branch
from

Conversation

jessebraham
Copy link
Member

@jessebraham jessebraham commented Sep 4, 2024

Both SYSTIMER alarms and TIMGx timers now implement this trait, and I've added some simple tests to cover the implementations.

I guess this closes #1864 because I am not sure there is a non-hacky and portable way to implement this trait for our Delay struct.

@jessebraham
Copy link
Member Author

The HIL failure for the S2 is a bit perplexing. I will have to find my programmer and try to run that locally.

async fn test_systimer_async_delay_ns(ctx: Context) {
let mut alarms = SystemTimer::new(ctx.peripherals.SYSTIMER);
let unit = FrozenUnit::new(&mut alarms.unit0);
let alarm0 = Alarm::new_async(alarms.comparator0, &unit).into_periodic();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not related to this PR but isn't it a bit surprising that Delay is implemented for Periodic? - I would have expected it to be available for Target

@jessebraham
Copy link
Member Author

I've rebased this and made the necessary updates. Interestingly the ESP32 is failing in addition to the ESP32-S2, so maybe that will help me hunt down this issue.

esp-hal/src/timer/timg.rs Outdated Show resolved Hide resolved
esp-hal/src/timer/timg.rs Outdated Show resolved Hide resolved
pub(crate) fn new(timer: &'a Timer<T, crate::Async>) -> Self {
use crate::timer::Timer as _;

timer.clear_interrupt();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is racey. You could end up clearing the interrupt bit that just fired for this delay causing this to hang forever.

This should use the semantics introduced in #1835

@JurajSadel JurajSadel self-assigned this Sep 9, 2024
@jessebraham
Copy link
Member Author

I'm moving soon and accidentally packed my programmer already 😅 So @JurajSadel has offered to wrap this up (thanks!)

@bugadani
Copy link
Contributor

bugadani commented Sep 10, 2024

Do we ever want to support type-erased timers that provide async delays? If so, I'm not sure this direction is the way forward - I'd probably not invest time in trying to provide async mode at all for the lower level timers (both Systimer and Timg).

At this point, our API supports creating OneShotTimer out of periodic systimers, and PeriodicTimer out of Target systimers (through ErasedTimer). Without this PR, Async mode in TimerGroup has absolutely no use and systimer is just a complex heap of... something. What's the design behind all this and where are we headed?

@bjoernQ
Copy link
Contributor

bjoernQ commented Sep 11, 2024

Do we ever want to support type-erased timers that provide async delays? If so, I'm not sure this direction is the way forward - I'd probably not invest time in trying to provide async mode at all for the lower level timers (both Systimer and Timg).

That is something I was wondering about, too. Should we care about implementing foreign traits on the low-level timers at all?
(My gut feeling is no but maybe there are good reasons to do so)

@bugadani
Copy link
Contributor

I'm leaning towards no, the low-level timers should be, where possible, only backends for higher level abstractions. Ideally we shouldn't provide multiple, drastically different ways to do the same thing, and where possible I'd love to group timer functionality behind PeriodicTimer/OneShotTimer. Doing so allows us to expose a single API regardless of the backend. A single API reduces the barrier of entry for new users as they don't have to consider details. And we have lots of details :)

I'd like to see an ergonimics-first approach to our APIs, with optional complexity if the user needs it. We can provide the first one by prioritising AnyTimer and PeriodicTimer/OneShotTimer. The latter is more tricky with timers, but not impossible if our timers have functionality that can't be abstracted over.

@bjoernQ
Copy link
Contributor

bjoernQ commented Sep 12, 2024

I think we agreed we don't want to have the traits implemented on low-level timers but on the abstractions - nevertheless the failing tests are suspicious

I had a look and apparently on ESP32 and ESP32-S2 disabling INT_ENA doesn't de-assert the pending interrupt - i.e. after returning from the handler, it gets immediately called again

You can verify this by changing the timer_interrupt example accordingly

That's easy to fix by clearing the interrupt pending flag via INT_CLR

The tests then pass for me locally

Patch
diff --git a/esp-hal/src/timer/timg.rs b/esp-hal/src/timer/timg.rs
index 9d1f8157..77083926 100644
--- a/esp-hal/src/timer/timg.rs
+++ b/esp-hal/src/timer/timg.rs
@@ -1180,6 +1180,10 @@ mod asynch {
             .int_ena()
             .modify(|_, w| w.t(0).clear_bit());
 
+        unsafe { &*crate::peripherals::TIMG0::PTR }
+            .int_clr()
+            .write(|w| w.t(0).clear_bit_by_one());
+
         WAKERS[0].wake();
     }
 
@@ -1190,6 +1194,10 @@ mod asynch {
             .int_ena()
             .modify(|_, w| w.t(0).clear_bit());
 
+        unsafe { &*crate::peripherals::TIMG1::PTR }
+            .int_clr()
+            .write(|w| w.t(0).clear_bit_by_one());
+
         WAKERS[1].wake();
     }
 
@@ -1200,6 +1208,10 @@ mod asynch {
             .int_ena()
             .modify(|_, w| w.t(1).clear_bit());
 
+        unsafe { &*crate::peripherals::TIMG0::PTR }
+            .int_clr()
+            .write(|w| w.t(1).clear_bit_by_one());
+
         WAKERS[2].wake();
     }
 
@@ -1210,6 +1222,10 @@ mod asynch {
             .int_ena()
             .modify(|_, w| w.t(1).clear_bit());
 
+        unsafe { &*crate::peripherals::TIMG1::PTR }
+            .int_clr()
+            .write(|w| w.t(1).clear_bit_by_one());
+
         WAKERS[3].wake();
     }
 }

@Dominaezzz
Copy link
Contributor

Dominaezzz commented Sep 12, 2024

I had a look and apparently on ESP32 and ESP32-S2 disabling INT_ENA doesn't de-assert the pending interrupt - i.e. after returning from the handler, it gets immediately called again

That's really strange.
Perhaps it's due to

// always use level interrupt
#[cfg(any(esp32, esp32s2))]
self.register_block()
.t(self.timer_number().into())
.config()
.modify(|_, w| w.level_int_en().set_bit());
?

Clearing the interrupt bit without setting some kind of flag is a little dangerous, in terms of race conditions.

@JurajSadel
Copy link
Contributor

I think we agreed we don't want to have the traits implemented on low-level timers but on the abstractions - nevertheless the failing tests are suspicious

I had a look and apparently on ESP32 and ESP32-S2 disabling INT_ENA doesn't de-assert the pending interrupt - i.e. after returning from the handler, it gets immediately called again

You can verify this by changing the timer_interrupt example accordingly

That's easy to fix by clearing the interrupt pending flag via INT_CLR

The tests then pass for me locally

Patch

diff --git a/esp-hal/src/timer/timg.rs b/esp-hal/src/timer/timg.rs
index 9d1f8157..77083926 100644
--- a/esp-hal/src/timer/timg.rs
+++ b/esp-hal/src/timer/timg.rs
@@ -1180,6 +1180,10 @@ mod asynch {
             .int_ena()
             .modify(|_, w| w.t(0).clear_bit());
 
+        unsafe { &*crate::peripherals::TIMG0::PTR }
+            .int_clr()
+            .write(|w| w.t(0).clear_bit_by_one());
+
         WAKERS[0].wake();
     }
 
@@ -1190,6 +1194,10 @@ mod asynch {
             .int_ena()
             .modify(|_, w| w.t(0).clear_bit());
 
+        unsafe { &*crate::peripherals::TIMG1::PTR }
+            .int_clr()
+            .write(|w| w.t(0).clear_bit_by_one());
+
         WAKERS[1].wake();
     }
 
@@ -1200,6 +1208,10 @@ mod asynch {
             .int_ena()
             .modify(|_, w| w.t(1).clear_bit());
 
+        unsafe { &*crate::peripherals::TIMG0::PTR }
+            .int_clr()
+            .write(|w| w.t(1).clear_bit_by_one());
+
         WAKERS[2].wake();
     }
 
@@ -1210,6 +1222,10 @@ mod asynch {
             .int_ena()
             .modify(|_, w| w.t(1).clear_bit());
 
+        unsafe { &*crate::peripherals::TIMG1::PTR }
+            .int_clr()
+            .write(|w| w.t(1).clear_bit_by_one());
+
         WAKERS[3].wake();
     }
 }

When I looked into this issue on Tuesday, tests were timing out with this exact change for me 🤔

esp-hal/src/timer/timg.rs Outdated Show resolved Hide resolved
esp-hal/src/timer/timg.rs Outdated Show resolved Hide resolved
esp-hal/src/timer/timg.rs Outdated Show resolved Hide resolved
esp-hal/src/timer/timg.rs Outdated Show resolved Hide resolved
esp-hal/src/timer/timg.rs Outdated Show resolved Hide resolved
hil-test/tests/delay_async.rs Outdated Show resolved Hide resolved
hil-test/tests/delay_async.rs Show resolved Hide resolved
}
}

impl<'a, T> core::future::Future for TimerFuture<'a, T>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When this future is dropped (early) it should disable the interrupt.

.modify(|_, w| w.t(0).clear_bit())
});

unsafe { &*crate::peripherals::TIMG0::PTR }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you mind adding a comment here explaining why this INT_CLR is necessary even though the INT_ENA has been cleared above.

type Output = ();

fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> {
let index = (self.timer.timer_number() << 1) | self.timer.timer_group();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mind making a (const) function for this math? Then it can be used in the interrupt handlers as well, just so we know the indexing is consistent/correct.

Comment on lines +1175 to +1176
self.start();
self.listen();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably not super important for this PR but if the user cancels the future returned by this delay function, the call to start and listen should be undone. I'm guessing there's a stop and unlisten somewhere.

peripherals: Peripherals,
}

async fn test_async_delay_ns(mut timer: impl DelayNs, duration: u32) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mind adding a loop in here to ensure repeated calls to delay_ns work as expected.
It happens quite often in drivers that the first one works but the next ones don't.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

The embedded_hal_async::delay::DelayNs trait is not implemented for delay::Delay or timer::timg::Timer
6 participants