While trying to investigate my Softdevice assert issue a bit more in depth, I noticed a bug in timeslot_timer.c:
The function timer_mut_lock() sets the variable m_timer_mut through the _DISABLE_IRQS() macro:
static inline void timer_mut_lock(void) { _DISABLE_IRQS(m_timer_mut); }
#define _DISABLE_IRQS(_was_masked) _was_masked = __disable_irq()
Whereas the corresponding timer_mut_unlock() function probably should clear the m_timer_mut variable, it doesn't since it only calls the _ENABLE_IRQS() macro which does not modify the variable:
static inline void timer_mut_unlock(void) { _ENABLE_IRQS(m_timer_mut); }
#define _ENABLE_IRQS(_was_masked) do{ if (!(_was_masked)) { __enable_irq(); } } while(0)
As soon as timer_mut_lock() gets called once while interrupts are already disabled, the TIMER0 interrupt will never be re-enabled again, because of the following excerpt from ts_timer_on_ts_begin().
/* only enable interrupts if we're not locked. */ if (!m_timer_mut) { (void) NVIC_EnableIRQ(TIMER0_IRQn); }