Execute radio notification in low priority context
The ble-nrf51822 implementation of the BLE API executes callbacks for radio notification events at very high priority. This functionality is replaced by executing the radio notification callback at a lower priority. When using mbed OS the callback is posted through minar. In mbed classic the callback is executed directly, but from a lower priority. Note that minar or callback execution in mbed classic could not be posted/generated directly from the radio notification handler because this causes race conditions that may lead to a hard-fault. Alternatively, a Timeout was used to post the callback in another context with lower priority.
This commit is contained in:
parent
b37a42c347
commit
167ddd63ab
1 changed files with 60 additions and 40 deletions
|
@ -113,64 +113,84 @@ public:
|
|||
#endif
|
||||
|
||||
private:
|
||||
#ifdef YOTTA_CFG_MBED_OS
|
||||
/*
|
||||
* In mbed OS, all user-facing BLE events (interrupts) are posted to the
|
||||
* MINAR scheduler to be executed as callbacks in thread mode. MINAR guards
|
||||
* its critical sections from interrupts by acquiring CriticalSectionLock,
|
||||
* which results in a call to sd_nvic_critical_region_enter(). Thus, it is
|
||||
* safe to invoke MINAR APIs from interrupt context as long as those
|
||||
* interrupts are blocked by sd_nvic_critical_region_enter().
|
||||
*
|
||||
* Radio notifications are a special case for the above. The Radio
|
||||
* Notification IRQ is handled at a very high priority--higher than the
|
||||
* level blocked by sd_nvic_critical_region_enter(). Thus Radio Notification
|
||||
* events can preempt MINAR's critical sections. Using MINAR APIs (such as
|
||||
* posting an event) directly in processRadioNotification() may result in a
|
||||
* race condition ending in a hard-fault.
|
||||
*
|
||||
* The solution is to *not* call MINAR APIs directly from the Radio
|
||||
* Notification handling; i.e. to do the bulk of RadioNotification
|
||||
* processing at a reduced priority which respects MINAR's critical
|
||||
* sections. Unfortunately, on a cortex-M0, there is no clean way to demote
|
||||
* priority for the currently executing interrupt--we wouldn't want to
|
||||
* demote the radio notification handling anyway because it is sensitive to
|
||||
* timing, and the system expects to finish this handling very quickly. The
|
||||
* workaround is to employ a Timeout to trigger
|
||||
* postRadioNotificationCallback() after a very short delay (~0 us) and post
|
||||
* the MINAR callback that context.
|
||||
*
|
||||
* !!!WARNING!!! Radio notifications are very time critical events. The
|
||||
* current solution is expected to work under the assumption that
|
||||
* postRadioNotificationCalback() will be executed BEFORE the next radio
|
||||
* notification event is generated.
|
||||
*/
|
||||
|
||||
bool radioNotificationCallbackParam; /* parameter to be passed into the Timeout-generated radio notification callback. */
|
||||
Timeout radioNotificationTimeout;
|
||||
|
||||
/*
|
||||
* A helper function to post radio notification callbacks through MINAR when using mbed OS.
|
||||
* A helper function to post radio notification callbacks with low interrupt priority.
|
||||
*/
|
||||
void postRadioNotificationCallback(void) {
|
||||
#ifdef YOTTA_CFG_MBED_OS
|
||||
/*
|
||||
* In mbed OS, all user-facing BLE events (interrupts) are posted to the
|
||||
* MINAR scheduler to be executed as callbacks in thread mode. MINAR guards
|
||||
* its critical sections from interrupts by acquiring CriticalSectionLock,
|
||||
* which results in a call to sd_nvic_critical_region_enter(). Thus, it is
|
||||
* safe to invoke MINAR APIs from interrupt context as long as those
|
||||
* interrupts are blocked by sd_nvic_critical_region_enter().
|
||||
*
|
||||
* Radio notifications are a special case for the above. The Radio
|
||||
* Notification IRQ is handled at a very high priority--higher than the
|
||||
* level blocked by sd_nvic_critical_region_enter(). Thus Radio Notification
|
||||
* events can preempt MINAR's critical sections. Using MINAR APIs (such as
|
||||
* posting an event) directly in processRadioNotification() may result in a
|
||||
* race condition ending in a hard-fault.
|
||||
*
|
||||
* The solution is to *not* call MINAR APIs directly from the Radio
|
||||
* Notification handling; i.e. to do the bulk of RadioNotification
|
||||
* processing at a reduced priority which respects MINAR's critical
|
||||
* sections. Unfortunately, on a cortex-M0, there is no clean way to demote
|
||||
* priority for the currently executing interrupt--we wouldn't want to
|
||||
* demote the radio notification handling anyway because it is sensitive to
|
||||
* timing, and the system expects to finish this handling very quickly. The
|
||||
* workaround is to employ a Timeout to trigger
|
||||
* postRadioNotificationCallback() after a very short delay (~0 us) and post
|
||||
* the MINAR callback that context.
|
||||
*
|
||||
* !!!WARNING!!! Radio notifications are very time critical events. The
|
||||
* current solution is expected to work under the assumption that
|
||||
* postRadioNotificationCalback() will be executed BEFORE the next radio
|
||||
* notification event is generated.
|
||||
*/
|
||||
minar::Scheduler::postCallback(
|
||||
mbed::util::FunctionPointer1<void, bool>(&radioNotificationCallback, &FunctionPointerWithContext<bool>::call).bind(radioNotificationCallbackParam)
|
||||
);
|
||||
}
|
||||
#else
|
||||
/*
|
||||
* In mbed classic, all user-facing BLE events execute callbacks in interrupt
|
||||
* mode. Radio Notifications are a special case because its IRQ is handled at
|
||||
* a very high priority. Thus Radio Notification events can preempt other
|
||||
* operations that require interaction with the SoftDevice such as advertising
|
||||
* payload updates and changing the Gap state. Therefore, executing a Radio
|
||||
* Notification callback directly from processRadioNotification() may result
|
||||
* in a race condition ending in a hard-fault.
|
||||
*
|
||||
* The solution is to *not* execute the Radio Notification callback directly
|
||||
* from the Radio Notification handling; i.e. to do the bulk of the
|
||||
* Radio Notification processing at a reduced priority. Unfortunately, on a
|
||||
* cortex-M0, there is no clean way to demote priority for the currently
|
||||
* executing interrupt--we wouldn't want to demote the radio notification
|
||||
* handling anyway because it is sensitive to timing, and the system expects
|
||||
* to finish this handling very quickly. The workaround is to employ a Timeout
|
||||
* to trigger postRadioNotificationCallback() after a very short delay (~0 us)
|
||||
* and execute the callback in that context.
|
||||
*
|
||||
* !!!WARNING!!! Radio notifications are very time critical events. The
|
||||
* current solution is expected to work under the assumption that
|
||||
* postRadioNotificationCalback() will be executed BEFORE the next radio
|
||||
* notification event is generated.
|
||||
*/
|
||||
radioNotificationCallback.call(radioNotificationCallbackParam);
|
||||
#endif /* #ifdef YOTTA_CFG_MBED_OS */
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper function to process radio-notification events; to be called internally.
|
||||
* @param param [description]
|
||||
*/
|
||||
void processRadioNotificationEvent(bool param) {
|
||||
#ifdef YOTTA_CFG_MBED_OS
|
||||
/* When using mbed OS the callback to the user-defined function will be posted through minar */
|
||||
radioNotificationCallbackParam = param;
|
||||
radioNotificationTimeout.attach_us(this, &nRF5xGap::postRadioNotificationCallback, 0);
|
||||
#else
|
||||
radioNotificationCallback.call(param);
|
||||
#endif
|
||||
}
|
||||
friend void radioNotificationStaticCallback(bool param); /* allow invocations of processRadioNotificationEvent() */
|
||||
|
||||
|
|
Loading…
Reference in a new issue