drivers/timer/hpet: Completely new, simplified, tickless-capable driver
Rewritten along the lines of ARM SysTick. Implements only the new, simplified API. MUCH smaller. Works with tickless pervasively. No loss of functionality. Signed-off-by: Andy Ross <andrew.j.ross@intel.com>
This commit is contained in:
parent
9ff98f797a
commit
002a9f4cb7
2 changed files with 114 additions and 708 deletions
|
@ -16,6 +16,7 @@ config HPET_TIMER
|
|||
select IOAPIC
|
||||
select LOAPIC
|
||||
select TIMER_READS_ITS_FREQUENCY_AT_RUNTIME
|
||||
select TICKLESS_CAPABLE
|
||||
help
|
||||
This option selects High Precision Event Timer (HPET) as a
|
||||
system timer.
|
||||
|
|
|
@ -1,750 +1,155 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2015 Wind River Systems, Inc.
|
||||
* Copyright (c) 2018 Intel Corporation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* @brief Intel HPET device driver
|
||||
*
|
||||
* This module implements a kernel device driver for the Intel High Precision
|
||||
* Event Timer (HPET) device, and provides the standard "system clock driver"
|
||||
* interfaces.
|
||||
*
|
||||
* The driver utilizes HPET timer0 to provide kernel ticks.
|
||||
*
|
||||
* \INTERNAL IMPLEMENTATION DETAILS
|
||||
* The HPET device driver makes no assumption about the initial state of the
|
||||
* HPET, and explicitly puts the device into a reset-like state. It also assumes
|
||||
* that the main up counter never wraps around to 0 during the lifetime of the
|
||||
* system.
|
||||
*
|
||||
* The platform can configure the HPET to use level rather than the default edge
|
||||
* sensitive interrupts by enabling the following configuration parameters:
|
||||
* CONFIG_HPET_TIMER_LEVEL_HIGH or CONFIG_HPET_TIMER_LEVEL_LOW
|
||||
*
|
||||
* When not configured to support tickless idle timer0 is programmed in periodic
|
||||
* mode so it automatically generates a single interrupt per kernel tick
|
||||
* interval.
|
||||
*
|
||||
* When configured to support tickless idle timer0 is programmed in one-shot
|
||||
* mode. When the CPU is not idling the timer interrupt handler sets the timer
|
||||
* to expire when the next kernel tick is due, waits for this to occur, and then
|
||||
* repeats this "ad infinitum". When the CPU begins idling the timer driver
|
||||
* reprograms the expiry time for the timer (thereby overriding the previously
|
||||
* scheduled timer interrupt) and waits for the timer to expire or for a
|
||||
* non-timer interrupt to occur. When the CPU ceases idling the driver
|
||||
* determines how many complete ticks have elapsed, reprograms the timer so that
|
||||
* it expires on the next tick, and announces the number of elapsed ticks (if
|
||||
* any) to the kernel.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <kernel.h>
|
||||
#include <toolchain.h>
|
||||
#include <linker/sections.h>
|
||||
#include <sys_clock.h>
|
||||
#include <drivers/ioapic.h>
|
||||
#include <drivers/system_timer.h>
|
||||
#include <kernel_structs.h>
|
||||
#include <sys_clock.h>
|
||||
#include <spinlock.h>
|
||||
|
||||
#include "legacy_api.h"
|
||||
#define HPET_REG32(off) (*(volatile u32_t *)(long) \
|
||||
(CONFIG_HPET_TIMER_BASE_ADDRESS + (off)))
|
||||
|
||||
/* HPET register offsets */
|
||||
#define CLK_PERIOD_REG HPET_REG32(0x04) /* High dword of caps reg */
|
||||
#define GENERAL_CONF_REG HPET_REG32(0x10)
|
||||
#define MAIN_COUNTER_REG HPET_REG32(0xf0)
|
||||
#define TIMER0_CONF_REG HPET_REG32(0x100)
|
||||
#define TIMER0_COMPARATOR_REG HPET_REG32(0x108)
|
||||
|
||||
#define GENERAL_CAPS_REG 0 /* 64-bit register */
|
||||
#define GENERAL_CONFIG_REG 0x10 /* 64-bit register */
|
||||
#define GENERAL_INT_STATUS_REG 0x20 /* 64-bit register */
|
||||
#define MAIN_COUNTER_VALUE_REG 0xf0 /* 64-bit register */
|
||||
/* GENERAL_CONF_REG bits */
|
||||
#define GCONF_ENABLE BIT(0)
|
||||
#define GCONF_LR BIT(1) /* legacy interrupt routing, disables PIT */
|
||||
|
||||
#define TIMER0_CONFIG_CAPS_REG 0x100 /* 64-bit register */
|
||||
#define TIMER0_COMPARATOR_REG 0x108 /* 64-bit register */
|
||||
#define TIMER0_FSB_INT_ROUTE_REG 0x110 /* 64-bit register */
|
||||
/* TIMERn_CONF_REG bits */
|
||||
#define TCONF_INT_ENABLE BIT(2)
|
||||
#define TCONF_PERIODIC BIT(3)
|
||||
#define TCONF_VAL_SET BIT(6)
|
||||
#define TCONF_MODE32 BIT(8)
|
||||
|
||||
/* read the GENERAL_CAPS_REG to determine # of timers actually implemented */
|
||||
#define MIN_DELAY 1000
|
||||
|
||||
#define TIMER1_CONFIG_CAP_REG 0x120 /* 64-bit register */
|
||||
#define TIMER1_COMPARATOR_REG 0x128 /* 64-bit register */
|
||||
#define TIMER1_FSB_INT_ROUTE_REG 0x130 /* 64-bit register */
|
||||
static struct k_spinlock lock;
|
||||
static unsigned int max_ticks;
|
||||
static unsigned int cyc_per_tick;
|
||||
static unsigned int last_count;
|
||||
|
||||
#define TIMER2_CONFIG_CAP_REG 0x140 /* 64-bit register */
|
||||
#define TIMER2_COMPARATOR_REG 0x148 /* 64-bit register */
|
||||
#define TIMER2_FSB_INT_ROUTE_REG 0x150 /* 64-bit register */
|
||||
|
||||
/* convenience macros for accessing specific HPET registers */
|
||||
|
||||
#define _HPET_GENERAL_CAPS ((volatile u64_t *) \
|
||||
(CONFIG_HPET_TIMER_BASE_ADDRESS + GENERAL_CAPS_REG))
|
||||
|
||||
/*
|
||||
* Although the general configuration register is 64-bits, only a 32-bit access
|
||||
* is performed since the most significant bits contain no useful information.
|
||||
*/
|
||||
|
||||
#define _HPET_GENERAL_CONFIG ((volatile u32_t *) \
|
||||
(CONFIG_HPET_TIMER_BASE_ADDRESS + GENERAL_CONFIG_REG))
|
||||
|
||||
/*
|
||||
* Although the general interrupt status is 64-bits, only a 32-bit access
|
||||
* is performed since this driver only utilizes timer0
|
||||
* (i.e. there is no need to determine the interrupt status of other timers).
|
||||
*/
|
||||
|
||||
#define _HPET_GENERAL_INT_STATUS ((volatile u32_t *) \
|
||||
(CONFIG_HPET_TIMER_BASE_ADDRESS + GENERAL_INT_STATUS_REG))
|
||||
|
||||
#define _HPET_MAIN_COUNTER_VALUE ((volatile u64_t *) \
|
||||
(CONFIG_HPET_TIMER_BASE_ADDRESS + MAIN_COUNTER_VALUE_REG))
|
||||
#define _HPET_MAIN_COUNTER_LSW ((volatile u32_t *) \
|
||||
(CONFIG_HPET_TIMER_BASE_ADDRESS + MAIN_COUNTER_VALUE_REG))
|
||||
#define _HPET_MAIN_COUNTER_MSW ((volatile u32_t *) \
|
||||
(CONFIG_HPET_TIMER_BASE_ADDRESS + MAIN_COUNTER_VALUE_REG + 0x4))
|
||||
|
||||
#define _HPET_TIMER0_CONFIG_CAPS ((volatile u64_t *) \
|
||||
(CONFIG_HPET_TIMER_BASE_ADDRESS + TIMER0_CONFIG_CAPS_REG))
|
||||
#define _HPET_TIMER0_COMPARATOR ((volatile u64_t *) \
|
||||
(CONFIG_HPET_TIMER_BASE_ADDRESS + TIMER0_COMPARATOR_REG))
|
||||
#define _HPET_TIMER0_FSB_INT_ROUTE ((volatile u64_t *) \
|
||||
(CONFIG_HPET_TIMER_BASE_ADDRESS + TIMER0_FSB_INT_ROUTE_REG))
|
||||
|
||||
/* general capabilities register macros */
|
||||
|
||||
#define HPET_COUNTER_CLK_PERIOD(caps) (caps >> 32)
|
||||
#define HPET_NUM_TIMERS(caps) (((caps >> 8) & 0x1f) + 1)
|
||||
#define HPET_IS64BITS(caps) (caps & 0x1000)
|
||||
|
||||
/* general configuration register macros */
|
||||
|
||||
#define HPET_ENABLE_CNF (1 << 0)
|
||||
#define HPET_LEGACY_RT_CNF (1 << 1)
|
||||
|
||||
/* timer N configuration and capabilities register macros */
|
||||
|
||||
#define HPET_Tn_INT_ROUTE_CAP(caps) (caps > 32)
|
||||
#define HPET_Tn_FSB_INT_DEL_CAP(caps) (caps & (1 << 15))
|
||||
#define HPET_Tn_FSB_EN_CNF (1 << 14)
|
||||
#define HPET_Tn_INT_ROUTE_CNF_MASK (0x1f << 9)
|
||||
#define HPET_Tn_INT_ROUTE_CNF_SHIFT 9
|
||||
#define HPET_Tn_32MODE_CNF (1 << 8)
|
||||
#define HPET_Tn_VAL_SET_CNF (1 << 6)
|
||||
#define HPET_Tn_SIZE_CAP(caps) (caps & (1 << 5))
|
||||
#define HPET_Tn_PER_INT_CAP(caps) (caps & (1 << 4))
|
||||
#define HPET_Tn_TYPE_CNF (1 << 3)
|
||||
#define HPET_Tn_INT_ENB_CNF (1 << 2)
|
||||
#define HPET_Tn_INT_TYPE_CNF (1 << 1)
|
||||
|
||||
/*
|
||||
* HPET comparator delay factor; this is the minimum value by which a new
|
||||
* timer expiration setting must exceed the current main counter value when
|
||||
* programming a timer in one-shot mode. Failure to allow for delays incurred
|
||||
* in programming a timer may result in the HPET not generating an interrupt
|
||||
* when the desired expiration time is reached. (See HPET documentation for
|
||||
* a more complete description of this issue.)
|
||||
*
|
||||
* The value is expressed in main counter units. For example, if the HPET main
|
||||
* counter increments at a rate of 19.2 MHz, this delay corresponds to 10 us
|
||||
* (or about 0.1% of a system clock tick, assuming a tick rate of 100 Hz).
|
||||
*/
|
||||
|
||||
#define HPET_COMP_DELAY 192
|
||||
|
||||
#if defined(CONFIG_HPET_TIMER_FALLING_EDGE)
|
||||
#define HPET_IOAPIC_FLAGS (IOAPIC_EDGE | IOAPIC_LOW)
|
||||
#elif defined(CONFIG_HPET_TIMER_RISING_EDGE)
|
||||
#define HPET_IOAPIC_FLAGS (IOAPIC_EDGE | IOAPIC_HIGH)
|
||||
#elif defined(CONFIG_HPET_TIMER_LEVEL_HIGH)
|
||||
#define HPET_IOAPIC_FLAGS (IOAPIC_LEVEL | IOAPIC_HIGH)
|
||||
#elif defined(CONFIG_HPET_TIMER_LEVEL_LOW)
|
||||
#define HPET_IOAPIC_FLAGS (IOAPIC_LEVEL | IOAPIC_LOW)
|
||||
#endif
|
||||
|
||||
extern int z_clock_hw_cycles_per_sec;
|
||||
|
||||
#ifdef CONFIG_INT_LATENCY_BENCHMARK
|
||||
static u32_t main_count_first_irq_value;
|
||||
static u32_t main_count_expected_value;
|
||||
extern u32_t _hw_irq_to_c_handler_latency;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_HPET_TIMER_DEBUG
|
||||
#include <misc/printk.h>
|
||||
#define DBG(...) printk(__VA_ARGS__)
|
||||
#else
|
||||
#define DBG(...)
|
||||
#endif
|
||||
|
||||
static s32_t _sys_idle_elapsed_ticks = 1;
|
||||
|
||||
#ifdef CONFIG_TICKLESS_IDLE
|
||||
|
||||
/* additional globals, locals, and forward declarations */
|
||||
|
||||
/* main counter units per system tick */
|
||||
static u32_t __noinit counter_load_value;
|
||||
/* counter value for most recent tick */
|
||||
static u64_t counter_last_value;
|
||||
/* # ticks timer is programmed for */
|
||||
static s32_t programmed_ticks = 1;
|
||||
/* is stale interrupt possible? */
|
||||
static int stale_irq_check;
|
||||
|
||||
/**
|
||||
*
|
||||
* @brief Safely read the main HPET up counter
|
||||
*
|
||||
* This routine simulates an atomic read of the 64-bit system clock on CPUs
|
||||
* that only support 32-bit memory accesses. The most significant word
|
||||
* of the counter is read twice to ensure it doesn't change while the least
|
||||
* significant word is being retrieved (as per HPET documentation).
|
||||
*
|
||||
* @return current 64-bit counter value
|
||||
*/
|
||||
static u64_t _hpetMainCounterAtomic(void)
|
||||
static void hpet_isr(void *arg)
|
||||
{
|
||||
u32_t highBits;
|
||||
u32_t lowBits;
|
||||
ARG_UNUSED(arg);
|
||||
k_spinlock_key_t key = k_spin_lock(&lock);
|
||||
u32_t now = MAIN_COUNTER_REG;
|
||||
u32_t dticks = (now - last_count) / cyc_per_tick;
|
||||
|
||||
do {
|
||||
highBits = *_HPET_MAIN_COUNTER_MSW;
|
||||
lowBits = *_HPET_MAIN_COUNTER_LSW;
|
||||
} while (highBits != *_HPET_MAIN_COUNTER_MSW);
|
||||
last_count += dticks * cyc_per_tick;
|
||||
|
||||
return ((u64_t)highBits << 32) | lowBits;
|
||||
}
|
||||
if (!IS_ENABLED(CONFIG_TICKLESS_KERNEL) ||
|
||||
IS_ENABLED(CONFIG_QEMU_TICKLESS_WORKAROUND)) {
|
||||
u32_t next = last_count + cyc_per_tick;
|
||||
|
||||
#endif /* CONFIG_TICKLESS_IDLE */
|
||||
|
||||
#ifdef CONFIG_TICKLESS_KERNEL
|
||||
static inline void program_max_cycles(void)
|
||||
{
|
||||
stale_irq_check = 1;
|
||||
*_HPET_TIMER0_CONFIG_CAPS |= HPET_Tn_VAL_SET_CNF;
|
||||
counter_last_value = *_HPET_TIMER0_COMPARATOR;
|
||||
*_HPET_TIMER0_COMPARATOR = counter_last_value - 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
*
|
||||
* @brief System clock tick handler
|
||||
*
|
||||
* This routine handles the system clock tick interrupt. A TICK_EVENT event
|
||||
* is pushed onto the kernel stack.
|
||||
*
|
||||
* @return N/A
|
||||
*/
|
||||
void _timer_int_handler(void *unused)
|
||||
{
|
||||
ARG_UNUSED(unused);
|
||||
|
||||
#if defined(CONFIG_HPET_TIMER_LEVEL_LOW) || defined(CONFIG_HPET_TIMER_LEVEL_HIGH)
|
||||
/* Acknowledge interrupt */
|
||||
*_HPET_GENERAL_INT_STATUS = 1;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_INT_LATENCY_BENCHMARK
|
||||
u32_t delta = *_HPET_MAIN_COUNTER_VALUE - main_count_expected_value;
|
||||
|
||||
if (_hw_irq_to_c_handler_latency > delta) {
|
||||
/* keep the lowest value observed */
|
||||
_hw_irq_to_c_handler_latency = delta;
|
||||
}
|
||||
/* compute the next expected main counter value */
|
||||
main_count_expected_value += main_count_first_irq_value;
|
||||
#endif
|
||||
|
||||
|
||||
#ifndef CONFIG_TICKLESS_IDLE
|
||||
|
||||
/*
|
||||
* one more tick has occurred -- don't need to do anything special since
|
||||
* timer is already configured to interrupt on the following tick
|
||||
*/
|
||||
|
||||
z_clock_announce(_sys_idle_elapsed_ticks);
|
||||
|
||||
#else
|
||||
|
||||
/* see if interrupt was triggered while timer was being reprogrammed */
|
||||
|
||||
#if defined(CONFIG_TICKLESS_KERNEL)
|
||||
/* If timer not programmed or already consumed exit */
|
||||
if (!programmed_ticks) {
|
||||
if (_sys_clock_always_on) {
|
||||
z_tick_set(z_clock_uptime());
|
||||
program_max_cycles();
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
if (stale_irq_check) {
|
||||
stale_irq_check = 0;
|
||||
if (_hpetMainCounterAtomic() < *_HPET_TIMER0_COMPARATOR) {
|
||||
return; /* ignore "stale" interrupt */
|
||||
if ((s32_t)(next - now) < MIN_DELAY) {
|
||||
next += cyc_per_tick;
|
||||
}
|
||||
TIMER0_COMPARATOR_REG = next;
|
||||
}
|
||||
|
||||
/* configure timer to expire on next tick for tick based kernel */
|
||||
|
||||
#if defined(CONFIG_TICKLESS_KERNEL)
|
||||
|
||||
_sys_idle_elapsed_ticks = programmed_ticks;
|
||||
|
||||
/*
|
||||
* Clear programmed ticks before announcing elapsed time so
|
||||
* that recursive calls to _update_elapsed_time() will not
|
||||
* announce already consumed elapsed time
|
||||
*/
|
||||
programmed_ticks = 0;
|
||||
z_clock_announce(_sys_idle_elapsed_ticks);
|
||||
|
||||
/* z_clock_announce() could cause new programming */
|
||||
if (!programmed_ticks && _sys_clock_always_on) {
|
||||
z_tick_set(z_clock_uptime());
|
||||
program_max_cycles();
|
||||
}
|
||||
#else
|
||||
counter_last_value = *_HPET_TIMER0_COMPARATOR;
|
||||
*_HPET_TIMER0_CONFIG_CAPS |= HPET_Tn_VAL_SET_CNF;
|
||||
*_HPET_TIMER0_COMPARATOR = counter_last_value + counter_load_value;
|
||||
programmed_ticks = 1;
|
||||
_sys_idle_elapsed_ticks = 1;
|
||||
z_clock_announce(_sys_idle_elapsed_ticks);
|
||||
#endif
|
||||
#endif /* !CONFIG_TICKLESS_IDLE */
|
||||
|
||||
k_spin_unlock(&lock, key);
|
||||
z_clock_announce(dticks);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_TICKLESS_KERNEL
|
||||
u32_t _get_program_time(void)
|
||||
static void set_timer0_irq(unsigned int irq)
|
||||
{
|
||||
return programmed_ticks;
|
||||
/* 5-bit IRQ field starting at bit 9 */
|
||||
u32_t val = (TIMER0_CONF_REG & ~(0x1f << 9)) | ((irq & 0x1f) << 9);
|
||||
|
||||
TIMER0_CONF_REG = val;
|
||||
}
|
||||
|
||||
u32_t _get_remaining_program_time(void)
|
||||
{
|
||||
if (programmed_ticks == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (u32_t) ((s64_t)
|
||||
(*_HPET_TIMER0_COMPARATOR -
|
||||
_hpetMainCounterAtomic()) / counter_load_value);
|
||||
}
|
||||
|
||||
u32_t _get_elapsed_program_time(void)
|
||||
{
|
||||
if (programmed_ticks == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (u32_t) (programmed_ticks -
|
||||
((s64_t)(*_HPET_TIMER0_COMPARATOR -
|
||||
_hpetMainCounterAtomic()) / counter_load_value));
|
||||
}
|
||||
|
||||
void _set_time(u32_t time)
|
||||
{
|
||||
/* Assumes cycles in one time unit is greater than HPET_COMP_DELAY */
|
||||
|
||||
if (!time) {
|
||||
programmed_ticks = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
programmed_ticks = time;
|
||||
|
||||
z_tick_set(z_clock_uptime());
|
||||
|
||||
stale_irq_check = 1;
|
||||
|
||||
*_HPET_TIMER0_CONFIG_CAPS |= HPET_Tn_VAL_SET_CNF;
|
||||
counter_last_value = _hpetMainCounterAtomic();
|
||||
*_HPET_TIMER0_COMPARATOR =
|
||||
counter_last_value + time * counter_load_value;
|
||||
}
|
||||
|
||||
void _enable_sys_clock(void)
|
||||
{
|
||||
if (!programmed_ticks) {
|
||||
program_max_cycles();
|
||||
}
|
||||
}
|
||||
|
||||
u64_t z_clock_uptime(void)
|
||||
{
|
||||
u64_t elapsed;
|
||||
|
||||
elapsed = z_tick_get();
|
||||
elapsed += ((s64_t)(_hpetMainCounterAtomic() -
|
||||
counter_last_value) / counter_load_value);
|
||||
|
||||
return elapsed;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_TICKLESS_IDLE
|
||||
|
||||
/*
|
||||
* Ensure that _timer_idle_enter() is never asked to idle for fewer than 2
|
||||
* ticks (since this might require the timer to be reprogrammed for a deadline
|
||||
* too close to the current time, resulting in a missed interrupt which would
|
||||
* permanently disable the tick timer)
|
||||
*/
|
||||
|
||||
#if (CONFIG_TICKLESS_IDLE_THRESH < 2)
|
||||
#error Tickless idle threshold is too small (must be at least 2)
|
||||
#endif
|
||||
|
||||
/**
|
||||
*
|
||||
* @brief Place system timer into idle state
|
||||
*
|
||||
* Re-program the timer to enter into the idle state for the given number of
|
||||
* ticks (-1 means infinite number of ticks).
|
||||
*
|
||||
* @return N/A
|
||||
*
|
||||
* \INTERNAL IMPLEMENTATION DETAILS
|
||||
* Called while interrupts are locked.
|
||||
*/
|
||||
|
||||
void _timer_idle_enter(s32_t ticks /* system ticks */
|
||||
)
|
||||
{
|
||||
#ifdef CONFIG_TICKLESS_KERNEL
|
||||
if (ticks != K_FOREVER) {
|
||||
/* Need to reprogram only if current program is smaller */
|
||||
if (ticks > programmed_ticks) {
|
||||
_set_time(ticks);
|
||||
}
|
||||
} else {
|
||||
programmed_ticks = 0;
|
||||
counter_last_value = *_HPET_TIMER0_COMPARATOR;
|
||||
*_HPET_GENERAL_CONFIG &= ~HPET_ENABLE_CNF;
|
||||
}
|
||||
#else
|
||||
/*
|
||||
* reprogram timer to expire at the desired time (which is guaranteed
|
||||
* to be at least one full tick from the current counter value)
|
||||
*/
|
||||
|
||||
*_HPET_TIMER0_CONFIG_CAPS |= HPET_Tn_VAL_SET_CNF;
|
||||
*_HPET_TIMER0_COMPARATOR =
|
||||
(ticks >= 0) ? counter_last_value + ticks * counter_load_value
|
||||
: ~(u64_t)0;
|
||||
programmed_ticks = ticks;
|
||||
#endif
|
||||
stale_irq_check = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @brief Take system timer out of idle state
|
||||
*
|
||||
* Determine how long timer has been idling and reprogram it to interrupt at the
|
||||
* next tick.
|
||||
*
|
||||
* Note that in this routine, _sys_idle_elapsed_ticks must be zero because the
|
||||
* ticker has done its work and consumed all the ticks. This has to be true
|
||||
* otherwise idle mode wouldn't have been entered in the first place.
|
||||
*
|
||||
* @return N/A
|
||||
*
|
||||
*/
|
||||
|
||||
void z_clock_idle_exit(void)
|
||||
{
|
||||
#ifdef CONFIG_TICKLESS_KERNEL
|
||||
if (!programmed_ticks && _sys_clock_always_on) {
|
||||
program_max_cycles();
|
||||
}
|
||||
#else
|
||||
u64_t currTime = _hpetMainCounterAtomic();
|
||||
s32_t elapsedTicks;
|
||||
u64_t counterNextValue;
|
||||
|
||||
/* see if idling ended because timer expired at the desired tick */
|
||||
|
||||
if (currTime >= *_HPET_TIMER0_COMPARATOR) {
|
||||
/*
|
||||
* update # of ticks since last tick event was announced,
|
||||
* so that this value is available to ISRs that run before the
|
||||
* timer interrupt handler runs (which is unlikely, but could
|
||||
* happen)
|
||||
*/
|
||||
|
||||
_sys_idle_elapsed_ticks = programmed_ticks - 1;
|
||||
|
||||
/*
|
||||
* Announce elapsed ticks to the kernel. Note we are guaranteed
|
||||
* that the timer ISR will execute first before the tick event
|
||||
* is serviced.
|
||||
*/
|
||||
z_clock_announce(_sys_idle_elapsed_ticks);
|
||||
|
||||
/* timer interrupt handler reprograms the timer for the next
|
||||
* tick
|
||||
*/
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* idling ceased because a non-timer interrupt occurred
|
||||
*
|
||||
* compute how much idle time has elapsed and reprogram the timer
|
||||
* to expire on the next tick; if the next tick will happen so soon
|
||||
* that HPET might miss the interrupt declare that tick prematurely
|
||||
* and program the timer for the tick after that
|
||||
*
|
||||
* note: a premature tick declaration has no significant impact on
|
||||
* the kernel, which gets informed of the correct number of elapsed
|
||||
* ticks when the following tick finally occurs; however, any ISRs that
|
||||
* access _sys_idle_elapsed_ticks to determine the current time may be
|
||||
* misled during the (very brief) interval before the tick-in-progress
|
||||
* finishes and the following tick begins
|
||||
*/
|
||||
|
||||
elapsedTicks =
|
||||
(s32_t)((currTime - counter_last_value) / counter_load_value);
|
||||
counter_last_value += (u64_t)elapsedTicks * counter_load_value;
|
||||
|
||||
counterNextValue = counter_last_value + counter_load_value;
|
||||
|
||||
if ((counterNextValue - currTime) <= HPET_COMP_DELAY) {
|
||||
elapsedTicks++;
|
||||
counterNextValue += counter_load_value;
|
||||
counter_last_value += counter_load_value;
|
||||
}
|
||||
|
||||
*_HPET_TIMER0_CONFIG_CAPS |= HPET_Tn_VAL_SET_CNF;
|
||||
*_HPET_TIMER0_COMPARATOR = counterNextValue;
|
||||
stale_irq_check = 1;
|
||||
|
||||
/*
|
||||
* update # of ticks since last tick event was announced,
|
||||
* so that this value is available to ISRs that run before the timer
|
||||
* expires and the timer interrupt handler runs
|
||||
*/
|
||||
|
||||
_sys_idle_elapsed_ticks = elapsedTicks;
|
||||
|
||||
if (_sys_idle_elapsed_ticks) {
|
||||
/* Announce elapsed ticks to the kernel */
|
||||
z_clock_announce(_sys_idle_elapsed_ticks);
|
||||
}
|
||||
|
||||
/*
|
||||
* Any elapsed ticks have been accounted for so simply set the
|
||||
* programmed ticks to 1 since the timer has been programmed to fire on
|
||||
* the next tick boundary.
|
||||
*/
|
||||
|
||||
programmed_ticks = 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif /* CONFIG_TICKLESS_IDLE */
|
||||
|
||||
/**
|
||||
*
|
||||
* @brief Initialize and enable the system clock
|
||||
*
|
||||
* This routine is used to program the HPET to deliver interrupts at the
|
||||
* rate specified via the 'sys_clock_us_per_tick' global variable.
|
||||
*
|
||||
* @return 0
|
||||
*/
|
||||
|
||||
int z_clock_driver_init(struct device *device)
|
||||
{
|
||||
u64_t hpetClockPeriod;
|
||||
u64_t tickFempto;
|
||||
#ifndef CONFIG_TICKLESS_IDLE
|
||||
u32_t counter_load_value;
|
||||
#endif
|
||||
extern int z_clock_hw_cycles_per_sec;
|
||||
u32_t hz;
|
||||
|
||||
ARG_UNUSED(device);
|
||||
|
||||
/*
|
||||
* Initial state of HPET is unknown, so put it back in a reset-like
|
||||
* state (i.e. set main counter to 0 and disable interrupts)
|
||||
*/
|
||||
|
||||
*_HPET_GENERAL_CONFIG &= ~HPET_ENABLE_CNF;
|
||||
*_HPET_MAIN_COUNTER_VALUE = 0;
|
||||
|
||||
/*
|
||||
* Determine the comparator load value (based on a start count of 0)
|
||||
* to achieve the configured tick rate.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Get tick time (in femptoseconds).
|
||||
*/
|
||||
|
||||
tickFempto = 1000000000000000ull / CONFIG_SYS_CLOCK_TICKS_PER_SEC;
|
||||
|
||||
/*
|
||||
* This driver shall read the COUNTER_CLK_PERIOD value from the general
|
||||
* capabilities register rather than rely on a board.h provide macro
|
||||
* (or the global variable 'sys_clock_hw_cycles_per_tick()')
|
||||
* to determine the frequency of clock applied to the HPET device.
|
||||
*/
|
||||
|
||||
/* read the clock period: units are fempto (10^-15) seconds */
|
||||
|
||||
hpetClockPeriod = HPET_COUNTER_CLK_PERIOD(*_HPET_GENERAL_CAPS);
|
||||
|
||||
/*
|
||||
* compute value for the comparator register to achieve
|
||||
* 'sys_clock_us_per_tick' period
|
||||
*/
|
||||
|
||||
counter_load_value = (u32_t)(tickFempto / hpetClockPeriod);
|
||||
|
||||
DBG("\n\nHPET: configuration: 0x%x, clock period: 0x%x (%d pico-s)\n",
|
||||
(u32_t)(*_HPET_GENERAL_CAPS),
|
||||
(u32_t)hpetClockPeriod, (u32_t)hpetClockPeriod / 1000);
|
||||
|
||||
DBG("HPET: timer0: available interrupts mask 0x%x\n",
|
||||
(u32_t)(*_HPET_TIMER0_CONFIG_CAPS >> 32));
|
||||
|
||||
/* Initialize sys_clock_hw_cycles_per_sec */
|
||||
|
||||
z_clock_hw_cycles_per_sec = counter_load_value *
|
||||
CONFIG_SYS_CLOCK_TICKS_PER_SEC;
|
||||
|
||||
|
||||
#ifdef CONFIG_INT_LATENCY_BENCHMARK
|
||||
main_count_first_irq_value = counter_load_value;
|
||||
main_count_expected_value = main_count_first_irq_value;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_HPET_TIMER_LEGACY_EMULATION
|
||||
/*
|
||||
* Configure HPET replace legacy 8254 timer.
|
||||
* In this case the timer0 interrupt is routed to IRQ2
|
||||
* and legacy timer generates no interrupts
|
||||
*/
|
||||
*_HPET_GENERAL_CONFIG |= HPET_LEGACY_RT_CNF;
|
||||
#endif /* CONFIG_HPET_TIMER_LEGACY_EMULATION */
|
||||
|
||||
#ifndef CONFIG_TICKLESS_IDLE
|
||||
/*
|
||||
* Set timer0 to periodic mode, ready to expire every tick
|
||||
* Setting 32-bit mode during the first load of the comparator
|
||||
* value is required to work around some hardware that otherwise
|
||||
* does not work properly.
|
||||
*/
|
||||
|
||||
*_HPET_TIMER0_CONFIG_CAPS |= HPET_Tn_TYPE_CNF | HPET_Tn_32MODE_CNF;
|
||||
#else
|
||||
/* set timer0 to one-shot mode, ready to expire on the first tick */
|
||||
|
||||
*_HPET_TIMER0_CONFIG_CAPS &= ~HPET_Tn_TYPE_CNF;
|
||||
#endif /* !CONFIG_TICKLESS_IDLE */
|
||||
|
||||
/*
|
||||
* Set the comparator register for timer0. The write to the comparator
|
||||
* register is allowed due to setting the HPET_Tn_VAL_SET_CNF bit.
|
||||
*/
|
||||
*_HPET_TIMER0_CONFIG_CAPS |= HPET_Tn_VAL_SET_CNF;
|
||||
*_HPET_TIMER0_COMPARATOR = counter_load_value;
|
||||
/*
|
||||
* After the comparator is loaded, 32-bit mode can be safely
|
||||
* switched off
|
||||
*/
|
||||
*_HPET_TIMER0_CONFIG_CAPS &= ~HPET_Tn_32MODE_CNF;
|
||||
|
||||
/*
|
||||
* Route interrupts to the I/O APIC. If HPET_Tn_INT_TYPE_CNF is set this
|
||||
* means edge triggered interrupt mode is utilized; Otherwise level
|
||||
* sensitive interrupts are used.
|
||||
*/
|
||||
|
||||
/*
|
||||
* HPET timers IRQ field is 5 bits wide, and hence, can support only
|
||||
* IRQ's up to 31. Some platforms, however, use IRQs greater than 31. In
|
||||
* this case program leaves the IRQ fields blank.
|
||||
*/
|
||||
|
||||
*_HPET_TIMER0_CONFIG_CAPS =
|
||||
#if CONFIG_HPET_TIMER_IRQ < 32
|
||||
(*_HPET_TIMER0_CONFIG_CAPS & ~HPET_Tn_INT_ROUTE_CNF_MASK) |
|
||||
(CONFIG_HPET_TIMER_IRQ << HPET_Tn_INT_ROUTE_CNF_SHIFT)
|
||||
#else
|
||||
(*_HPET_TIMER0_CONFIG_CAPS & ~HPET_Tn_INT_ROUTE_CNF_MASK)
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_HPET_TIMER_LEVEL_LOW) || defined(CONFIG_HPET_TIMER_LEVEL_HIGH)
|
||||
| HPET_Tn_INT_TYPE_CNF;
|
||||
#else
|
||||
;
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Although the stub has already been "connected", the vector number
|
||||
* still has to be programmed into the interrupt controller.
|
||||
*/
|
||||
IRQ_CONNECT(CONFIG_HPET_TIMER_IRQ, CONFIG_HPET_TIMER_IRQ_PRIORITY,
|
||||
_timer_int_handler, 0, HPET_IOAPIC_FLAGS);
|
||||
|
||||
/* enable the IRQ in the interrupt controller */
|
||||
|
||||
hpet_isr, 0, 0);
|
||||
set_timer0_irq(CONFIG_HPET_TIMER_IRQ);
|
||||
irq_enable(CONFIG_HPET_TIMER_IRQ);
|
||||
|
||||
/* enable the HPET generally, and timer0 specifically */
|
||||
/* CLK_PERIOD_REG is in femtoseconds (1e-15 sec) */
|
||||
hz = (u32_t)(1000000000000000ull / CLK_PERIOD_REG);
|
||||
z_clock_hw_cycles_per_sec = hz;
|
||||
cyc_per_tick = hz / CONFIG_SYS_CLOCK_TICKS_PER_SEC;
|
||||
|
||||
*_HPET_GENERAL_CONFIG |= HPET_ENABLE_CNF;
|
||||
*_HPET_TIMER0_CONFIG_CAPS |= HPET_Tn_INT_ENB_CNF;
|
||||
/* Note: we set the legacy routing bit, because otherwise
|
||||
* nothing in Zephyr disables the PIT which then fires
|
||||
* interrupts into the same IRQ. But that means we're then
|
||||
* forced to use IRQ2 contra the way the kconfig IRQ selection
|
||||
* is supposed to work. Should fix this.
|
||||
*/
|
||||
GENERAL_CONF_REG |= GCONF_LR | GCONF_ENABLE;
|
||||
TIMER0_CONF_REG &= ~TCONF_PERIODIC;
|
||||
TIMER0_CONF_REG |= TCONF_MODE32;
|
||||
|
||||
max_ticks = (0x7fffffff - cyc_per_tick) / cyc_per_tick;
|
||||
last_count = MAIN_COUNTER_REG;
|
||||
|
||||
TIMER0_CONF_REG |= TCONF_INT_ENABLE;
|
||||
|
||||
if (IS_ENABLED(CONFIG_TICKLESS_KERNEL) &&
|
||||
!IS_ENABLED(CONFIG_QEMU_TICKLESS_WORKAROUND)) {
|
||||
TIMER0_COMPARATOR_REG = MAIN_COUNTER_REG + cyc_per_tick;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @brief Read the platform's timer hardware
|
||||
*
|
||||
* This routine returns the current time in terms of timer hardware clock
|
||||
* cycles.
|
||||
*
|
||||
* @return up counter of elapsed clock cycles
|
||||
*
|
||||
* \INTERNAL WARNING
|
||||
* If this routine is ever enhanced to return all 64 bits of the counter
|
||||
* it will need to call _hpetMainCounterAtomic().
|
||||
*/
|
||||
void z_clock_set_timeout(s32_t ticks, bool idle)
|
||||
{
|
||||
ARG_UNUSED(idle);
|
||||
|
||||
#if defined(CONFIG_TICKLESS_KERNEL) && !defined(CONFIG_QEMU_TICKLESS_WORKAROUND)
|
||||
if (ticks == K_FOREVER && idle) {
|
||||
GENERAL_CONF_REG &= ~GCONF_ENABLE;
|
||||
return;
|
||||
}
|
||||
|
||||
ticks = ticks == K_FOREVER ? max_ticks : ticks;
|
||||
ticks = max(min(ticks - 1, (s32_t)max_ticks), 0);
|
||||
|
||||
k_spinlock_key_t key = k_spin_lock(&lock);
|
||||
u32_t now = MAIN_COUNTER_REG, cyc;
|
||||
|
||||
/* Round up to next tick boundary */
|
||||
cyc = ticks * cyc_per_tick + (now - last_count) + (cyc_per_tick - 1);
|
||||
cyc = (cyc / cyc_per_tick) * cyc_per_tick;
|
||||
cyc += last_count;
|
||||
|
||||
if ((cyc - now) < MIN_DELAY) {
|
||||
cyc += cyc_per_tick;
|
||||
}
|
||||
|
||||
TIMER0_COMPARATOR_REG = cyc;
|
||||
k_spin_unlock(&lock, key);
|
||||
#endif
|
||||
}
|
||||
|
||||
u32_t z_clock_elapsed(void)
|
||||
{
|
||||
if (!IS_ENABLED(CONFIG_TICKLESS_KERNEL)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
k_spinlock_key_t key = k_spin_lock(&lock);
|
||||
u32_t ret = (MAIN_COUNTER_REG - last_count) / cyc_per_tick;
|
||||
|
||||
k_spin_unlock(&lock, key);
|
||||
return ret;
|
||||
}
|
||||
|
||||
u32_t _timer_cycle_get_32(void)
|
||||
{
|
||||
return (u32_t) *_HPET_MAIN_COUNTER_VALUE;
|
||||
return MAIN_COUNTER_REG;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_SYSTEM_CLOCK_DISABLE
|
||||
|
||||
/**
|
||||
*
|
||||
* @brief Stop announcing ticks into the kernel
|
||||
*
|
||||
* This routine disables the HPET so that timer interrupts are no
|
||||
* longer delivered.
|
||||
*
|
||||
* @return N/A
|
||||
*/
|
||||
|
||||
void sys_clock_disable(void)
|
||||
void z_clock_idle_exit(void)
|
||||
{
|
||||
/*
|
||||
* disable the main HPET up counter and all timer interrupts;
|
||||
* there is no need to lock interrupts before doing this since
|
||||
* no other code alters the HPET's main configuration register
|
||||
* once the driver has been initialized
|
||||
*/
|
||||
|
||||
*_HPET_GENERAL_CONFIG &= ~HPET_ENABLE_CNF;
|
||||
GENERAL_CONF_REG |= GCONF_ENABLE;
|
||||
}
|
||||
|
||||
#endif /* CONFIG_SYSTEM_CLOCK_DISABLE */
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue