#include <sys/time.h>
#include <sys/psm.h>
#include <sys/psm_common.h>
#include <sys/apic.h>
#include <sys/pit.h>
#include <sys/x86_archext.h>
#include <sys/archsystm.h>
#include <sys/machsystm.h>
#include <sys/cpuvar.h>
#include <sys/clock.h>
#include <sys/apic_timer.h>
int apic_timer_preferred_mode = APIC_TIMER_MODE_DEADLINE;
int apic_oneshot = 0;
uint_t apic_hertz_count;
uint_t apic_nsec_per_intr = 0;
uint64_t apic_ticks_per_SFnsecs;
static int apic_min_timer_ticks = 1;
static hrtime_t apic_nsec_max;
static void periodic_timer_enable(void);
static void periodic_timer_disable(void);
static void periodic_timer_reprogram(hrtime_t);
static void oneshot_timer_enable(void);
static void oneshot_timer_disable(void);
static void oneshot_timer_reprogram(hrtime_t);
static void deadline_timer_enable(void);
static void deadline_timer_disable(void);
static void deadline_timer_reprogram(hrtime_t);
extern int apic_clkvect;
extern uint32_t apic_divide_reg_init;
typedef struct apic_timer {
int mode;
void (*apic_timer_enable_ops)(void);
void (*apic_timer_disable_ops)(void);
void (*apic_timer_reprogram_ops)(hrtime_t);
} apic_timer_t;
static apic_timer_t apic_timer;
int
apic_timer_init(int hertz)
{
int ret, timer_mode;
static int firsttime = 1;
if (firsttime) {
apic_ticks_per_SFnsecs = apic_calibrate();
apic_nsec_max = APIC_TICKS_TO_NSECS(APIC_MAXVAL);
firsttime = 0;
}
if (hertz == 0) {
if (!tsc_gethrtime_enable)
return (0);
if ((apic_timer_preferred_mode != APIC_TIMER_MODE_ONESHOT) &&
(apic_timer_preferred_mode != APIC_TIMER_MODE_DEADLINE))
return (0);
apic_oneshot = 1;
ret = (int)APIC_TICKS_TO_NSECS(1);
if ((apic_timer_preferred_mode == APIC_TIMER_MODE_DEADLINE) &&
cpuid_deadline_tsc_supported()) {
timer_mode = APIC_TIMER_MODE_DEADLINE;
} else {
timer_mode = APIC_TIMER_MODE_ONESHOT;
}
} else {
apic_nsec_per_intr = NANOSEC / hertz;
apic_hertz_count = APIC_NSECS_TO_TICKS(apic_nsec_per_intr);
apic_reg_ops->apic_write(APIC_INIT_COUNT, apic_hertz_count);
apic_reg_ops->apic_write(APIC_LOCAL_TIMER,
(apic_clkvect + APIC_BASE_VECT) | AV_PERIODIC);
apic_oneshot = 0;
timer_mode = APIC_TIMER_MODE_PERIODIC;
ret = NANOSEC / hertz;
}
apic_timer.mode = timer_mode;
switch (timer_mode) {
default:
case APIC_TIMER_MODE_ONESHOT:
apic_timer.apic_timer_enable_ops = oneshot_timer_enable;
apic_timer.apic_timer_disable_ops = oneshot_timer_disable;
apic_timer.apic_timer_reprogram_ops = oneshot_timer_reprogram;
break;
case APIC_TIMER_MODE_PERIODIC:
apic_timer.apic_timer_enable_ops = periodic_timer_enable;
apic_timer.apic_timer_disable_ops = periodic_timer_disable;
apic_timer.apic_timer_reprogram_ops = periodic_timer_reprogram;
break;
case APIC_TIMER_MODE_DEADLINE:
apic_timer.apic_timer_enable_ops = deadline_timer_enable;
apic_timer.apic_timer_disable_ops = deadline_timer_disable;
apic_timer.apic_timer_reprogram_ops = deadline_timer_reprogram;
break;
}
return (ret);
}
static void
periodic_timer_enable(void)
{
apic_reg_ops->apic_write(APIC_LOCAL_TIMER,
(apic_clkvect + APIC_BASE_VECT) | AV_PERIODIC);
}
static void
periodic_timer_disable(void)
{
apic_reg_ops->apic_write(APIC_LOCAL_TIMER,
(apic_clkvect + APIC_BASE_VECT) | AV_MASK);
}
static void
periodic_timer_reprogram(hrtime_t time)
{
uint_t ticks;
ticks = APIC_NSECS_TO_TICKS(time);
if (ticks < apic_min_timer_ticks)
ticks = apic_min_timer_ticks;
apic_reg_ops->apic_write(APIC_INIT_COUNT, ticks);
}
static void
oneshot_timer_enable(void)
{
apic_reg_ops->apic_write(APIC_LOCAL_TIMER,
(apic_clkvect + APIC_BASE_VECT));
}
static void
oneshot_timer_disable(void)
{
apic_reg_ops->apic_write(APIC_LOCAL_TIMER,
(apic_clkvect + APIC_BASE_VECT) | AV_MASK);
}
static void
oneshot_timer_reprogram(hrtime_t time)
{
hrtime_t now;
int64_t delta;
uint_t ticks;
now = gethrtime();
delta = time - now;
if (delta <= 0) {
ticks = apic_min_timer_ticks;
} else if (delta > apic_nsec_max) {
ticks = APIC_MAXVAL;
#ifdef DEBUG
cmn_err(CE_CONT, "apic_timer_reprogram, request at"
" %lld too far in future, current time"
" %lld \n", time, now);
#endif
} else {
ticks = APIC_NSECS_TO_TICKS(delta);
}
if (ticks < apic_min_timer_ticks)
ticks = apic_min_timer_ticks;
apic_reg_ops->apic_write(APIC_INIT_COUNT, ticks);
}
static void
deadline_timer_enable(void)
{
uint64_t ticks;
apic_reg_ops->apic_write(APIC_LOCAL_TIMER,
(apic_clkvect + APIC_BASE_VECT) | AV_DEADLINE);
do {
wrmsr(IA32_DEADLINE_TSC_MSR, 1ULL << 63);
ticks = rdmsr(IA32_DEADLINE_TSC_MSR);
} while (ticks == 0);
}
static void
deadline_timer_disable(void)
{
apic_reg_ops->apic_write(APIC_LOCAL_TIMER,
(apic_clkvect + APIC_BASE_VECT) | AV_MASK);
}
static void
deadline_timer_reprogram(hrtime_t time)
{
int64_t delta;
uint64_t ticks;
delta = time - gethrtime();
delta = max(delta, 0);
ticks = (uint64_t)tsc_read() + unscalehrtime(delta);
wrmsr(IA32_DEADLINE_TSC_MSR, ticks);
}
void
apic_timer_reprogram(hrtime_t time)
{
apic_timer.apic_timer_reprogram_ops(time);
}
void
apic_timer_enable(void)
{
apic_timer.apic_timer_enable_ops();
}
void
apic_timer_disable(void)
{
apic_timer.apic_timer_disable_ops();
}
hrtime_t
apic_timer_stop_count(void)
{
hrtime_t ns_val;
int enable_val, count_val;
ASSERT(!interrupts_enabled());
enable_val = apic_reg_ops->apic_read(APIC_LOCAL_TIMER);
if ((enable_val & AV_MASK) == AV_MASK)
return ((hrtime_t)-1);
count_val = apic_reg_ops->apic_read(APIC_CURR_COUNT);
ns_val = APIC_TICKS_TO_NSECS(count_val);
apic_reg_ops->apic_write(APIC_INIT_COUNT, APIC_MAXVAL);
return (ns_val);
}
void
apic_timer_restart(hrtime_t time)
{
apic_timer_reprogram(time);
}