mirror of
https://github.com/Telecominfraproject/OpenCellular.git
synced 2026-01-09 17:11:42 +00:00
This fixes two race conditions that lead to a watchdog timeout:
1) ticks_to_usecs()
common/timer.c:process_timers() wraps its body in a
"while (next.val <= get_time().val)" loop meant to ensure that
it never returns after having scheduled an expired timer
(to address potential __hw_clock_event_set() overflows/underflows).
However get_time() through __hw_clock_source_read() calls ticks_to_usecs()
which "expands" the hw_rollover_count by a truncated clock_div_factor which
causes that loop condition to observe a "current time" that is up to ~15us
in the past (assuming a 24MHz clock). This race arises frequently with
workloads that repeatedly sleep for a short duration.
2) __hw_clock_event_irq()
The HW timer rollover interrupt was configured to be higher priority than
the event timer interrupt (i.e. it can preempt it) which is problematic if:
- There is a scheduled deadline soon after a "clksrc_high / .le.hi" boundary
- An earlier (before the clksrc_high rollover) event timer interrupt kicks in
- After the event timer interrupt handler gets to "now = get_time()"
in common/timer.c:process_timers() the rollover interrupt triggers
incrementing clksrc_high (i.e. the overflow case)
- The rollover interrupt handler arms the event timer to trigger at
that deadline (mentioned in the first bullet) and returns
- The original event timer interrupt handler resumes execution but finds
no events to schedule since the "timer_deadline[tskid].le.hi == now.le.hi"
clause won't evaluate to true. It will then call __hw_clock_event_clear()
before returning causing a watchdog timeout
This commit also contains a fix to properly initialize the HW timer
after a sysjump.
BRANCH=none
BUG=none
TEST=Reproduced both races and successfully tested the fix. The workload I was
using to reproduce (typically within an hour) has been running smoothly
for the past 24 hours.
Change-Id: Ic0b0958e66e701b52481fcfe506745ca1c892dd1
Signed-off-by: Nadim Taha <ntaha@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/347465
Reviewed-by: Bill Richardson <wfrichar@chromium.org>
158 lines
4.4 KiB
C
158 lines
4.4 KiB
C
/* Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
|
|
* Use of this source code is governed by a BSD-style license that can be
|
|
* found in the LICENSE file.
|
|
*/
|
|
|
|
#include "common.h"
|
|
#include "hooks.h"
|
|
#include "hwtimer.h"
|
|
#include "registers.h"
|
|
#include "task.h"
|
|
#include "util.h"
|
|
|
|
/*
|
|
* Other chips can interrupt at arbitrary match points. We can only interrupt
|
|
* at zero, so we'll have to use a separate timer for events. We'll use module
|
|
* 0, timer 1 for the current time and module 0, timer 2 for event timers.
|
|
*
|
|
* Oh, and we can't control the rate at which the timers tick. We expect to
|
|
* have all counter values in microseconds, but instead they'll be some factor
|
|
* faster than that. 1 usec / tick == 1 MHz, so if PCLK is 30 MHz, we'll have
|
|
* to divide the hardware counter by 30 to get the values expected outside of
|
|
* this file.
|
|
*/
|
|
static uint32_t clock_mul_factor;
|
|
static uint32_t hw_rollover_count;
|
|
|
|
static inline uint32_t ticks_to_usecs(uint32_t ticks)
|
|
{
|
|
return ((uint64_t)hw_rollover_count * 0xffffffff + ticks)
|
|
/ clock_mul_factor;
|
|
}
|
|
|
|
static void update_prescaler(void)
|
|
{
|
|
/*
|
|
* We want the timer to tick every microsecond, but we can only divide
|
|
* PCLK by 1, 16, or 256. We're targeting 30MHz, so we'll just let it
|
|
* run at 1:1.
|
|
*/
|
|
REG_WRITE_MLV(GR_TIMEHS_CONTROL(0, 1),
|
|
GC_TIMEHS_TIMER1CONTROL_PRE_MASK,
|
|
GC_TIMEHS_TIMER1CONTROL_PRE_LSB, 0);
|
|
REG_WRITE_MLV(GR_TIMEHS_CONTROL(0, 2),
|
|
GC_TIMEHS_TIMER1CONTROL_PRE_MASK,
|
|
GC_TIMEHS_TIMER1CONTROL_PRE_LSB, 0);
|
|
|
|
/*
|
|
* Assume the clock rate is an integer multiple of MHz.
|
|
*/
|
|
clock_mul_factor = PCLK_FREQ / 1000000;
|
|
}
|
|
DECLARE_HOOK(HOOK_FREQ_CHANGE, update_prescaler, HOOK_PRIO_DEFAULT);
|
|
|
|
uint32_t __hw_clock_event_get(void)
|
|
{
|
|
/* At what time will the next event fire? */
|
|
uint32_t time_now_in_ticks;
|
|
time_now_in_ticks = (0xffffffff - GR_TIMEHS_VALUE(0, 1));
|
|
return ticks_to_usecs(time_now_in_ticks + GR_TIMEHS_VALUE(0, 2));
|
|
}
|
|
|
|
void __hw_clock_event_clear(void)
|
|
{
|
|
/* one-shot, 32-bit, timer & interrupts disabled, 1:1 prescale */
|
|
GR_TIMEHS_CONTROL(0, 2) = 0x3;
|
|
/* Clear any pending interrupts */
|
|
GR_TIMEHS_INTCLR(0, 2) = 0x1;
|
|
}
|
|
|
|
void __hw_clock_event_set(uint32_t deadline)
|
|
{
|
|
uint32_t time_now_in_ticks;
|
|
|
|
__hw_clock_event_clear();
|
|
|
|
/* How long from the current time to the deadline? */
|
|
time_now_in_ticks = (0xffffffff - GR_TIMEHS_VALUE(0, 1));
|
|
GR_TIMEHS_LOAD(0, 2) = (deadline - time_now_in_ticks / clock_mul_factor)
|
|
* clock_mul_factor;
|
|
|
|
/* timer & interrupts enabled */
|
|
GR_TIMEHS_CONTROL(0, 2) = 0xa3;
|
|
}
|
|
|
|
/*
|
|
* Handle event matches. It's priority matches the HW rollover irq to prevent
|
|
* a race condition that could lead to a watchdog timeout if preempted after
|
|
* the get_time() call in process_timers().
|
|
*/
|
|
void __hw_clock_event_irq(void)
|
|
{
|
|
__hw_clock_event_clear();
|
|
process_timers(0);
|
|
}
|
|
DECLARE_IRQ(GC_IRQNUM_TIMEHS0_TIMINT2, __hw_clock_event_irq, 1);
|
|
|
|
uint32_t __hw_clock_source_read(void)
|
|
{
|
|
/*
|
|
* Return the current time in usecs. Since the counter counts down,
|
|
* we have to invert the value.
|
|
*/
|
|
return ticks_to_usecs(0xffffffff - GR_TIMEHS_VALUE(0, 1));
|
|
}
|
|
|
|
void __hw_clock_source_set(uint32_t ts)
|
|
{
|
|
hw_rollover_count = ((uint64_t)ts * clock_mul_factor) >> 32;
|
|
GR_TIMEHS_LOAD(0, 1) = 0xffffffff - ts * clock_mul_factor;
|
|
GR_TIMEHS_BGLOAD(0, 1) = 0xffffffff;
|
|
}
|
|
|
|
/* This handles rollover in the HW timer */
|
|
void __hw_clock_source_irq(void)
|
|
{
|
|
/* Clear the interrupt */
|
|
GR_TIMEHS_INTCLR(0, 1) = 0x1;
|
|
|
|
/* The one-tick-per-clock HW counter has rolled over. */
|
|
hw_rollover_count++;
|
|
/* Has the system's usec counter rolled over? */
|
|
if (hw_rollover_count >= clock_mul_factor) {
|
|
hw_rollover_count = 0;
|
|
process_timers(1);
|
|
} else {
|
|
process_timers(0);
|
|
}
|
|
}
|
|
DECLARE_IRQ(GC_IRQNUM_TIMEHS0_TIMINT1, __hw_clock_source_irq, 1);
|
|
|
|
int __hw_clock_source_init(uint32_t start_t)
|
|
{
|
|
/* Set the reload and current value. */
|
|
GR_TIMEHS_BGLOAD(0, 1) = 0xffffffff;
|
|
GR_TIMEHS_LOAD(0, 1) = 0xffffffff;
|
|
|
|
/* HW Timer enabled, periodic, interrupt enabled, 32-bit, wrapping */
|
|
GR_TIMEHS_CONTROL(0, 1) = 0xe2;
|
|
/* Event timer disabled */
|
|
__hw_clock_event_clear();
|
|
|
|
/* Account for the clock speed. */
|
|
update_prescaler();
|
|
|
|
/* Clear any pending interrupts */
|
|
GR_TIMEHS_INTCLR(0, 1) = 0x1;
|
|
|
|
/* Force the time to whatever we're told it is */
|
|
__hw_clock_source_set(start_t);
|
|
|
|
/* Here we go... */
|
|
task_enable_irq(GC_IRQNUM_TIMEHS0_TIMINT1);
|
|
task_enable_irq(GC_IRQNUM_TIMEHS0_TIMINT2);
|
|
|
|
/* Return the Event timer IRQ number (NOT the HW timer IRQ) */
|
|
return GC_IRQNUM_TIMEHS0_TIMINT2;
|
|
}
|