blob: 228354e739c0bfe04a0f9540a963c16a5069d550 [file] [log] [blame]
/* Copyright (c) 2009 The Regents of the University of California
* David (Yu) Zhu <yuzhu@cs.berkeley.edu>
* Barret Rhoden <brho@cs.berkeley.edu>
*
* See LICENSE for details. */
#include <arch/x86.h>
#include <arch/arch.h>
#include <arch/pic.h>
#include <arch/apic.h>
#include <time.h>
#include <trap.h>
#include <assert.h>
#include <stdio.h>
#include <ros/procinfo.h>
#include <arch/uaccess.h>
static uint16_t pit_divisor;
static uint8_t pit_mode;
static uint64_t compute_tsc_freq(void)
{
uint64_t tscval[2];
/* some boards have this unmasked early on. */
pic_mask_irq(0, 0 + PIC1_OFFSET);
pit_set_timer(0xffff, TIMER_RATEGEN);
tscval[0] = read_tsc();
udelay_pit(1000000);
tscval[1] = read_tsc();
return tscval[1] - tscval[0];
}
static void set_tsc_freq(void)
{
uint64_t msr_val, tsc_freq = 0;
bool computed = FALSE;
if (!read_msr_safe(MSR_PLATFORM_INFO, &msr_val))
tsc_freq = __proc_global_info.bus_freq
* ((msr_val >> 8) & 0xff);
/* Even if we have the MSR, it might have given us 0. (QEMU). */
if (!tsc_freq) {
tsc_freq = compute_tsc_freq();
computed = TRUE;
}
__proc_global_info.tsc_freq = tsc_freq;
printk("TSC Frequency: %llu%s\n", tsc_freq,
computed ? " (computed)" : "");
}
static uint64_t compute_bus_freq(void)
{
uint32_t timercount[2];
__lapic_set_timer(0xffffffff, IdtLAPIC_TIMER, FALSE,
LAPIC_TIMER_DIVISOR_BITS);
// Mask the LAPIC Timer, so we never receive this interrupt (minor race)
mask_lapic_lvt(MSR_LAPIC_LVT_TIMER);
timercount[0] = apicrget(MSR_LAPIC_CURRENT_COUNT);
udelay_pit(1000000);
timercount[1] = apicrget(MSR_LAPIC_CURRENT_COUNT);
/* The time base for the timer is derived from the processor's bus
* clock, divided by the value specified in the divide configuration
* register. Note we mult and div by the divisor, saving the actual
* freq (even though we don't use it yet). */
return (timercount[0] - timercount[1]) * LAPIC_TIMER_DIVISOR_VAL;
}
static uint64_t lookup_bus_freq(void)
{
/* Got these from the good book for any model supporting
* MSR_PLATFORM_INFO. If they don't support that MSR, we're going to
* compute the TSC anyways.
*
* A couple models weren't in the book, but were reported at:
* http://a4lg.com/tech/x86/database/x86-families-and-models.en.html.
* Feel free to add more. If we fail here, we'll compute it manually
* and be off slightly. */
switch ((x86_family << 16) | x86_model) {
case 0x6001a:
case 0x6001e:
case 0x6001f:
case 0x6002e:
/* Nehalem */
return 133333333;
case 0x60025:
case 0x6002c:
case 0x6002f: /* from a4lg.com */
/* Westmere */
return 133333333;
case 0x6002a:
case 0x6002d:
/* Sandy Bridge */
return 100000000;
case 0x6003a: /* from a4lg.com */
case 0x6003e:
/* Ivy Bridge */
return 100000000;
case 0x6003c:
case 0x6003f:
case 0x60045:
case 0x60046:
/* Haswell */
return 100000000;
case 0x6003d:
case 0x6004f:
case 0x60056:
/* Broadwell */
return 100000000;
case 0x6004d:
/* Sky Lake */
return 100000000;
case 0x60057:
/* Knights Landing */
return 100000000;
}
return 0;
}
static void set_bus_freq(void)
{
uint64_t bus_freq;
bool computed = FALSE;
bus_freq = lookup_bus_freq();
if (!bus_freq) {
bus_freq = compute_bus_freq();
computed = TRUE;
}
__proc_global_info.bus_freq = bus_freq;
printk("Bus Frequency: %llu%s\n", bus_freq,
computed ? " (computed)" : "");
}
void timer_init(void)
{
set_bus_freq();
assert(__proc_global_info.bus_freq);
set_tsc_freq();
}
void pit_set_timer(uint32_t divisor, uint32_t mode)
{
if (divisor & 0xffff0000)
warn("Divisor too large!");
mode = TIMER_SEL0|TIMER_16BIT|mode;
outb(TIMER_MODE, mode);
outb(TIMER_CNTR0, divisor & 0xff);
outb(TIMER_CNTR0, (divisor >> 8) );
pit_mode = mode;
pit_divisor = divisor;
// cprintf("timer mode set to %d, divisor %d\n",mode, divisor);
}
static int getpit(void)
{
int high, low;
// TODO: need a lock to protect access to PIT
/* Select counter 0 and latch counter value. */
outb(TIMER_MODE, TIMER_SEL0 | TIMER_LATCH);
low = inb(TIMER_CNTR0);
high = inb(TIMER_CNTR0);
return ((high << 8) | low);
}
// forces cpu to relax for usec miliseconds. declared in kern/include/time.h
void udelay(uint64_t usec)
{
#if !defined(__BOCHS__)
if (__proc_global_info.tsc_freq != 0)
{
uint64_t start, end, now;
start = read_tsc();
end = start + usec2tsc(usec);
//cprintf("start %llu, end %llu\n", start, end);
if (end == 0) cprintf("This is terribly wrong \n");
do {
cpu_relax();
now = read_tsc();
//cprintf("now %llu\n", now);
} while (now < end || (now > start && end < start));
return;
} else
#endif
{
udelay_pit(usec);
}
}
void udelay_pit(uint64_t usec)
{
int64_t delta, prev_tick, tick, ticks_left;
if (usec <= 0)
return;
prev_tick = getpit();
/*
* Calculate ticks as (usec * (i8254_freq / 1e6)) rounded up
* without using floating point and without any avoidable overflows.
*/
ticks_left = ((usec * PIT_FREQ) + 999999) / 1000000;
while (ticks_left > 0) {
tick = getpit();
delta = prev_tick - tick;
prev_tick = tick;
if (delta < 0) {
// counter looped around during the delta time period
delta += pit_divisor; // maximum count
if (delta < 0)
delta = 0;
}
ticks_left -= delta;
}
}
uint64_t gettimer(void)
{
return read_tsc();
}
uint64_t getfreq(void)
{
return __proc_global_info.tsc_freq;
}
void set_core_timer(uint32_t usec, bool periodic)
{
if (usec)
lapic_set_timer(usec, periodic);
else
lapic_disable_timer();
}