Boot Linux faster!

Check our new training course

Boot Linux faster!

Check our new training course
and Creative Commons CC-BY-SA
lecture and lab materials

Bootlin logo

Elixir Cross Referencer

/*
 * Copyright (c) 2018 Intel Corporation
 *
 * SPDX-License-Identifier: Apache-2.0
 */
#include <drivers/system_timer.h>
#include <sys_clock.h>
#include <spinlock.h>
#include <arch/arm/cortex_m/cmsis.h>

void z_ExcExit(void);

#define COUNTER_MAX 0x00ffffff
#define TIMER_STOPPED 0xff000000

#define CYC_PER_TICK (CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC	\
		      / CONFIG_SYS_CLOCK_TICKS_PER_SEC)
#define MAX_TICKS ((COUNTER_MAX / CYC_PER_TICK) - 1)
#define MAX_CYCLES (MAX_TICKS * CYC_PER_TICK)

/* Minimum cycles in the future to try to program.  Note that this is
 * NOT simply "enough cycles to get the counter read and reprogrammed
 * reliably" -- it becomes the minimum value of the LOAD register, and
 * thus reflects how much time we can reliably see expire between
 * calls to elapsed() to read the COUNTFLAG bit.  So it needs to be
 * set to be larger than the maximum time the interrupt might be
 * masked.  Choosing a fraction of a tick is probably a good enough
 * default, with an absolute minimum of 1k cyc.
 */
#define MIN_DELAY MAX(1024, (CYC_PER_TICK/16))

#define TICKLESS (IS_ENABLED(CONFIG_TICKLESS_KERNEL) &&			\
		  !IS_ENABLED(CONFIG_QEMU_TICKLESS_WORKAROUND))

/* VAL value above which we assume that a subsequent COUNTFLAG
 * overflow seen in CTRL is real and not an artifact of wraparound
 * timing.
 */
#define VAL_ABOUT_TO_WRAP 8

static struct k_spinlock lock;

static u32_t last_load;

static u32_t cycle_count;

static u32_t announced_cycles;

static volatile u32_t overflow_cyc;

static u32_t elapsed(void)
{
	u32_t val, ctrl1, ctrl2;

	/* SysTick is infuriatingly racy.  The counter wraps at zero
	 * automatically, setting a 1 in the COUNTFLAG bit of the CTRL
	 * register when it does.  But reading the control register
	 * automatically resets that bit, so we need to save it for
	 * future calls.  And ordering is critical and race-prone: if
	 * we read CTRL first, then it is possible for VAL to wrap
	 * after that read but before we read VAL and we'll miss the
	 * overflow.  If we read VAL first, then it can wrap after we
	 * read it and we'll see an "extra" overflow in CTRL.  And we
	 * want to handle multiple overflows, so we effectively must
	 * read CTRL first otherwise there will be no way to detect
	 * the double-overflow if called at the end of a cycle.  There
	 * is no safe algorithm here, so we split the difference by
	 * reading CTRL twice, suppressing the second overflow bit if
	 * VAL was "about to overflow".
	 */
	ctrl1 = SysTick->CTRL;
	val = SysTick->VAL & COUNTER_MAX;
	ctrl2 = SysTick->CTRL;

	overflow_cyc += (ctrl1 & SysTick_CTRL_COUNTFLAG_Msk) ? last_load : 0;
	if (val > VAL_ABOUT_TO_WRAP) {
		int wrap = ctrl2 & SysTick_CTRL_COUNTFLAG_Msk;

		overflow_cyc += (wrap != 0) ? last_load : 0;
	}

	return (last_load - val) + overflow_cyc;
}

/* Callout out of platform assembly, not hooked via IRQ_CONNECT... */
void z_clock_isr(void *arg)
{
	ARG_UNUSED(arg);
	u32_t dticks;

	cycle_count += last_load;
	dticks = (cycle_count - announced_cycles) / CYC_PER_TICK;
	announced_cycles += dticks * CYC_PER_TICK;

	overflow_cyc = SysTick->CTRL; /* Reset overflow flag */
	overflow_cyc = 0U;

	z_clock_announce(TICKLESS ? dticks : 1);
	z_ExcExit();
}

int z_clock_driver_init(struct device *device)
{
	NVIC_SetPriority(SysTick_IRQn, _IRQ_PRIO_OFFSET);
	last_load = CYC_PER_TICK - 1;
	overflow_cyc = 0U;
	SysTick->LOAD = last_load;
	SysTick->VAL = 0; /* resets timer to last_load */
	SysTick->CTRL |= (SysTick_CTRL_ENABLE_Msk |
			  SysTick_CTRL_TICKINT_Msk |
			  SysTick_CTRL_CLKSOURCE_Msk);
	return 0;
}

void z_clock_set_timeout(s32_t ticks, bool idle)
{
	/* Fast CPUs and a 24 bit counter mean that even idle systems
	 * need to wake up multiple times per second.  If the kernel
	 * allows us to miss tick announcements in idle, then shut off
	 * the counter. (Note: we can assume if idle==true that
	 * interrupts are already disabled)
	 */
	if (IS_ENABLED(CONFIG_TICKLESS_IDLE) && idle && ticks == K_FOREVER) {
		SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
		last_load = TIMER_STOPPED;
		return;
	}

#if defined(CONFIG_TICKLESS_KERNEL) && !defined(CONFIG_QEMU_TICKLESS_WORKAROUND)
	u32_t delay;

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

	/* Desired delay in the future */
	delay = (ticks == 0) ? MIN_DELAY : ticks * CYC_PER_TICK;

	k_spinlock_key_t key = k_spin_lock(&lock);

	cycle_count += elapsed();

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

	overflow_cyc = 0U;
	SysTick->LOAD = last_load - 1;
	SysTick->VAL = 0; /* resets timer to last_load */

	k_spin_unlock(&lock, key);
#endif
}

u32_t z_clock_elapsed(void)
{
	if (!TICKLESS) {
		return 0;
	}

	k_spinlock_key_t key = k_spin_lock(&lock);
	u32_t cyc = elapsed() + cycle_count - announced_cycles;

	k_spin_unlock(&lock, key);
	return cyc / CYC_PER_TICK;
}

u32_t z_timer_cycle_get_32(void)
{
	k_spinlock_key_t key = k_spin_lock(&lock);
	u32_t ret = elapsed() + cycle_count;

	k_spin_unlock(&lock, key);
	return ret;
}

void z_clock_idle_exit(void)
{
	if (last_load == TIMER_STOPPED) {
		SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
	}
}

void sys_clock_disable(void)
{
	SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
}