280 lines
6.9 KiB
C
280 lines
6.9 KiB
C
/**
|
|
* @file kernel/arch/x86_64/cmos.c
|
|
* @author K. Lange
|
|
* @brief Real-time clock.
|
|
*
|
|
* Provides access to the CMOS RTC for initial boot time and
|
|
* calibrates the TSC to use as a general timing source. IRQ 0
|
|
* handler is also in here because it updates the wall clock time
|
|
* and triggers timeout-based wakeups.
|
|
*/
|
|
#include <kernel/printf.h>
|
|
#include <kernel/string.h>
|
|
#include <kernel/process.h>
|
|
#include <kernel/arch/x86_64/ports.h>
|
|
#include <kernel/arch/x86_64/irq.h>
|
|
#include <sys/time.h>
|
|
|
|
uint64_t arch_boot_time = 0;
|
|
uint64_t tsc_basis_time = 0;
|
|
|
|
unsigned long tsc_mhz = 3500; /* XXX */
|
|
|
|
#define from_bcd(val) ((val / 16) * 10 + (val & 0xf))
|
|
#define CMOS_ADDRESS 0x70
|
|
#define CMOS_DATA 0x71
|
|
|
|
enum {
|
|
CMOS_SECOND = 0,
|
|
CMOS_MINUTE = 2,
|
|
CMOS_HOUR = 4,
|
|
CMOS_DAY = 7,
|
|
CMOS_MONTH = 8,
|
|
CMOS_YEAR = 9
|
|
};
|
|
|
|
static void cmos_dump(uint16_t * values) {
|
|
for (uint16_t index = 0; index < 128; ++index) {
|
|
outportb(CMOS_ADDRESS, index);
|
|
values[index] = inportb(CMOS_DATA);
|
|
}
|
|
}
|
|
|
|
static int is_update_in_progress(void) {
|
|
outportb(CMOS_ADDRESS, 0x0a);
|
|
return inportb(CMOS_DATA) & 0x80;
|
|
}
|
|
|
|
static uint32_t secs_of_years(int years) {
|
|
uint32_t days = 0;
|
|
years += 2000;
|
|
while (years > 1969) {
|
|
days += 365;
|
|
if (years % 4 == 0) {
|
|
if (years % 100 == 0) {
|
|
if (years % 400 == 0) {
|
|
days++;
|
|
}
|
|
} else {
|
|
days++;
|
|
}
|
|
}
|
|
years--;
|
|
}
|
|
return days * 86400;
|
|
}
|
|
|
|
static uint32_t secs_of_month(int months, int year) {
|
|
year += 2000;
|
|
|
|
uint32_t days = 0;
|
|
switch(months) {
|
|
case 11:
|
|
days += 30; /* fallthrough */
|
|
case 10:
|
|
days += 31; /* fallthrough */
|
|
case 9:
|
|
days += 30; /* fallthrough */
|
|
case 8:
|
|
days += 31; /* fallthrough */
|
|
case 7:
|
|
days += 31; /* fallthrough */
|
|
case 6:
|
|
days += 30; /* fallthrough */
|
|
case 5:
|
|
days += 31; /* fallthrough */
|
|
case 4:
|
|
days += 30; /* fallthrough */
|
|
case 3:
|
|
days += 31; /* fallthrough */
|
|
case 2:
|
|
days += 28;
|
|
if ((year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0))) {
|
|
days++;
|
|
} /* fallthrough */
|
|
case 1:
|
|
days += 31; /* fallthrough */
|
|
default:
|
|
break;
|
|
}
|
|
return days * 86400;
|
|
}
|
|
|
|
uint32_t read_cmos(void) {
|
|
uint16_t values[128];
|
|
uint16_t old_values[128];
|
|
|
|
while (is_update_in_progress());
|
|
cmos_dump(values);
|
|
|
|
do {
|
|
memcpy(old_values, values, 128);
|
|
while (is_update_in_progress());
|
|
cmos_dump(values);
|
|
} while ((old_values[CMOS_SECOND] != values[CMOS_SECOND]) ||
|
|
(old_values[CMOS_MINUTE] != values[CMOS_MINUTE]) ||
|
|
(old_values[CMOS_HOUR] != values[CMOS_HOUR]) ||
|
|
(old_values[CMOS_DAY] != values[CMOS_DAY]) ||
|
|
(old_values[CMOS_MONTH] != values[CMOS_MONTH]) ||
|
|
(old_values[CMOS_YEAR] != values[CMOS_YEAR]));
|
|
|
|
/* Math Time */
|
|
uint32_t time =
|
|
secs_of_years(from_bcd(values[CMOS_YEAR]) - 1) +
|
|
secs_of_month(from_bcd(values[CMOS_MONTH]) - 1,
|
|
from_bcd(values[CMOS_YEAR])) +
|
|
(from_bcd(values[CMOS_DAY]) - 1) * 86400 +
|
|
(from_bcd(values[CMOS_HOUR])) * 3600 +
|
|
(from_bcd(values[CMOS_MINUTE])) * 60 +
|
|
from_bcd(values[CMOS_SECOND]) + 0;
|
|
|
|
return time;
|
|
}
|
|
|
|
static inline uint64_t read_tsc(void) {
|
|
uint32_t lo, hi;
|
|
asm volatile ( "rdtsc" : "=a"(lo), "=d"(hi) );
|
|
return ((uint64_t)hi << 32UL) | (uint64_t)lo;
|
|
}
|
|
|
|
uint64_t arch_perf_timer(void) {
|
|
return read_tsc();
|
|
}
|
|
|
|
size_t arch_cpu_mhz(void) {
|
|
return tsc_mhz;
|
|
}
|
|
|
|
void arch_clock_initialize(void) {
|
|
dprintf("tsc: Calibrating system timestamp counter.\n");
|
|
arch_boot_time = read_cmos();
|
|
uintptr_t end_lo, end_hi;
|
|
uint32_t start_lo, start_hi;
|
|
asm volatile (
|
|
/* Disables and sets gating for channel 2 */
|
|
"inb $0x61, %%al\n"
|
|
"andb $0xDD, %%al\n"
|
|
"orb $0x01, %%al\n"
|
|
"outb %%al, $0x61\n"
|
|
/* Configure channel 2 to one-shot, next two bytes are low/high */
|
|
"movb $0xB2, %%al\n" /* 0b10110010 */
|
|
"outb %%al, $0x43\n"
|
|
/* 0x__9b */
|
|
"movb $0x9B, %%al\n"
|
|
"outb %%al, $0x42\n"
|
|
"inb $0x60, %%al\n"
|
|
/* 0x2e__ */
|
|
"movb $0x2E, %%al\n"
|
|
"outb %%al, $0x42\n"
|
|
/* Re-enable */
|
|
"inb $0x61, %%al\n"
|
|
"andb $0xDE, %%al\n"
|
|
"outb %%al, $0x61\n"
|
|
/* Pulse high */
|
|
"orb $0x01, %%al\n"
|
|
"outb %%al, $0x61\n"
|
|
/* Read TSC and store in vars */
|
|
"rdtsc\n"
|
|
"movl %%eax, %2\n"
|
|
"movl %%edx, %3\n"
|
|
/* In QEMU and VirtualBox, this seems to flip low.
|
|
* On real hardware and VMware it flips high. */
|
|
"inb $0x61, %%al\n"
|
|
"andb $0x20, %%al\n"
|
|
"jz 2f\n"
|
|
/* Loop until output goes low? */
|
|
"1:\n"
|
|
"inb $0x61, %%al\n"
|
|
"andb $0x20, %%al\n"
|
|
"jnz 1b\n"
|
|
"rdtsc\n"
|
|
"jmp 3f\n"
|
|
/* Loop until output goes high */
|
|
"2:\n"
|
|
"inb $0x61, %%al\n"
|
|
"andb $0x20, %%al\n"
|
|
"jz 2b\n"
|
|
"rdtsc\n"
|
|
"3:\n"
|
|
: "=a"(end_lo), "=d"(end_hi), "=r"(start_lo), "=r"(start_hi)
|
|
);
|
|
|
|
uintptr_t end = ((end_hi & 0xFFFFffff) << 32) | (end_lo & 0xFFFFffff);
|
|
uintptr_t start = ((uintptr_t)(start_hi & 0xFFFFffff) << 32) | (start_lo & 0xFFFFffff);
|
|
tsc_mhz = (end - start) / 10000;
|
|
if (tsc_mhz == 0) tsc_mhz = 2000; /* uh oh */
|
|
tsc_basis_time = start / tsc_mhz;
|
|
|
|
dprintf("tsc: TSC timed at %lu MHz..\n", tsc_mhz);
|
|
dprintf("tsc: Boot time is %lus.\n", arch_boot_time);
|
|
dprintf("tsc: Initial TSC timestamp was %luus.\n", tsc_basis_time);
|
|
}
|
|
|
|
#define SUBSECONDS_PER_SECOND 1000000
|
|
static void update_ticks(uint64_t ticks, uint64_t *timer_ticks, uint64_t *timer_subticks) {
|
|
*timer_subticks = ticks - tsc_basis_time;
|
|
*timer_ticks = *timer_subticks / SUBSECONDS_PER_SECOND;
|
|
*timer_subticks = *timer_subticks % SUBSECONDS_PER_SECOND;
|
|
}
|
|
|
|
int gettimeofday(struct timeval * t, void *z) {
|
|
uint64_t tsc = read_tsc();
|
|
uint64_t timer_ticks, timer_subticks;
|
|
update_ticks(tsc / tsc_mhz, &timer_ticks, &timer_subticks);
|
|
t->tv_sec = arch_boot_time + timer_ticks;
|
|
t->tv_usec = timer_subticks;
|
|
return 0;
|
|
}
|
|
|
|
uint64_t now(void) {
|
|
struct timeval t;
|
|
gettimeofday(&t, NULL);
|
|
return t.tv_sec;
|
|
}
|
|
|
|
|
|
void relative_time(unsigned long seconds, unsigned long subseconds, unsigned long * out_seconds, unsigned long * out_subseconds) {
|
|
if (!arch_boot_time) {
|
|
*out_seconds = 0;
|
|
*out_subseconds = 0;
|
|
return;
|
|
}
|
|
|
|
uint64_t tsc = read_tsc();
|
|
uint64_t timer_ticks, timer_subticks;
|
|
update_ticks(tsc / tsc_mhz, &timer_ticks, &timer_subticks);
|
|
if (subseconds + timer_subticks >= SUBSECONDS_PER_SECOND) {
|
|
*out_seconds = timer_ticks + seconds + (subseconds + timer_subticks) / SUBSECONDS_PER_SECOND;
|
|
*out_subseconds = (subseconds + timer_subticks) % SUBSECONDS_PER_SECOND;
|
|
} else {
|
|
*out_seconds = timer_ticks + seconds;
|
|
*out_subseconds = timer_subticks + subseconds;
|
|
}
|
|
}
|
|
|
|
void arch_tick_others(void);
|
|
|
|
static uint64_t time_slice_basis = 0;
|
|
|
|
int cmos_time_stuff(struct regs *r) {
|
|
uint64_t clock_ticks = read_tsc() / tsc_mhz;
|
|
uint64_t timer_ticks, timer_subticks;
|
|
update_ticks(clock_ticks, &timer_ticks, &timer_subticks);
|
|
wakeup_sleepers(timer_ticks, timer_subticks);
|
|
irq_ack(0);
|
|
|
|
if (time_slice_basis + SUBSECONDS_PER_SECOND/4 <= clock_ticks) {
|
|
update_process_usage(clock_ticks - time_slice_basis, tsc_mhz);
|
|
time_slice_basis = clock_ticks;
|
|
}
|
|
|
|
arch_tick_others();
|
|
switch_task(1);
|
|
asm volatile (
|
|
".global _ret_from_preempt_source\n"
|
|
"_ret_from_preempt_source:"
|
|
);
|
|
return 1;
|
|
}
|
|
|