drivers: timer: nrf_rtc: Refactor alarm setting
User reported a flaw in the current algorithm which fails when Zero Latency Interrupts (ZLI) are used. Ported algorithm from counter_nrfx_rtc.c which covers all cases. Algorithm is lockless so no distinction for ZLI is needed. Signed-off-by: Krzysztof Chruscinski <krzysztof.chruscinski@nordicsemi.no>
This commit is contained in:
parent
b18b1d4350
commit
10d15d185b
1 changed files with 125 additions and 91 deletions
|
@ -20,7 +20,7 @@
|
|||
#define COUNTER_HALF_SPAN (COUNTER_SPAN / 2U)
|
||||
#define CYC_PER_TICK (sys_clock_hw_cycles_per_sec() \
|
||||
/ CONFIG_SYS_CLOCK_TICKS_PER_SEC)
|
||||
#define MAX_TICKS ((COUNTER_MAX - CYC_PER_TICK) / CYC_PER_TICK)
|
||||
#define MAX_TICKS ((COUNTER_HALF_SPAN - CYC_PER_TICK) / CYC_PER_TICK)
|
||||
#define MAX_CYCLES (MAX_TICKS * CYC_PER_TICK)
|
||||
|
||||
static struct k_spinlock lock;
|
||||
|
@ -37,11 +37,121 @@ static void set_comparator(u32_t cyc)
|
|||
nrf_rtc_cc_set(RTC, 0, cyc & COUNTER_MAX);
|
||||
}
|
||||
|
||||
static u32_t get_comparator(void)
|
||||
{
|
||||
return nrf_rtc_cc_get(RTC, 0);
|
||||
}
|
||||
|
||||
static void event_clear(void)
|
||||
{
|
||||
nrf_rtc_event_clear(RTC, NRF_RTC_EVENT_COMPARE_0);
|
||||
}
|
||||
|
||||
static void event_enable(void)
|
||||
{
|
||||
nrf_rtc_event_enable(RTC, NRF_RTC_INT_COMPARE0_MASK);
|
||||
}
|
||||
|
||||
static void int_disable(void)
|
||||
{
|
||||
nrf_rtc_int_disable(RTC, NRF_RTC_INT_COMPARE0_MASK);
|
||||
}
|
||||
|
||||
static void int_enable(void)
|
||||
{
|
||||
nrf_rtc_int_enable(RTC, NRF_RTC_INT_COMPARE0_MASK);
|
||||
}
|
||||
|
||||
static u32_t counter(void)
|
||||
{
|
||||
return nrf_rtc_counter_get(RTC);
|
||||
}
|
||||
|
||||
/* Function ensures that previous CC value will not set event */
|
||||
static void prevent_false_prev_evt(void)
|
||||
{
|
||||
u32_t now = counter();
|
||||
u32_t prev_val;
|
||||
|
||||
/* First take care of a risk of an event coming from CC being set to
|
||||
* next tick. Reconfigure CC to future (now tick is the furtherest
|
||||
* future). If CC was set to next tick we need to wait for up to 15us
|
||||
* (half of 32k tick) and clean potential event. After that time there
|
||||
* is no risk of unwanted event.
|
||||
*/
|
||||
prev_val = get_comparator();
|
||||
event_clear();
|
||||
set_comparator(now);
|
||||
event_enable();
|
||||
|
||||
if (counter_sub(prev_val, now) == 1) {
|
||||
k_busy_wait(15);
|
||||
event_clear();
|
||||
}
|
||||
}
|
||||
|
||||
/* If settings is next tick from now, function attempts to set next tick. If
|
||||
* counter progresses during that time it means that 1 tick elapsed and
|
||||
* interrupt is set pending.
|
||||
*/
|
||||
static void handle_next_tick_case(u32_t t)
|
||||
{
|
||||
set_comparator(t + 2);
|
||||
while (t != counter()) {
|
||||
/* already expired, tick elapsed but event might not be
|
||||
* generated. Trigger interrupt.
|
||||
*/
|
||||
t = counter();
|
||||
set_comparator(t + 2);
|
||||
}
|
||||
}
|
||||
|
||||
/* Function safely sets absolute alarm. It assumes that provided value is
|
||||
* less than MAX_TICKS from now. It detects late setting and also handles
|
||||
* +1 tick case.
|
||||
*/
|
||||
static void set_absolute_ticks(u32_t abs_val)
|
||||
{
|
||||
u32_t diff;
|
||||
u32_t t = counter();
|
||||
|
||||
diff = counter_sub(abs_val, t);
|
||||
if (diff == 1) {
|
||||
handle_next_tick_case(t);
|
||||
return;
|
||||
}
|
||||
|
||||
set_comparator(abs_val);
|
||||
t = counter();
|
||||
/* A little trick, subtract 2 to force now and now + 1 case fall into
|
||||
* negative (> MAX_TICKS). Diff 0 means two ticks from now.
|
||||
*/
|
||||
diff = counter_sub(abs_val - 2, t);
|
||||
if (diff > MAX_TICKS) {
|
||||
/* Already expired. set for next tick */
|
||||
/* It is possible that setting CC was interrupted and CC might
|
||||
* be set to COUNTER+1 value which will not generate an event.
|
||||
* In that case, special handling is performed (attempt to set
|
||||
* CC to COUNTER+2).
|
||||
*/
|
||||
handle_next_tick_case(t);
|
||||
}
|
||||
}
|
||||
|
||||
/* Sets relative ticks alarm from any context. Function is lockless. It only
|
||||
* blocks RTC interrupt.
|
||||
*/
|
||||
static void set_protected_absolute_ticks(u32_t ticks)
|
||||
{
|
||||
int_disable();
|
||||
|
||||
prevent_false_prev_evt();
|
||||
|
||||
set_absolute_ticks(ticks);
|
||||
|
||||
int_enable();
|
||||
}
|
||||
|
||||
/* Note: this function has public linkage, and MUST have this
|
||||
* particular name. The platform architecture itself doesn't care,
|
||||
* but there is a test (tests/arch/arm_irq_vector_table) that needs
|
||||
|
@ -53,27 +163,20 @@ static u32_t counter(void)
|
|||
void rtc1_nrf_isr(void *arg)
|
||||
{
|
||||
ARG_UNUSED(arg);
|
||||
RTC->EVENTS_COMPARE[0] = 0;
|
||||
event_clear();
|
||||
|
||||
k_spinlock_key_t key = k_spin_lock(&lock);
|
||||
u32_t t = counter();
|
||||
u32_t t = get_comparator();
|
||||
u32_t dticks = counter_sub(t, last_count) / CYC_PER_TICK;
|
||||
|
||||
last_count += dticks * CYC_PER_TICK;
|
||||
|
||||
if (!IS_ENABLED(CONFIG_TICKLESS_KERNEL)) {
|
||||
u32_t next = last_count + CYC_PER_TICK;
|
||||
|
||||
/* As below: we're guaranteed to get an interrupt as
|
||||
* long as it's set two or more cycles in the future
|
||||
/* protection is not needed because we are in the RTC interrupt
|
||||
* so it won't get preempted by the interrupt.
|
||||
*/
|
||||
if (counter_sub(next, t) < 3) {
|
||||
next += CYC_PER_TICK;
|
||||
}
|
||||
set_comparator(next);
|
||||
set_absolute_ticks(last_count + CYC_PER_TICK);
|
||||
}
|
||||
|
||||
k_spin_unlock(&lock, key);
|
||||
z_clock_announce(IS_ENABLED(CONFIG_TICKLESS_KERNEL) ? dticks : 1);
|
||||
}
|
||||
|
||||
|
@ -92,12 +195,9 @@ int z_clock_driver_init(struct device *device)
|
|||
|
||||
/* TODO: replace with counter driver to access RTC */
|
||||
nrf_rtc_prescaler_set(RTC, 0);
|
||||
nrf_rtc_cc_set(RTC, 0, CYC_PER_TICK);
|
||||
nrf_rtc_int_enable(RTC, RTC_INTENSET_COMPARE0_Msk);
|
||||
|
||||
/* Clear the event flag and possible pending interrupt */
|
||||
nrf_rtc_event_clear(RTC, NRF_RTC_EVENT_COMPARE_0);
|
||||
event_clear();
|
||||
NVIC_ClearPendingIRQ(RTC1_IRQn);
|
||||
int_enable();
|
||||
|
||||
IRQ_CONNECT(RTC1_IRQn, 1, rtc1_nrf_isr, 0, 0);
|
||||
irq_enable(RTC1_IRQn);
|
||||
|
@ -115,15 +215,16 @@ int z_clock_driver_init(struct device *device)
|
|||
void z_clock_set_timeout(s32_t ticks, bool idle)
|
||||
{
|
||||
ARG_UNUSED(idle);
|
||||
u32_t cyc;
|
||||
|
||||
if (!IS_ENABLED(CONFIG_TICKLESS_KERNEL)) {
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_TICKLESS_KERNEL
|
||||
ticks = (ticks == K_TICKS_FOREVER) ? MAX_TICKS : ticks;
|
||||
ticks = MAX(MIN(ticks - 1, (s32_t)MAX_TICKS), 0);
|
||||
|
||||
k_spinlock_key_t key = k_spin_lock(&lock);
|
||||
u32_t cyc, dt, t = counter();
|
||||
u32_t unannounced = counter_sub(t, last_count);
|
||||
bool zli_fixup = IS_ENABLED(CONFIG_ZERO_LATENCY_IRQS);
|
||||
u32_t unannounced = counter_sub(counter(), last_count);
|
||||
|
||||
/* If we haven't announced for more than half the 24-bit wrap
|
||||
* duration, then force an announce to avoid loss of a wrap
|
||||
|
@ -149,74 +250,7 @@ void z_clock_set_timeout(s32_t ticks, bool idle)
|
|||
}
|
||||
|
||||
cyc += last_count;
|
||||
|
||||
/* Per NRF docs, the RTC is guaranteed to trigger a compare
|
||||
* event if the comparator value to be set is at least two
|
||||
* cycles later than the current value of the counter. So if
|
||||
* we're three or more cycles out, we can set it blindly. If
|
||||
* not, check the time again immediately after setting: it's
|
||||
* possible we "just missed it" and can flag an immediate
|
||||
* interrupt. Or it could be exactly two cycles out, which
|
||||
* will have worked. Otherwise, there's no way to get an
|
||||
* interrupt at the right time and we have to slip the event
|
||||
* by one clock cycle (or we could spin, but this is a slow
|
||||
* clock and spinning for a whole cycle can be thousands of
|
||||
* instructions!)
|
||||
*
|
||||
* You might ask: why not set the comparator first and then
|
||||
* check the timer synchronously to see if we missed it, which
|
||||
* would avoid the need for a slipped cycle. That doesn't
|
||||
* work, the states overlap inside the counter hardware. It's
|
||||
* possible to set a comparator value of "N", issue a DSB
|
||||
* instruction to flush the pipeline, and then immediately
|
||||
* read a counter value of "N-1" (i.e. the comparator is still
|
||||
* in the future), and yet still not receive an interrupt at
|
||||
* least on nRF52. Some experimentation on nrf52840 shows
|
||||
* that you need to be early by about 400 processor cycles
|
||||
* (about 1/5th of a RTC cycle) in order to reliably get the
|
||||
* interrupt. The docs say two cycles, they mean two cycles.
|
||||
*/
|
||||
if (counter_sub(cyc, t) > 2) {
|
||||
set_comparator(cyc);
|
||||
} else {
|
||||
set_comparator(cyc);
|
||||
dt = counter_sub(cyc, counter());
|
||||
if (dt == 0 || dt > 0x7fffff) {
|
||||
/* Missed it! */
|
||||
NVIC_SetPendingIRQ(RTC1_IRQn);
|
||||
if (IS_ENABLED(CONFIG_ZERO_LATENCY_IRQS)) {
|
||||
zli_fixup = false;
|
||||
}
|
||||
} else if (dt == 1) {
|
||||
/* Too soon, interrupt won't arrive. */
|
||||
set_comparator(cyc + 2);
|
||||
}
|
||||
/* Otherwise it was two cycles out, we're fine */
|
||||
}
|
||||
|
||||
#ifdef CONFIG_ZERO_LATENCY_IRQS
|
||||
/* Failsafe. ZLIs can preempt us even though interrupts are
|
||||
* masked, blowing up the sensitive timing above. If the
|
||||
* feature is enabled and we haven't recorded the presence of
|
||||
* a pending interrupt then we need a final check (in a loop!
|
||||
* because this too can be interrupted) to confirm that the
|
||||
* comparator is still in the future. Don't bother being
|
||||
* fancy with cycle counting here, just set an interrupt
|
||||
* "soon" that we know will get the timer back to a known
|
||||
* state. This handles (via some hairy modular expressions)
|
||||
* the wraparound cases where we are preempted for as much as
|
||||
* half the counter space.
|
||||
*/
|
||||
if (zli_fixup && counter_sub(cyc, counter()) <= 0x7fffff) {
|
||||
while (counter_sub(cyc, counter() + 2) > 0x7fffff) {
|
||||
cyc = counter() + 3;
|
||||
set_comparator(cyc);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
k_spin_unlock(&lock, key);
|
||||
#endif /* CONFIG_TICKLESS_KERNEL */
|
||||
set_protected_absolute_ticks(cyc);
|
||||
}
|
||||
|
||||
u32_t z_clock_elapsed(void)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue