drivers: improve the arcv2_timer driver to update cycles correctly
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 #24332 Signed-off-by: Wayne Ren <wei.ren@synopsys.com>
This commit is contained in:
parent
293cd5664d
commit
0156511e71
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
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;
|
||||
|
||||
if (last_load != delay) {
|
||||
if (timer0_control_register_get() & _ARC_V2_TMR_CTRL_IP) {
|
||||
delay -= last_load;
|
||||
|
||||
/* 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
|
||||
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue