summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeff Epler <jepler@gmail.com>2020-07-14 17:34:45 -0500
committerJeff Epler <jepler@gmail.com>2020-07-15 09:26:47 -0500
commitdc74ae83da6f519a36d75388b5be9923a10bf063 (patch)
treed272d380b1d55e87840dfab37a63cee662d9dae6
parent51b9a1aeca51c34e0894a08f88888eb4173af32a (diff)
nRF: Always use sd_nvic_critical_region calls
The motivation for doing this is so that we can allow common_hal_mcu_disable_interrupts in IRQ context, something that works on other ports, but not on nRF with SD enabled. This is because when SD is enabled, calling sd_softdevice_is_enabled in the context of an interrupt with priority 2 or 3 causes a HardFault. We have chosen to give the USB interrupt priority 2 on nRF, the highest priority that is compatible with SD. Since at least SoftDevice s130 v2.0.1, sd_nvic_critical_region_enter/exit have been implemented as inline functions and are safe to call even if softdevice is not enabled. Reference kindly provided by danh: https://devzone.nordicsemi.com/f/nordic-q-a/29553/sd_nvic_critical_region_enter-exit-missing-in-s130-v2 Switching to these as the default/only way to enable/disable interrupts simplifies things, and fixes several problems and potential problems: * Interrupts at priority 2 or 3 could not call common_hal_mcu_disable_interrupts because the call to sd_softdevice_is_enabled would HardFault * Hypothetically, the state of sd_softdevice_is_enabled could change from the disable to the enable call, meaning the calls would not match (__disable_irq() could be balanced with sd_nvic_critical_region_exit). This also fixes a problem I believe would exist if disable() were called twice when SD is enabled. There is a single "is_nested_critical_region" flag, and the second call would set it to 1. Both of the enable() calls that followed would call critical_region_exit(1), and interrupts would not properly be reenabled. In the new version of the code, we use our own nesting_count value to track the intended state, so now nested disable()s only call critical_region_enter() once, only updating is_nested_critical_region once; and only the second enable() call will call critical_region_exit, with the right value of i_n_c_r. Finally, in port_sleep_until_interrupt, if !sd_enabled, we really do need to __disable_irq, rather than using the common_hal_mcu routines; the reason why is documented in a comment.
-rw-r--r--ports/nrf/common-hal/microcontroller/__init__.c44
-rw-r--r--ports/nrf/supervisor/port.c13
2 files changed, 32 insertions, 25 deletions
diff --git a/ports/nrf/common-hal/microcontroller/__init__.c b/ports/nrf/common-hal/microcontroller/__init__.c
index f5caf68ef..06aac9409 100644
--- a/ports/nrf/common-hal/microcontroller/__init__.c
+++ b/ports/nrf/common-hal/microcontroller/__init__.c
@@ -50,36 +50,34 @@ void common_hal_mcu_delay_us(uint32_t delay) {
static volatile uint32_t nesting_count = 0;
static uint8_t is_nested_critical_region;
-static uint8_t sd_is_enabled = false;
void common_hal_mcu_disable_interrupts() {
- sd_softdevice_is_enabled(&sd_is_enabled);
- if (sd_is_enabled) {
+ if (nesting_count == 0) {
+ // Unlike __disable_irq(), this should only be called the first time
+ // "is_nested_critical_region" is sd's equivalent of our nesting count
+ // so a nested call would store 0 in the global and make the later
+ // exit call not actually reenable interrupts
+ //
+ // This only disables interrupts of priority 2 through 7; levels 0, 1,
+ // and 4, are exclusive to softdevice and should never be used, so
+ // this limitation is not important.
sd_nvic_critical_region_enter(&is_nested_critical_region);
- } else {
- __disable_irq();
- __DMB();
- nesting_count++;
}
+ __DMB();
+ nesting_count++;
}
void common_hal_mcu_enable_interrupts() {
- // Don't check here if SD is enabled, because we'll crash if interrupts
- // were turned off and sd_softdevice_is_enabled is called.
- if (sd_is_enabled) {
- sd_nvic_critical_region_exit(is_nested_critical_region);
- } else {
- if (nesting_count == 0) {
- // This is very very bad because it means there was mismatched disable/enables so we
- // crash.
- reset_into_safe_mode(HARD_CRASH);
- }
- nesting_count--;
- if (nesting_count > 0) {
- return;
- }
- __DMB();
- __enable_irq();
+ if (nesting_count == 0) {
+ // This is very very bad because it means there was mismatched disable/enables so we
+ // crash.
+ reset_into_safe_mode(HARD_CRASH);
}
+ nesting_count--;
+ if (nesting_count > 0) {
+ return;
+ }
+ __DMB();
+ sd_nvic_critical_region_exit(is_nested_critical_region);
}
void common_hal_mcu_on_next_reset(mcu_runmode_t runmode) {
diff --git a/ports/nrf/supervisor/port.c b/ports/nrf/supervisor/port.c
index aee2d63e1..e681e6825 100644
--- a/ports/nrf/supervisor/port.c
+++ b/ports/nrf/supervisor/port.c
@@ -313,12 +313,21 @@ void port_sleep_until_interrupt(void) {
// instruction will returned as long as an interrupt is
// available, even though the actual handler won't fire until
// we re-enable interrupts.
- common_hal_mcu_disable_interrupts();
+ //
+ // We do not use common_hal_mcu_disable_interrupts here because
+ // we truly require that interrupts be disabled, while
+ // common_hal_mcu_disable_interrupts actually just masks the
+ // interrupts that are not required to allow the softdevice to
+ // function (whether or not SD is enabled)
+ int nested = __get_PRIMASK();
+ __disable_irq();
if (!tud_task_event_ready()) {
__DSB();
__WFI();
}
- common_hal_mcu_enable_interrupts();
+ if (!nested) {
+ __enable_irq();
+ }
}
}