Skip to content

Commit

Permalink
drivers: improve the arcv2_timer driver to update cycles correctly
Browse files Browse the repository at this point in the history
referring the ARM's Systick driver, we did the following improvements:

* use 31 bits of 32-bit counter to avoid the rare but possible
  overflow of elapsed(). If 32 bits val are used, elpased() may
  return a wrong value. then wrong HW cycles.
* two ways to update the correct cycles
  - through systick timer irq
  - when systick timer irq cann't be handled because of irq
    locked/disabled, call z_timer_cycle_get_32->elapsed to update
    the correct cylces. no more than one counter-wrap is allowed.
* if elapsed() is not called too long (more than one counter-wrap) from
  systick tiemr irq or from z_timer_cycle_get_32. The lost of HW cycles
  is unavoidable.

* some detailed discussion can be found in zephyrproject-rtos#24332

Signed-off-by: Wayne Ren <wei.ren@synopsys.com>
  • Loading branch information
Wayne Ren committed Apr 21, 2020
1 parent 7c5c4da commit 17c355f
Showing 1 changed file with 119 additions and 27 deletions.
146 changes: 119 additions & 27 deletions drivers/timer/arcv2_timer0.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,11 @@
#define _ARC_V2_TMR_CTRL_IP 0x8 /* interrupt pending flag */

/* Minimum cycles in the future to try to program. */
#define MIN_DELAY 512
#define COUNTER_MAX 0xffffffff
#define MIN_DELAY 1024
/* arc timer has 32 bit, here use 31 bit to avoid the possible
* overflow,e.g, 0xffffffff + any value will cause overflow
*/
#define COUNTER_MAX 0x7fffffff
#define TIMER_STOPPED 0x0
#define CYC_PER_TICK (sys_clock_hw_cycles_per_sec() \
/ CONFIG_SYS_CLOCK_TICKS_PER_SEC)
Expand All @@ -61,7 +64,36 @@ volatile static u64_t start_time;
#else
static u32_t last_load;


/*
* This local variable holds the amount of timer cycles elapsed
* and it is updated in z_clock_isr() and z_clock_set_timeout().
*
* Note:
* At an arbitrary point in time the "current" value of the
* HW timer is calculated as:
*
* t = cycle_counter + elapsed();
*/
static u32_t cycle_count;

/*
* This local variable holds the amount of elapsed HW cycles
* that have been announced to the kernel.
*/
static u32_t announced_cycles;


/*
* This local variable holds the amount of elapsed HW cycles due to
* timer wraps ('overflows') and is used in the calculation
* in elapsed() function, as well as in the updates to cycle_count.
*
* Note:
* Each time cycle_count is updated with the value from overflow_cyc,
* the overflow_cyc must be reset to zero.
*/
static volatile u32_t overflow_cyc;
#endif

/**
Expand Down Expand Up @@ -131,17 +163,40 @@ static ALWAYS_INLINE void timer0_limit_register_set(u32_t count)
}

#if !SMP_TIMER_DRIVER
/* This internal function calculates the amount of HW cycles that have
* elapsed since the last time the absolute HW cycles counter has been
* updated. 'cycle_count' may be updated either by the ISR, or
* in z_clock_set_timeout().
*
* Additionally, the function updates the 'overflow_cyc' counter, that
* holds the amount of elapsed HW cycles due to (possibly) multiple
* timer wraps (overflows).
*
* Prerequisites:
* - reprogramming of LIMIT must be clearing the COUNT
* - ISR must be clearing the 'overflow_cyc' counter.
* - no more than one counter-wrap has occurred between
* - the timer reset or the last time the function was called
* - and until the current call of the function is completed.
* - the function is invoked with interrupts disabled.
*/
static u32_t elapsed(void)
{
u32_t val, ov, ctrl;
u32_t val, ctrl;

do {
val = timer0_count_register_get();
ctrl = timer0_control_register_get();
} while (timer0_count_register_get() < val);

ov = (ctrl & _ARC_V2_TMR_CTRL_IP) ? last_load : 0;
return val + ov;
if (ctrl & _ARC_V2_TMR_CTRL_IP) {
overflow_cyc += last_load;
/* clear the IP bit of the control register */
timer0_control_register_set(_ARC_V2_TMR_CTRL_NH |
_ARC_V2_TMR_CTRL_IE);
}

return val + overflow_cyc;
}
#endif

Expand All @@ -160,8 +215,6 @@ static void timer_int_handler(void *unused)
ARG_UNUSED(unused);
u32_t dticks;

/* clear the interrupt by writing 0 to IP bit of the control register */
timer0_control_register_set(_ARC_V2_TMR_CTRL_NH | _ARC_V2_TMR_CTRL_IE);

