We incorporated the basic functionality of the NCS 2.0's DTM sample app inside an larger application with other threads running.
In our case, for what maps to the dtm_wait(), we experienced a race condition in nrfx_timer_enable() leading to an assert in that routine. Here is the relevant code in the DTM sample and the nrfx timer routines:
void dtm_wait(void)
{
int err;
nrfx_timer_enable(&dtm_inst.wait_timer);
err = k_sem_take(&dtm_inst.wait_sem, K_FOREVER);
etc.
}
static void wait_timer_handler(nrf_timer_event_t event_type, void *context)
{
nrfx_timer_disable(&dtm_inst.wait_timer);
nrfx_timer_clear(&dtm_inst.wait_timer);
k_sem_give(&dtm_inst.wait_sem);
}
void nrfx_timer_enable(nrfx_timer_t const * p_instance)
{
NRFX_ASSERT(m_cb[p_instance->instance_id].state == NRFX_DRV_STATE_INITIALIZED);
nrf_timer_task_trigger(p_instance->p_reg, NRF_TIMER_TASK_START);
m_cb[p_instance->instance_id].state = NRFX_DRV_STATE_POWERED_ON;
NRFX_LOG_INFO("Enabled instance: %d.", p_instance->instance_id);
}
void nrfx_timer_disable(nrfx_timer_t const * p_instance)
{
NRFX_ASSERT(m_cb[p_instance->instance_id].state != NRFX_DRV_STATE_UNINITIALIZED);
nrf_timer_task_trigger(p_instance->p_reg, NRF_TIMER_TASK_SHUTDOWN);
m_cb[p_instance->instance_id].state = NRFX_DRV_STATE_INITIALIZED;
NRFX_LOG_INFO("Disabled instance: %d.", p_instance->instance_id);
}
You can see the potential race condition by supposing the following sequence occurs in the DTM sample app code.
- dtm_wait() / nrfx_timer_enable() has just completed the call to nrf_timer_task_trigger() (lines 4 and 19), and then the thread of that execution gets preempted.
- While the thread of dtm_wait() is preempted, the nrfx_timer expires and calls wait_timer_handler().
- The wait_time_handler() disables the timer by calling nrfx_timer_disable() (line 11), which sets the timer state to NRFX_DRV_STATE_INITIALIZED (line 28).
- The thread of dtm_wait() resumes at setting the timer state to NRFX_DRV_STATE_POWERED_ON (line 20).
- The dtm_wait() takes the semaphore (line 5), which was already given since the timer handler already expired (line 13).
- At the next call to dtm_wait()/nrfx_timer_enable(), we fail the assert (line 18) because the state is wrong since step 3 and step 4 are not the expected order.
We fixed the race condition by putting an irq_lock() in dtm_wait() around the call to nrfx_time_enable().
It seems that the race condition could be fixed alternatively by swapping the timer trigger and the state change in your routine, as follows (lines 4 and 5 swapped):
void nrfx_timer_enable(nrfx_timer_t const * p_instance)
{
NRFX_ASSERT(m_cb[p_instance->instance_id].state == NRFX_DRV_STATE_INITIALIZED);
m_cb[p_instance->instance_id].state = NRFX_DRV_STATE_POWERED_ON;
nrf_timer_task_trigger(p_instance->p_reg, NRF_TIMER_TASK_START);
NRFX_LOG_INFO("Enabled instance: %d.", p_instance->instance_id);
}
Is that change appropriate for you to make in the NCS? If not, should there be a note somewhere for nrfx_timer_enable() that the caller should ensure the call is protected?
Thank you in advance.