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

Add basic Embassy support #53

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

Conversation

Serhii-the-Dev
Copy link

@Serhii-the-Dev Serhii-the-Dev commented Sep 8, 2024

Resolves #49

Embassy time driver implementation is mostly a copy-pasta of the STM32 driver except it's not implementing RTC functionality required for low-power/deep-sleep scenarios in order to keep it simple (AFAIK current HAL implementation also lacks of RTC support).

This PR brings a set of new features:

  • embassy - enables Embassy support in general
  • time-driver-tim<1, 2, 14> - enables Embassy time driver support for a specified general-purpose timer instance

Both features are required to use the Embassy executor, so any of the time-driver-tim<1, 2, 14> features enables the embassy feature in general.

Despite that the driver uses unsafe access to the selected timer's registry, in order to guard an access to a TIMERx peripheral, the driver initialization requires to move a timer<1, 2, 14> so it couldn't be used anywhere else causing undefined behavior, ex:

embassy::init(p.timer1, &clocks, &mut rcu.apb1);

After that the p.timer1 could not be moved, to create a Timer::timer1, for example.

Please take into account that Embassy support comes with a price, as a binary relying on the embassy feature and using the async executor would have additional ~2KiB added to it's size (also an extra memory would be required for the tasks arena):

> cargo bloat --release --features gd32f130x8,time-driver-tim1 --example embassy
...
File  .text   Size            Crate Name
0.4%  41.9% 1.4KiB embassy_executor embassy_executor::arch::thread::Executor::run
0.2%  20.2%   706B embassy_executor embassy_executor::raw::TaskStorage<F>::poll
0.1%  11.6%   406B     gd32f1x0_hal TIMER1
0.1%  10.5%   366B embassy_executor embassy_executor::raw::TaskStorage<F>::poll
0.0%   4.7%   164B        [Unknown] __aeabi_memclr8
0.0%   2.3%    80B embassy_executor embassy_executor::arch::thread::Executor::new
0.0%   1.3%    46B embassy_executor embassy_executor::raw::util::UninitCell<T>::write_in_place
0.0%   1.1%    40B      cortex_m_rt Reset
0.0%   0.7%    24B        [Unknown] HardFaultTrampoline
0.0%   0.6%    20B          embassy embassy::__cortex_m_rt_main
0.0%   0.5%    18B              std core::option::unwrap_failed
0.0%   0.5%    16B     gd32f1x0_hal gd32f1x0_hal::gpio::gpioc::<impl gd32f1x0_hal::gpio::GpioRegExt for gd32f1::gd32f130::gpioc::RegisterBlock>::is_set_low
0.0%   0.5%    16B     gd32f1x0_hal gd32f1x0_hal::gpio::gpioc::<impl gd32f1x0_hal::gpio::GpioRegExt for gd32f1::gd32f130::gpioc::RegisterBlock>::is_low
0.0%   0.4%    14B     gd32f1x0_hal gd32f1x0_hal::gpio::gpioc::<impl gd32f1x0_hal::gpio::GpioRegExt for gd32f1::gd32f130::gpioc::RegisterBlock>::set_high
0.0%   0.4%    14B     gd32f1x0_hal gd32f1x0_hal::gpio::gpioc::<impl gd32f1x0_hal::gpio::GpioRegExt for gd32f1::gd32f130::gpioc::RegisterBlock>::set_low
0.0%   0.3%    10B         cortex_m _critical_section_1_0_release
0.0%   0.2%     8B embassy_executor embassy_executor::raw::SyncExecutor::alarm_callback
0.0%   0.2%     8B              std core::panicking::panic_const::panic_const_async_fn_resumed
0.0%   0.2%     8B              std core::panicking::panic
0.0%   0.2%     8B              std core::panicking::panic_fmt
0.0%   1.1%    38B                  And 9 smaller methods. Use -n N to show more.
0.8% 100.0% 3.4KiB                  .text section size, the file size is 405.7KiB

@Serhii-the-Dev Serhii-the-Dev marked this pull request as draft September 19, 2024 18:18
@Serhii-the-Dev
Copy link
Author

Serhii-the-Dev commented Sep 19, 2024