#if defined(CONFIG_SMP) && CONFIG_MP_NUM_CPUS > 1
u64_t curr_time;
Expand All @@ -178,8 +231,14 @@ static void timer_int_handler(void *unused)

z_clock_announce(dticks);
#else
cycle_count += last_load;
dticks = last_load / CYC_PER_TICK;
elapsed();
cycle_count += overflow_cyc;
overflow_cyc = 0;


dticks = (cycle_count - announced_cycles) / CYC_PER_TICK;

announced_cycles += dticks * CYC_PER_TICK;
z_clock_announce(TICKLESS ? dticks : 1);
#endif

Expand Down Expand Up @@ -211,11 +270,18 @@ int z_clock_driver_init(struct device *device)
start_time = last_time;
#else
last_load = CYC_PER_TICK;
overflow_cyc = 0;
announced_cycles = 0;

IRQ_CONNECT(IRQ_TIMER0, CONFIG_ARCV2_TIMER_IRQ_PRIORITY,
timer_int_handler, NULL, 0);

timer0_limit_register_set(last_load - 1);
/* make timer irq pulse sensitive, and self-clear when it's handled
* then we can clear the IP bit of CTRL in non-interrupt context,
* without missing timer irq.
*/
z_arc_v2_irq_unit_sensitivity_set(IRQ_TIMER0, 1);
#ifdef CONFIG_BOOT_TIME_MEASUREMENT
cycle_count = timer0_count_register_get();
#endif
Expand Down Expand Up @@ -279,29 +345,55 @@ void z_clock_set_timeout(s32_t ticks, bool idle)

#if defined(CONFIG_TICKLESS_KERNEL)
u32_t delay;
u32_t unannounced;

ticks = MIN(MAX_TICKS, MAX(ticks - 1, 0));

/* Desired delay in the future */
delay = (ticks == 0) ? MIN_DELAY : ticks * CYC_PER_TICK;
ticks = MIN(MAX_TICKS, (u32_t)(MAX((s32_t)(ticks - 1), 0)));

k_spinlock_key_t key = k_spin_lock(&lock);

delay += elapsed();

/* Round delay up to next tick boundary */
delay = ((delay + CYC_PER_TICK - 1) / CYC_PER_TICK) * CYC_PER_TICK;

if (last_load != delay) {
if (timer0_control_register_get() & _ARC_V2_TMR_CTRL_IP) {
delay -= last_load;
cycle_count += elapsed();
/* clear counter early to avoid cycle loss as few as possible,
* between cycle_count and clearing 0, few cycles are possible
* to loss
*/
timer0_count_register_set(0);
overflow_cyc = 0U;


/* normal case */
unannounced = cycle_count - announced_cycles;

if ((s32_t)unannounced < 0) {
/* We haven't announced for more than half the 32-bit
* wrap duration, because new timeouts keep being set
* before the existing one fires. Force an announce
* to avoid loss of a wrap event, making sure the
* delay is at least the minimum delay possible.
*/
last_load = MIN_DELAY;
} else {
/* Desired delay in the future */
delay = ticks * CYC_PER_TICK;

/* Round delay up to next tick boundary */
delay += unannounced;
delay =
((delay + CYC_PER_TICK - 1) / CYC_PER_TICK) * CYC_PER_TICK;

delay -= unannounced;
delay = MAX(delay, MIN_DELAY);

if (delay > MAX_CYCLES) {
last_load = MAX_CYCLES;
} else {
last_load = delay;
}
timer0_limit_register_set(delay - 1);
last_load = delay;
timer0_control_register_set(_ARC_V2_TMR_CTRL_NH |
_ARC_V2_TMR_CTRL_IE);
}

timer0_limit_register_set(last_load - 1);
timer0_control_register_set(_ARC_V2_TMR_CTRL_NH | _ARC_V2_TMR_CTRL_IE);

k_spin_unlock(&lock, key);
#endif
#endif
Expand All @@ -317,14 +409,14 @@ u32_t z_clock_elapsed(void)
k_spinlock_key_t key = k_spin_lock(&lock);

#if SMP_TIMER_DRIVER
cyc = (z_arc_connect_gfrc_read() - last_time) / CYC_PER_TICK;
cyc = (z_arc_connect_gfrc_read() - last_time);
#else
cyc = elapsed() / CYC_PER_TICK;
cyc = elapsed() + cycle_count - announced_cycles;
#endif

k_spin_unlock(&lock, key);

return cyc;
return cyc / CYC_PER_TICK;
}

u32_t z_timer_cycle_get_32(void)
Expand Down

0 comments on commit 17c355f

Please sign in to comment.