aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorArve Hjønnevåg <arve@android.com>2023-11-17 10:21:03 -0800
committerArve Hjønnevåg <arve@android.com>2024-03-30 00:29:18 +0000
commit1ec3401a7d40d62b215d99172b1d07fae0f35f58 (patch)
tree41df6e50721ecd829162a448c191d6ec8e1efb8b
parent53039a27a5f9bca3f38b911dcca0e3c245a4e793 (diff)
downloadcommon-1ec3401a7d40d62b215d99172b1d07fae0f35f58.tar.gz
dev: timer: x86_generic: Fix divisor calculation
The old code incorrectly claimed the timer could handle a 55 ms timer, and clamped the millisecond value to 55 before converting from ms to pit timer ticks. The converted value overflow when then clamped to 16 bits resulting in a timer request less than 100 microseconds into the future instead of 55 millioseconds. This resulted in 100% cpu usage while waiting for a timer 55ms or longer. Improve the precision of the conversion by converting directly from nano seconds to pit timer ticks instead of first converting to milliseconds. Then clamp the value after the conversion instead where the boundary is more clear. Also remove code that starts a periodic timer on boot that had no apparent purpose. Bug: 311709679 Change-Id: Ia2cff162b9bf2359973356a5108de87ec860d61b
-rw-r--r--dev/timer/x86_generic/x86_pit.c91
1 files changed, 25 insertions, 66 deletions
diff --git a/dev/timer/x86_generic/x86_pit.c b/dev/timer/x86_generic/x86_pit.c
index 9f4ba9c3..6c74e37a 100644
--- a/dev/timer/x86_generic/x86_pit.c
+++ b/dev/timer/x86_generic/x86_pit.c
@@ -29,38 +29,29 @@
#include <dev/timer/x86_pit.h>
#include <err.h>
#include <lib/fixed_point.h>
+#include <lk/trace.h>
#include <platform/interrupts.h>
#include <platform/timer.h>
+#define LOCAL_TRACE 0
+
static platform_timer_callback t_callback;
static struct fp_32_64 ms_per_tsc;
static struct fp_32_64 ns_per_tsc;
-static struct fp_32_64 ticks_per_ms;
+static struct fp_32_64 pit_ticks_per_ns;
/* The oscillator used by the PIT chip runs at 1.193182MHz */
#define INTERNAL_FREQ 1193182UL
-/* Maximum amount of time that can be program, in milliseconds */
-#define MAX_TIMER_INTERVAL 55
-
-/*
- * Freqency in HZ of the timer.
- * TODO: Select the frequency in platform_set_oneshot_timer based on the time
- * we actually need to sleep so we don't wake up more often than needed when
- * sleeping more than 55ms and so we get better precision when sleeping
- * significantly less than 55ms.
- */
-#define PIT_FREQUENCY 1000
-
/* Mode/Command register */
#define I8253_CONTROL_REG 0x43
/* Channel 0 data port */
#define I8253_DATA_REG 0x40
-static uint64_t lk_time_to_ticks(lk_time_t lk_time)
+static uint64_t lk_time_ns_to_pit_ticks(lk_time_ns_t lk_time_ns)
{
- return u64_mul_u32_fp32_64(lk_time, ticks_per_ms);
+ return u64_mul_u64_fp32_64(lk_time_ns, pit_ticks_per_ns);
}
static lk_time_t tsc_cnt_to_lk_time(uint64_t tsc_cnt)
@@ -127,7 +118,7 @@ lk_time_t current_time(void)
return tsc_cnt_to_lk_time(__rdtsc());
}
-static enum handler_return os_timer_tick(void* arg)
+static enum handler_return x86_pit_timer_interrupt_handler(void* arg)
{
if (t_callback) {
return t_callback(arg, current_time_ns());
@@ -136,43 +127,13 @@ static enum handler_return os_timer_tick(void* arg)
}
}
-static void set_pit_frequency(uint32_t frequency)
-{
- struct fp_32_64 result;
- uint16_t count;
-
- /* Figure out the correct divisor for the desired frequency */
- if (frequency < 1) {
- dprintf(SPEW, "invalid frequency, %u, use max count\n", frequency);
- count = 0xffff;
- } else if (frequency >= INTERNAL_FREQ) {
- dprintf(SPEW, "invalid frequency, %u, use min count\n", frequency);
- count = 1;
- } else {
- fp_32_64_div_32_32(&result, INTERNAL_FREQ, frequency);
- if (result.l0 > 0xffff) {
- dprintf(SPEW, "invalid frequency, %u, use max count\n", frequency);
- count = 0xffff;
- } else {
- count = result.l0 & 0xffff;
- }
- }
-
- /*
- * Setup the Programmable Interval Timer
- * Counter 0, mode 2, binary counter, LSB followed by MSB
- */
- outp(I8253_CONTROL_REG, 0x34);
- outp(I8253_DATA_REG, count & 0xff);
- outp(I8253_DATA_REG, count >> 8);
-}
-
static void x86_pit_init_conversion_factors(void)
{
uint64_t begin, end;
uint8_t status = 0;
+ uint16_t ticks_per_ms = INTERNAL_FREQ / 1000;
- fp_32_64_div_32_32(&ticks_per_ms, INTERNAL_FREQ, 1000);
+ fp_32_64_div_32_32(&pit_ticks_per_ns, INTERNAL_FREQ, 1000000000);
/* Set PIT mode to count down and set OUT pin high when count reaches 0 */
outp(I8253_CONTROL_REG, 0x30);
@@ -191,9 +152,9 @@ static void x86_pit_init_conversion_factors(void)
serializing_instruction();
/* Write LSB in counter 0 */
- outp(I8253_DATA_REG, ticks_per_ms.l0 & 0xff);
+ outp(I8253_DATA_REG, ticks_per_ms & 0xff);
/* Write MSB in counter 0 */
- outp(I8253_DATA_REG, ticks_per_ms.l0 >> 8);
+ outp(I8253_DATA_REG, ticks_per_ms >> 8);
do {
/* Read-back command, count MSB, counter 0 */
@@ -222,33 +183,30 @@ void x86_init_pit(void)
x86_pit_init_conversion_factors();
- /* 1ms granularity */
- set_pit_frequency(PIT_FREQUENCY);
-
- register_int_handler(INT_PIT, &os_timer_tick, NULL);
+ register_int_handler(INT_PIT, &x86_pit_timer_interrupt_handler, NULL);
unmask_interrupt(INT_PIT);
}
status_t platform_set_oneshot_timer(platform_timer_callback callback,
- lk_time_ns_t time_ns)
+ lk_time_ns_t time_ns_abs)
{
- uint32_t count;
+ uint64_t pit_ticks;
+ uint32_t pit_ticks_clamped;
uint16_t divisor;
- uint32_t time_ms;
+ lk_time_ns_t time_ns_rel = time_ns_abs - current_time_ns();
t_callback = callback;
- /* Set millisecond timer with interval */
- time_ms = (time_ns - current_time_ns()) / (1000UL * 1000);
- if (time_ms > MAX_TIMER_INTERVAL) {
- time_ms = MAX_TIMER_INTERVAL;
- } else if (time_ms < 1) {
- time_ms = 1;
- }
+ pit_ticks = lk_time_ns_to_pit_ticks(time_ns_rel);
+ /* Clamp ticks to 1 - 0x10000. 0 in the 16 bit counter means 0x10000 */
+ pit_ticks_clamped = MAX(1, MIN(pit_ticks, UINT16_MAX + 1));
+ divisor = pit_ticks_clamped & UINT16_MAX;
- count = lk_time_to_ticks(time_ms);
+ LTRACEF("time_ns_abs %" PRIu64 " -> time_ns_rel %" PRIu64
+ " -> pit_ticks %" PRIu64 " -> pit_ticks_clamped %" PRIu32
+ " -> pit_ticks %" PRIu16 "\n",
+ time_ns_abs, time_ns_rel, pit_ticks, pit_ticks_clamped, divisor);
- divisor = count & 0xffff;
/*
* Program PIT in the software strobe configuration, to send one pulse
* after the count reach 0
@@ -262,6 +220,7 @@ status_t platform_set_oneshot_timer(platform_timer_callback callback,
void platform_stop_timer(void)
{
+ LTRACE;
/* Enable interrupt mode that will stop the decreasing counter of the PIT */
outp(I8253_CONTROL_REG, 0x30);
}