@qwandor I must ask to wait with a merge as I've just discovered some strange issues with this PR: a couple of fresh new GD32F130C8 are starting to reboot after a few alarm cycles on any timer/channel instance. I tried to fully erase my GD32F130G8's flash, and uploaded an example, now it's reproducing on this chip as well. Moreover, openocd is not able to program the GD32F130G8 anymore, however probe-rs is working just fine. Alas, no meaningful ideas what's going on so far, I'll keep investigation.

Copy link
Collaborator

@qwandor qwandor left a comment

Choose a reason for hiding this comment

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

Weird! Good luck. I haven't finished reviewing this yet, but some comments in the meantime...

Cargo.toml Outdated
"dep:embassy-time",
"dep:embassy-time-driver",
]
time-driver-tim1=["embassy"]
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: please format these lines with a space before and after the = to be consistent with the rest of the file.

Cargo.toml Outdated
"executor-thread",
"integrated-timers",
], optional = true }
embassy-time = { version = "0.3.2", optional = true }
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do you need this dependency? The documentation for embassy-time-driver says "If you are writing a driver, you should depend only on this crate, not on the main embassy-time crate.".

Cargo.toml Outdated
@@ -26,6 +27,14 @@ embedded-io = "0.6.1"
gd32f1 = { version = "0.9.1", features = ["critical-section"] }
nb = "1.1.0"
void = { version = "1.0.2", default-features = false, optional = true }
embassy-executor = { version = "0.6", features = [
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this needed for the implementation or just for the example? If it's only for the example then you can move it to the dev-dependencies section below.

@Serhii-the-Dev
Copy link
Author

Serhii-the-Dev commented Sep 21, 2024

@qwandor Thank you for pointing those out, I've fixed the issues you mentioned. Also I realized that my MCUs are restarting due to the free watchdog timer timeout. According to the GD32F1x0 user manual, the watchdog timer is always running which makes me confused why the examples were running before without chip reset.
I've added a watchdog timer loop into the embassy example however it's still looking odd.
P.S. I've just found your old thread describing the similar issue 😄 So after the SWWDG option was set to zero, the watchdog is disabled and everything is working as expected.

@Serhii-the-Dev Serhii-the-Dev marked this pull request as ready for review September 21, 2024 17:37
#[cfg(feature = "time-driver-tim14")]
const ALARM_COUNT: usize = 1;

fn timer() -> &'static RegisterBlock {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This function shouldn't be safe, because it can allow creating aliases to timer register blocks which may already be owned by other parts of the program.

Or better, rather than constructing the reference here, use the Timer passed in to EmbassyTimeDriver::init, as that way you know you have ownership of it.

callback: Cell<Option<(fn(*mut ()), *mut ())>>,
}

unsafe impl Send for AlarmState {}
Copy link
Collaborator

Choose a reason for hiding this comment

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

If this is sound then please add a safety comment explaining how AlarmState is guaranteed to meet the safety requirements of Send.

((period as u64) << 15) + ((counter ^ ((period & 1) << 15)) as u64)
}

const ALARM_STATE_NEW: AlarmState = AlarmState::new();
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why this constant, rather than just calling AlarmState::new() where you need it? Having both seems like unneccesary duplication.

}

fn get_alarm<'a>(&'a self, cs: CriticalSection<'a>, alarm: AlarmHandle) -> &'a AlarmState {
// safety: we're allowed to assume the AlarmState is created by us, and
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do you mean the AlarmHandle? This is a safe method, so you can't assume anything about what arguments it is called with. Either make this method unsafe and document the safety requirement, or use a safe method below to panic if the handle is out of bounds rather than causing undefined behaviour.

let period = self.period.load(Ordering::Relaxed);
compiler_fence(Ordering::Acquire);
// Timer1 has a 32-bit counter, while other timers resolution is limited to 16 bits
let counter = r.cnt().read().cnt().bits() as u32;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can you use .into() here rather than as u32? That way it's clear that no bits are being discarded.

fn now(&self) -> u64 {
let r = timer();
let period = self.period.load(Ordering::Relaxed);
compiler_fence(Ordering::Acquire);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Would using Ordering::Acquire for the period load operation on the previous line have an equivalent effect?

}

unsafe fn allocate_alarm(&self) -> Option<AlarmHandle> {
critical_section::with(|_| {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why does this need a critical section, when it's only accessing atomics?

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.

Embassy compatibility
2 participants