-
Notifications
You must be signed in to change notification settings - Fork 52
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
safe example with spin #72
base: master
Are you sure you want to change the base?
The head ref may contain hidden characters: "a\u00EFssata_spin_once"
Conversation
Cargo.toml
Outdated
@@ -25,6 +25,7 @@ version = "0.5.0" | |||
|
|||
[dependencies] | |||
critical-section = "1.0" | |||
spin = "0.9.8" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should go under dev-dependencies
for an example.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using spin
is always a bug in bare-metal.
For example, if you use it to sync data between main and interrupt (as a substitute of std::sync
) you WILL get deadlocks if, while main holds the lock, an interrupt fires. The interrupt will spin waiting on the already-acquired lock, which should be released by main, but main will never contine running until the interrupt returns.
Same for all the other sync primitives spin
has, including spin::Once
.
We really shouldn't promote it in embedded contexts, or people will see "ooh, nostd mutex, shiny!" and use it and shoot themselves in the foot.
@Dirbaio what do you propose instead for ensuring the heap is only initialised once? |
I agree. Spinning is an easy way to deadlock when there are interrupts handled. It is important to know if you are in cooperative or preemptive execution. I use the heap with the nrf-softdevice, spawning cooperative executors from the preemptive executor after the heap is initialized. Just initialize the heap first thing when nothing is spawned that can allocate. Nothing is going to be allocating before you have an uncontested chance to initialize. const HEAP_SIZE: usize = 192 * 1024;
#[repr(align(1024))]
struct HeapBuffer([u8; HEAP_SIZE]);
static mut HEAP_BUFFER: HeapBuffer = HeapBuffer([0; HEAP_SIZE]);
#[global_allocator]
static HEAP: Heap = Heap::empty();
///
/// SoftDevice gets 0,1,and 4 priorities
/// GPIO and Timer get 2 priorities
/// SAADC gets 3 priority
///
/// SoftDevice uses:
///
/// TIMER0
/// SWI1_EGU1 is priority 6 Radio Notification
/// SWI2_EGU2 is priority 6 SoftDevice Event Notification
/// SWI4_EGU4 is reserved for future use
/// SWI5_EGU5 is priority 4 SoftDevice Processing
/// PPI 20 and up ... and maybe more
/// check at https://infocenter.nordicsemi.com/pdf/S140_SDS_v2.1.pdf
///
///
#[embassy_executor::main]
async fn main(spawner: Spawner) -> ! {
// Configure for low-power SoftDevice operation
let mut config = embassy_nrf::config::Config::default();
config.gpiote_interrupt_priority = Priority::P2;
config.time_interrupt_priority = Priority::P2;
config.lfclk_source = embassy_nrf::config::LfclkSource::ExternalXtal;
let mut p = embassy_nrf::init(config);
// Confirm to the bootloader that we are alive by consuming the watchdog
let wdt_config = wdt::Config::try_new(&p.WDT).unwrap();
let (_wdt, [wdt_handle]) = match Watchdog::try_new(p.WDT, wdt_config) {
Ok(x) => x,
Err(_) => {
// Watchdog already active with the wrong number of handles, waiting for it to timeout...
loop {
cortex_m::asm::wfe();
}
}
};
// Okay, we are booted.
info!("Hello World!");
// Before anything can attempt to allocate from the HEAP, initialize the heap memory
// SAFETY: No tasks that can touch the heap may be spawned before this initialization
unsafe {
HEAP.init(HEAP_BUFFER.0.as_mut_ptr() as usize, HEAP_BUFFER.0.len());
}
// Configuration ...
// High-priority executor: SWI0_EGU0
info!("Starting SWI0 executor and task");
interrupt::SWI0_EGU0.set_priority(Priority::P5);
let swi_spawner = EXECUTOR_SWI0.start(interrupt::SWI0_EGU0);
unwrap!(swi_spawner.spawn(run_watchdog(wdt_handle)));
unwrap!(swi_spawner.spawn(run_ble()));
// Medium-priority executor: SWI3_EGU3
info!("Starting SWI3 executor and task");
interrupt::SWI3_EGU3.set_priority(Priority::P6);
let swi_spawner = EXECUTOR_SWI3.start(interrupt::SWI3_EGU3);
unwrap!(swi_spawner.spawn(run_battery_check(saadc, p.P0_02.degrade())));
unwrap!(swi_spawner.spawn(run_imu(imu_spim, imu_spi_cs, imu_spi_int)));
unwrap!(swi_spawner.spawn(run_pdm(pdm)));
// Run low priority, expensive/blocking work in thread mode
unwrap!(spawner.spawn(run_cpu_imu()));
unwrap!(spawner.spawn(run_cpu_pdm()));
unwrap!(spawner.spawn(run_cpu_heartbeat()));
unwrap!(spawner.spawn(run_cpu_temperature()));
unwrap!(spawner.spawn(run_cpu_nand(nand_spim, nand_spi_cs, nand_wp, nand_hold)));
unwrap!(spawner.spawn(run_dfu()));
...
``` |
We would like to add an example using
spin
to make sure the heap is initialized once :).