drivers/timer/hpet: Extend qemu workaround
Qemu when running more than one processor has a known synchronization bug where counter values read from the HPET (notionally a single global device) can be seen going "backwards" when read from different CPUs. There was a pre-existing workaround in the ISR that knew about this, but the problem can crop up anywhere the counter value is used. In particular I caught it aliasing with the "max_ticks" computation in z_clock_set_timeout(), where it would cause a rollover and the resulting negative comparator value would result in no end of hilarity. Wrap all access to the counter register with a counter() inline that (when the workaround is enabled) forces the result to be monotonic by clamping it to a minimum of one more than the previously read value. Signed-off-by: Andy Ross <andrew.j.ross@intel.com>
This commit is contained in:
parent
419f37043b
commit
4ae44dd712
1 changed files with 41 additions and 19 deletions
|
@ -41,18 +41,41 @@ DEVICE_MMIO_TOPLEVEL_STATIC(hpet_regs, DT_DRV_INST(0));
|
||||||
|
|
||||||
#define MIN_DELAY 1000
|
#define MIN_DELAY 1000
|
||||||
|
|
||||||
|
/* The clock on Qemu in SMP has been observed going backwards. This
|
||||||
|
* enables a workaround that forces the clock to be monotonic.
|
||||||
|
*/
|
||||||
|
#if defined(CONFIG_SMP) && defined(CONFIG_QEMU_TARGET)
|
||||||
|
#define GLITCHY_EMU
|
||||||
|
#endif
|
||||||
|
|
||||||
static struct k_spinlock lock;
|
static struct k_spinlock lock;
|
||||||
static unsigned int max_ticks;
|
static unsigned int max_ticks;
|
||||||
static unsigned int cyc_per_tick;
|
static unsigned int cyc_per_tick;
|
||||||
static unsigned int last_count;
|
static unsigned int last_count;
|
||||||
|
|
||||||
|
#ifdef GLITCHY_EMU
|
||||||
|
volatile static int32_t last_counter;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static ALWAYS_INLINE uint32_t counter(void)
|
||||||
|
{
|
||||||
|
int32_t now = MAIN_COUNTER_REG;
|
||||||
|
#ifdef GLITCHY_EMU
|
||||||
|
if ((now - last_counter) < 0) {
|
||||||
|
now = last_counter + 1;
|
||||||
|
}
|
||||||
|
last_counter = now;
|
||||||
|
#endif
|
||||||
|
return now;
|
||||||
|
}
|
||||||
|
|
||||||
static void hpet_isr(const void *arg)
|
static void hpet_isr(const void *arg)
|
||||||
{
|
{
|
||||||
ARG_UNUSED(arg);
|
ARG_UNUSED(arg);
|
||||||
|
|
||||||
k_spinlock_key_t key = k_spin_lock(&lock);
|
k_spinlock_key_t key = k_spin_lock(&lock);
|
||||||
|
|
||||||
uint32_t now = MAIN_COUNTER_REG;
|
uint32_t now = counter();
|
||||||
|
|
||||||
#if ((DT_INST_IRQ(0, sense) & IRQ_TYPE_LEVEL) == IRQ_TYPE_LEVEL)
|
#if ((DT_INST_IRQ(0, sense) & IRQ_TYPE_LEVEL) == IRQ_TYPE_LEVEL)
|
||||||
/*
|
/*
|
||||||
|
@ -63,19 +86,6 @@ static void hpet_isr(const void *arg)
|
||||||
INTR_STATUS_REG = TIMER0_INT_STS;
|
INTR_STATUS_REG = TIMER0_INT_STS;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (IS_ENABLED(CONFIG_SMP) &&
|
|
||||||
IS_ENABLED(CONFIG_QEMU_TARGET)) {
|
|
||||||
/* Qemu in SMP mode has observed the clock going
|
|
||||||
* "backwards" relative to interrupts already received
|
|
||||||
* on the other CPU, despite the HPET being
|
|
||||||
* theoretically a global device.
|
|
||||||
*/
|
|
||||||
int32_t diff = (int32_t)(now - last_count);
|
|
||||||
|
|
||||||
if (last_count && diff < 0) {
|
|
||||||
now = last_count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
uint32_t dticks = (now - last_count) / cyc_per_tick;
|
uint32_t dticks = (now - last_count) / cyc_per_tick;
|
||||||
|
|
||||||
last_count += dticks * cyc_per_tick;
|
last_count += dticks * cyc_per_tick;
|
||||||
|
@ -138,10 +148,14 @@ int z_clock_driver_init(const struct device *device)
|
||||||
TIMER0_CONF_REG |= TCONF_MODE32;
|
TIMER0_CONF_REG |= TCONF_MODE32;
|
||||||
|
|
||||||
max_ticks = (0x7fffffff - cyc_per_tick) / cyc_per_tick;
|
max_ticks = (0x7fffffff - cyc_per_tick) / cyc_per_tick;
|
||||||
last_count = MAIN_COUNTER_REG;
|
last_count = counter();
|
||||||
|
#ifdef GLITCHY_EMU
|
||||||
|
last_counter = last_count;
|
||||||
|
max_ticks -= 20;
|
||||||
|
#endif
|
||||||
|
|
||||||
TIMER0_CONF_REG |= TCONF_INT_ENABLE;
|
TIMER0_CONF_REG |= TCONF_INT_ENABLE;
|
||||||
TIMER0_COMPARATOR_REG = MAIN_COUNTER_REG + cyc_per_tick;
|
TIMER0_COMPARATOR_REG = counter() + cyc_per_tick;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -168,7 +182,7 @@ void z_clock_set_timeout(int32_t ticks, bool idle)
|
||||||
ticks = CLAMP(ticks - 1, 0, (int32_t)max_ticks);
|
ticks = CLAMP(ticks - 1, 0, (int32_t)max_ticks);
|
||||||
|
|
||||||
k_spinlock_key_t key = k_spin_lock(&lock);
|
k_spinlock_key_t key = k_spin_lock(&lock);
|
||||||
uint32_t now = MAIN_COUNTER_REG, cyc, adj;
|
uint32_t now = counter(), cyc, adj;
|
||||||
uint32_t max_cyc = max_ticks * cyc_per_tick;
|
uint32_t max_cyc = max_ticks * cyc_per_tick;
|
||||||
|
|
||||||
/* Round up to next tick boundary. */
|
/* Round up to next tick boundary. */
|
||||||
|
@ -198,7 +212,7 @@ uint32_t z_clock_elapsed(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
k_spinlock_key_t key = k_spin_lock(&lock);
|
k_spinlock_key_t key = k_spin_lock(&lock);
|
||||||
uint32_t ret = (MAIN_COUNTER_REG - last_count) / cyc_per_tick;
|
uint32_t ret = (counter() - last_count) / cyc_per_tick;
|
||||||
|
|
||||||
k_spin_unlock(&lock, key);
|
k_spin_unlock(&lock, key);
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -206,7 +220,15 @@ uint32_t z_clock_elapsed(void)
|
||||||
|
|
||||||
uint32_t z_timer_cycle_get_32(void)
|
uint32_t z_timer_cycle_get_32(void)
|
||||||
{
|
{
|
||||||
return MAIN_COUNTER_REG;
|
#ifdef GLITCHY_EMU
|
||||||
|
k_spinlock_key_t key = k_spin_lock(&lock);
|
||||||
|
uint32_t ret = counter();
|
||||||
|
|
||||||
|
k_spin_unlock(&lock, key);
|
||||||
|
return ret;
|
||||||
|
#else
|
||||||
|
return counter();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void z_clock_idle_exit(void)
|
void z_clock_idle_exit(void)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue