[platform][pc] refactor existing PIT code into separate file
Extend the PIT driver to allow for one shot timers even though it monotonically runs a 1kHz tick. This allows it to keep time and provide one shot events, though only at 1ms resolution.
This commit is contained in:
@@ -24,7 +24,7 @@
|
||||
|
||||
#include "platform_p.h"
|
||||
|
||||
#define LOCAL_TRACE 1
|
||||
#define LOCAL_TRACE 0
|
||||
|
||||
static bool lapic_present = false;
|
||||
static bool lapic_x2apic = false;
|
||||
|
||||
194
platform/pc/pit.c
Normal file
194
platform/pc/pit.c
Normal file
@@ -0,0 +1,194 @@
|
||||
/*
|
||||
* Copyright (c) 2009 Corey Tabaka
|
||||
*
|
||||
* Use of this source code is governed by a MIT-style
|
||||
* license that can be found in the LICENSE file or at
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
#include <sys/types.h>
|
||||
#include <lk/err.h>
|
||||
#include <lk/reg.h>
|
||||
#include <lk/debug.h>
|
||||
#include <lk/trace.h>
|
||||
#include <kernel/thread.h>
|
||||
#include <kernel/spinlock.h>
|
||||
#include <platform.h>
|
||||
#include <platform/interrupts.h>
|
||||
#include <platform/console.h>
|
||||
#include <platform/timer.h>
|
||||
#include <platform/pc.h>
|
||||
#include "platform_p.h"
|
||||
#include <arch/x86.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#define LOCAL_TRACE 0
|
||||
|
||||
static platform_timer_callback t_callback;
|
||||
static void *callback_arg;
|
||||
static spin_lock_t lock = SPIN_LOCK_INITIAL_VALUE;
|
||||
|
||||
static uint64_t ticks_per_ms;
|
||||
|
||||
// next callback event time in 32.32 fixed point milliseconds
|
||||
static uint64_t next_trigger_time;
|
||||
|
||||
// if periodic, the delta to set to the next event. if oneshot, 0
|
||||
static uint64_t next_trigger_delta;
|
||||
|
||||
// time in 32.32 fixed point milliseconds
|
||||
static volatile uint64_t timer_current_time;
|
||||
// delta time per periodic tick in 32.32
|
||||
static uint64_t timer_delta_time;
|
||||
|
||||
#define INTERNAL_FREQ 1193182ULL
|
||||
#define INTERNAL_FREQ_3X 3579546ULL
|
||||
|
||||
/* Maximum amount of time that can be program on the timer to schedule the next
|
||||
* interrupt, in milliseconds */
|
||||
#define MAX_TIMER_INTERVAL 55
|
||||
|
||||
lk_time_t pit_current_time(void) {
|
||||
spin_lock_saved_state_t state;
|
||||
spin_lock_irqsave(&lock, state);
|
||||
|
||||
lk_time_t time = (lk_time_t) (timer_current_time >> 32);
|
||||
|
||||
spin_unlock_irqrestore(&lock, state);
|
||||
|
||||
return time;
|
||||
}
|
||||
|
||||
lk_bigtime_t pit_current_time_hires(void) {
|
||||
spin_lock_saved_state_t state;
|
||||
spin_lock_irqsave(&lock, state);
|
||||
|
||||
lk_bigtime_t time = (lk_bigtime_t) ((timer_current_time >> 22) * 1000) >> 10;
|
||||
|
||||
spin_unlock_irqrestore(&lock, state);
|
||||
|
||||
return time;
|
||||
}
|
||||
|
||||
static enum handler_return pit_timer_tick(void *arg) {
|
||||
if (next_trigger_time != 0 || next_trigger_delta) {
|
||||
LTRACEF("ntt %#" PRIx64 ", ntd %#" PRIx64 "\n", next_trigger_time, next_trigger_delta);
|
||||
}
|
||||
|
||||
spin_lock(&lock);
|
||||
timer_current_time += timer_delta_time;
|
||||
spin_unlock(&lock);
|
||||
|
||||
lk_time_t time = current_time();
|
||||
|
||||
if (t_callback && next_trigger_time != 0 && timer_current_time >= next_trigger_time) {
|
||||
if (next_trigger_delta != 0) {
|
||||
uint64_t delta = timer_current_time - next_trigger_time;
|
||||
next_trigger_time = timer_current_time + next_trigger_delta - delta;
|
||||
} else {
|
||||
next_trigger_time = 0;
|
||||
}
|
||||
|
||||
return t_callback(callback_arg, time);
|
||||
} else {
|
||||
return INT_NO_RESCHEDULE;
|
||||
}
|
||||
}
|
||||
|
||||
static void set_pit_frequency(uint32_t frequency) {
|
||||
uint32_t count, remainder;
|
||||
|
||||
LTRACEF("frequency %u\n", frequency);
|
||||
|
||||
/* figure out the correct divisor for the desired frequency */
|
||||
if (frequency <= 18) {
|
||||
count = 0xffff;
|
||||
} else if (frequency >= INTERNAL_FREQ) {
|
||||
count = 1;
|
||||
} else {
|
||||
count = INTERNAL_FREQ_3X / frequency;
|
||||
remainder = INTERNAL_FREQ_3X % frequency;
|
||||
|
||||
if (remainder >= INTERNAL_FREQ_3X / 2) {
|
||||
count += 1;
|
||||
}
|
||||
|
||||
count /= 3;
|
||||
remainder = count % 3;
|
||||
|
||||
if (remainder >= 1) {
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t divisor = count & 0xffff;
|
||||
|
||||
/*
|
||||
* funky math that i don't feel like explaining. essentially 32.32 fixed
|
||||
* point representation of the configured timer delta.
|
||||
*/
|
||||
timer_delta_time = (3685982306ULL * count) >> 10;
|
||||
|
||||
LTRACEF("dt 0x%016" PRIx64 "\n", timer_delta_time);
|
||||
LTRACEF("divisor 0x%04" PRIx16 "\n", divisor);
|
||||
|
||||
/*
|
||||
* setup the Programmable Interval Timer
|
||||
* timer 0, mode 2, binary counter, LSB followed by MSB
|
||||
*/
|
||||
outp(I8253_CONTROL_REG, 0x34);
|
||||
outp(I8253_DATA_REG, divisor & 0xff); // LSB
|
||||
outp(I8253_DATA_REG, divisor >> 8); // MSB
|
||||
}
|
||||
|
||||
void pit_init(void) {
|
||||
timer_current_time = 0;
|
||||
ticks_per_ms = INTERNAL_FREQ/1000;
|
||||
set_pit_frequency(1000); // ~1ms granularity
|
||||
register_int_handler(INT_PIT, &pit_timer_tick, NULL);
|
||||
}
|
||||
|
||||
status_t pit_set_periodic_timer(platform_timer_callback callback, void *arg, lk_time_t interval) {
|
||||
LTRACEF("pit_set_periodic_timer: interval %u\n", interval);
|
||||
|
||||
spin_lock_saved_state_t state;
|
||||
spin_lock_irqsave(&lock, state);
|
||||
|
||||
t_callback = callback;
|
||||
callback_arg = arg;
|
||||
|
||||
next_trigger_delta = (uint64_t) interval << 32;
|
||||
next_trigger_time = timer_current_time + next_trigger_delta;
|
||||
|
||||
unmask_interrupt(INT_PIT);
|
||||
spin_unlock_irqrestore(&lock, state);
|
||||
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
status_t pit_set_oneshot_timer(platform_timer_callback callback, void *arg, lk_time_t interval) {
|
||||
LTRACEF("pit_set_oneshot_timer: interval %u\n", interval);
|
||||
|
||||
spin_lock_saved_state_t state;
|
||||
spin_lock_irqsave(&lock, state);
|
||||
|
||||
t_callback = callback;
|
||||
callback_arg = arg;
|
||||
|
||||
next_trigger_delta = 0;
|
||||
next_trigger_time = timer_current_time + ((uint64_t)interval << 32);
|
||||
|
||||
unmask_interrupt(INT_PIT);
|
||||
spin_unlock_irqrestore(&lock, state);
|
||||
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
void pit_stop_timer(void) {
|
||||
LTRACE;
|
||||
|
||||
spin_lock_saved_state_t state;
|
||||
spin_lock_irqsave(&lock, state);
|
||||
|
||||
mask_interrupt(INT_PIT);
|
||||
spin_unlock_irqrestore(&lock, state);
|
||||
}
|
||||
@@ -188,9 +188,6 @@ void platform_early_init(void) {
|
||||
/* initialize the interrupt controller */
|
||||
platform_init_interrupts();
|
||||
|
||||
/* initialize the timer */
|
||||
platform_init_timer();
|
||||
|
||||
/* look at multiboot to determine our memory size */
|
||||
size_t found_arenas;
|
||||
platform_parse_multiboot_info(&found_arenas);
|
||||
|
||||
@@ -8,13 +8,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <lib/cbuf.h>
|
||||
#include <platform/timer.h>
|
||||
|
||||
extern cbuf_t console_input_buf;
|
||||
|
||||
void platform_init_debug_early(void);
|
||||
void platform_init_debug(void);
|
||||
void platform_init_interrupts(void);
|
||||
void platform_init_timer(void);
|
||||
|
||||
// legacy programmable interrupt controller
|
||||
void pic_init(void);
|
||||
@@ -29,5 +29,13 @@ void lapic_send_init_ipi(uint32_t apic_id, bool level);
|
||||
void lapic_send_startup_ipi(uint32_t apic_id, uint32_t startup_vector);
|
||||
void lapic_send_ipi(uint32_t apic_id, uint32_t vector);
|
||||
|
||||
// programable interval timer
|
||||
void pit_init(void);
|
||||
status_t pit_set_periodic_timer(platform_timer_callback callback, void *arg, lk_time_t interval);
|
||||
status_t pit_set_oneshot_timer(platform_timer_callback callback, void *arg, lk_time_t interval);
|
||||
void pit_stop_timer(void);
|
||||
lk_time_t pit_current_time(void);
|
||||
lk_bigtime_t pit_current_time_hires(void);
|
||||
|
||||
// secondary cpus
|
||||
void platform_start_secondary_cpus(void);
|
||||
|
||||
@@ -26,11 +26,15 @@ MODULE_SRCS += \
|
||||
$(LOCAL_DIR)/mp.c \
|
||||
$(LOCAL_DIR)/mp-boot.S \
|
||||
$(LOCAL_DIR)/pic.c \
|
||||
$(LOCAL_DIR)/pit.c \
|
||||
$(LOCAL_DIR)/platform.c \
|
||||
$(LOCAL_DIR)/timer.c \
|
||||
$(LOCAL_DIR)/uart.c \
|
||||
|
||||
LK_HEAP_IMPLEMENTATION ?= dlmalloc
|
||||
|
||||
GLOBAL_DEFINES += \
|
||||
PLATFORM_HAS_DYNAMIC_TIMER=1
|
||||
|
||||
include make/module.mk
|
||||
|
||||
|
||||
@@ -1,184 +1,85 @@
|
||||
/*
|
||||
* Copyright (c) 2009 Corey Tabaka
|
||||
* Copyright (c) 2025 Travis Geiselbrecht
|
||||
*
|
||||
* Use of this source code is governed by a MIT-style
|
||||
* license that can be found in the LICENSE file or at
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
#include <sys/types.h>
|
||||
#include <lk/err.h>
|
||||
#include <lk/reg.h>
|
||||
#include <lk/debug.h>
|
||||
#include <lk/err.h>
|
||||
#include <lk/init.h>
|
||||
#include <lk/reg.h>
|
||||
#include <lk/trace.h>
|
||||
#include <kernel/thread.h>
|
||||
#include <kernel/spinlock.h>
|
||||
#include <platform.h>
|
||||
#include <platform/interrupts.h>
|
||||
#include <platform/console.h>
|
||||
#include <platform/timer.h>
|
||||
#include <platform/pc.h>
|
||||
#include "platform_p.h"
|
||||
#include <arch/x86.h>
|
||||
|
||||
static platform_timer_callback t_callback;
|
||||
static void *callback_arg;
|
||||
static spin_lock_t lock;
|
||||
#define LOCAL_TRACE 0
|
||||
|
||||
static uint64_t next_trigger_time;
|
||||
static uint64_t next_trigger_delta;
|
||||
static uint64_t ticks_per_ms;
|
||||
// Deals with all of the various clock sources and event timers on the PC platform.
|
||||
|
||||
static uint64_t timer_delta_time;
|
||||
static volatile uint64_t timer_current_time;
|
||||
static enum clock_source {
|
||||
CLOCK_SOURCE_INITIAL,
|
||||
CLOCK_SOURCE_PIT,
|
||||
CLOCK_SOURCE_TSC,
|
||||
CLOCK_SOURCE_HPET,
|
||||
} clock_source = CLOCK_SOURCE_INITIAL;
|
||||
|
||||
static uint16_t divisor;
|
||||
|
||||
#define INTERNAL_FREQ 1193182ULL
|
||||
#define INTERNAL_FREQ_3X 3579546ULL
|
||||
|
||||
/* Maximum amount of time that can be program on the timer to schedule the next
|
||||
* interrupt, in milliseconds */
|
||||
#define MAX_TIMER_INTERVAL 55
|
||||
|
||||
|
||||
|
||||
status_t platform_set_periodic_timer(platform_timer_callback callback, void *arg, lk_time_t interval) {
|
||||
t_callback = callback;
|
||||
callback_arg = arg;
|
||||
|
||||
next_trigger_delta = (uint64_t) interval << 32;
|
||||
next_trigger_time = timer_current_time + next_trigger_delta;
|
||||
|
||||
return NO_ERROR;
|
||||
static const char *clock_source_name(void) {
|
||||
switch (clock_source) {
|
||||
case CLOCK_SOURCE_INITIAL:
|
||||
return "initial";
|
||||
case CLOCK_SOURCE_PIT:
|
||||
return "pit";
|
||||
case CLOCK_SOURCE_TSC:
|
||||
return "tsc";
|
||||
case CLOCK_SOURCE_HPET:
|
||||
return "hpet";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
lk_time_t current_time(void) {
|
||||
lk_time_t time;
|
||||
|
||||
// XXX slight race
|
||||
time = (lk_time_t) (timer_current_time >> 32);
|
||||
|
||||
return time;
|
||||
switch (clock_source) {
|
||||
case CLOCK_SOURCE_PIT:
|
||||
return pit_current_time();
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
lk_bigtime_t current_time_hires(void) {
|
||||
lk_bigtime_t time;
|
||||
|
||||
// XXX slight race
|
||||
time = (lk_bigtime_t) ((timer_current_time >> 22) * 1000) >> 10;
|
||||
|
||||
return time;
|
||||
}
|
||||
static enum handler_return os_timer_tick(void *arg) {
|
||||
uint64_t delta;
|
||||
|
||||
timer_current_time += timer_delta_time;
|
||||
|
||||
lk_time_t time = current_time();
|
||||
//lk_bigtime_t btime = current_time_hires();
|
||||
//printf_xy(71, 0, WHITE, "%08u", (uint32_t) time);
|
||||
//printf_xy(63, 1, WHITE, "%016llu", (uint64_t) btime);
|
||||
|
||||
if (t_callback && timer_current_time >= next_trigger_time) {
|
||||
delta = timer_current_time - next_trigger_time;
|
||||
next_trigger_time = timer_current_time + next_trigger_delta - delta;
|
||||
|
||||
return t_callback(callback_arg, time);
|
||||
} else {
|
||||
return INT_NO_RESCHEDULE;
|
||||
switch (clock_source) {
|
||||
case CLOCK_SOURCE_PIT:
|
||||
return pit_current_time_hires();
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void set_pit_frequency(uint32_t frequency) {
|
||||
uint32_t count, remainder;
|
||||
void pc_init_timer(unsigned int level) {
|
||||
LTRACE_ENTRY;
|
||||
|
||||
/* figure out the correct divisor for the desired frequency */
|
||||
if (frequency <= 18) {
|
||||
count = 0xffff;
|
||||
} else if (frequency >= INTERNAL_FREQ) {
|
||||
count = 1;
|
||||
} else {
|
||||
count = INTERNAL_FREQ_3X / frequency;
|
||||
remainder = INTERNAL_FREQ_3X % frequency;
|
||||
|
||||
if (remainder >= INTERNAL_FREQ_3X / 2) {
|
||||
count += 1;
|
||||
}
|
||||
|
||||
count /= 3;
|
||||
remainder = count % 3;
|
||||
|
||||
if (remainder >= 1) {
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
divisor = count & 0xffff;
|
||||
|
||||
/*
|
||||
* funky math that i don't feel like explaining. essentially 32.32 fixed
|
||||
* point representation of the configured timer delta.
|
||||
*/
|
||||
timer_delta_time = (3685982306ULL * count) >> 10;
|
||||
|
||||
//dprintf(DEBUG, "set_pit_frequency: dt=%016llx\n", timer_delta_time);
|
||||
//dprintf(DEBUG, "set_pit_frequency: divisor=%04x\n", divisor);
|
||||
|
||||
/*
|
||||
* setup the Programmable Interval Timer
|
||||
* timer 0, mode 2, binary counter, LSB followed by MSB
|
||||
*/
|
||||
outp(I8253_CONTROL_REG, 0x34);
|
||||
outp(I8253_DATA_REG, divisor & 0xff); // LSB
|
||||
outp(I8253_DATA_REG, divisor >> 8); // MSB
|
||||
pit_init();
|
||||
clock_source = CLOCK_SOURCE_PIT;
|
||||
}
|
||||
|
||||
void platform_init_timer(void) {
|
||||
LK_INIT_HOOK(pc_timer, pc_init_timer, LK_INIT_LEVEL_VM);
|
||||
|
||||
timer_current_time = 0;
|
||||
ticks_per_ms = INTERNAL_FREQ/1000;
|
||||
set_pit_frequency(1000); // ~1ms granularity
|
||||
register_int_handler(INT_PIT, &os_timer_tick, NULL);
|
||||
unmask_interrupt(INT_PIT);
|
||||
}
|
||||
|
||||
static void platform_halt_timers(void) {
|
||||
mask_interrupt(INT_PIT);
|
||||
status_t platform_set_periodic_timer(platform_timer_callback callback, void *arg, lk_time_t interval) {
|
||||
return pit_set_periodic_timer(callback, arg, interval);
|
||||
}
|
||||
|
||||
status_t platform_set_oneshot_timer(platform_timer_callback callback,
|
||||
void *arg, lk_time_t interval) {
|
||||
uint32_t count;
|
||||
|
||||
spin_lock_saved_state_t state;
|
||||
spin_lock_irqsave(&lock, state);
|
||||
|
||||
t_callback = callback;
|
||||
callback_arg = arg;
|
||||
|
||||
|
||||
if (interval > MAX_TIMER_INTERVAL)
|
||||
interval = MAX_TIMER_INTERVAL;
|
||||
if (interval < 1) interval = 1;
|
||||
|
||||
count = ticks_per_ms * interval;
|
||||
|
||||
divisor = count & 0xffff;
|
||||
timer_delta_time = (3685982306ULL * count) >> 10;
|
||||
/* Program PIT in the software strobe configuration, to send one pulse
|
||||
* after the count reach 0 */
|
||||
outp(I8253_CONTROL_REG, 0x38);
|
||||
outp(I8253_DATA_REG, divisor & 0xff); // LSB
|
||||
outp(I8253_DATA_REG, divisor >> 8); // MSB
|
||||
|
||||
|
||||
unmask_interrupt(INT_PIT);
|
||||
spin_unlock_irqrestore(&lock, state);
|
||||
|
||||
return NO_ERROR;
|
||||
return pit_set_oneshot_timer(callback, arg, interval);
|
||||
}
|
||||
|
||||
void platform_stop_timer(void) {
|
||||
/* Enable interrupt mode that will stop the decreasing counter of the PIT */
|
||||
outp(I8253_CONTROL_REG, 0x30);
|
||||
return;
|
||||
pit_stop_timer();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user