1 /*
2 * Copyright (c) 2025 Travis Geiselbrecht
3 *
4 * Use of this source code is governed by a MIT-style
5 * license that can be found in the LICENSE file or at
6 * https://opensource.org/licenses/MIT
7 */
8 #include <sys/types.h>
9 #include <lk/debug.h>
10 #include <lk/err.h>
11 #include <lk/init.h>
12 #include <lk/reg.h>
13 #include <lk/trace.h>
14 #include <kernel/thread.h>
15 #include <kernel/vm.h>
16 #include <platform.h>
17 #include <platform/timer.h>
18 #include <platform/pc.h>
19 #include <platform/pc/timer.h>
20 #include <arch/x86.h>
21 #include <arch/x86/feature.h>
22 #include <arch/x86/lapic.h>
23 #include <arch/x86/pv.h>
24 #include <inttypes.h>
25 #include <lib/fixed_point.h>
26
27 #include "platform_p.h"
28
29 #define LOCAL_TRACE 0
30
31 // Deals with all of the various clock sources and event timers on the PC platform.
32 // TODO:
33 // HPET
34 // cpuid leaves that describe clock rates
35
36 static enum clock_source {
37 CLOCK_SOURCE_INITIAL,
38 CLOCK_SOURCE_PIT,
39 CLOCK_SOURCE_TSC,
40 CLOCK_SOURCE_HPET,
41 } clock_source = CLOCK_SOURCE_INITIAL;
42
43 static struct fp_32_64 tsc_to_timebase;
44 static struct fp_32_64 tsc_to_timebase_hires;
45 static struct fp_32_64 timebase_to_tsc;
46 static bool use_lapic_timer = false;
47
clock_source_name(void)48 static const char *clock_source_name(void) {
49 switch (clock_source) {
50 case CLOCK_SOURCE_INITIAL:
51 return "initial";
52 case CLOCK_SOURCE_PIT:
53 return "PIT";
54 case CLOCK_SOURCE_TSC:
55 return "TSC";
56 case CLOCK_SOURCE_HPET:
57 return "HPET";
58 default:
59 return "unknown";
60 }
61 }
62
current_time(void)63 lk_time_t current_time(void) {
64 switch (clock_source) {
65 case CLOCK_SOURCE_PIT:
66 return pit_current_time();
67 case CLOCK_SOURCE_TSC:
68 return u32_mul_u64_fp32_64(__builtin_ia32_rdtsc(), tsc_to_timebase);
69 default:
70 return 0;
71 }
72 }
73
current_time_hires(void)74 lk_bigtime_t current_time_hires(void) {
75 switch (clock_source) {
76 case CLOCK_SOURCE_PIT:
77 return pit_current_time_hires();
78 case CLOCK_SOURCE_TSC:
79 return u64_mul_u64_fp32_64(__builtin_ia32_rdtsc(), tsc_to_timebase_hires);
80 default:
81 return 0;
82 }
83 }
84
85 // Convert lk_time_t to TSC ticks
time_to_tsc_ticks(lk_time_t time)86 uint64_t time_to_tsc_ticks(lk_time_t time) {
87 return u64_mul_u32_fp32_64(time, timebase_to_tsc);
88 }
89
pc_init_timer(unsigned int level)90 void pc_init_timer(unsigned int level) {
91 // Initialize the PIT, it's always present in PC hardware
92 pit_init();
93 clock_source = CLOCK_SOURCE_PIT;
94
95 #if !X86_LEGACY
96 // XXX update note about what invariant TSC means
97 bool use_invariant_tsc = x86_feature_test(X86_FEATURE_INVAR_TSC);
98 LTRACEF("invariant TSC %d\n", use_invariant_tsc);
99
100 // Test for hypervisor PV clock, which also effectively says if TSC is invariant across
101 // all cpus.
102 if (pvclock_init() == NO_ERROR) {
103 bool pv_clock_stable = pv_clock_is_stable();
104
105 use_invariant_tsc |= pv_clock_stable;
106
107 printf("pv_clock: Clocksource is %sstable\n", (pv_clock_stable ? "" : "not "));
108 }
109
110 // XXX test for HPET and use it over PIT if present
111
112 if (use_invariant_tsc) {
113 // We're going to try to use the TSC as a time base, obtain the TSC frequency.
114 uint64_t tsc_hz = 0;
115
116 tsc_hz = pvclock_get_tsc_freq();
117 if (tsc_hz == 0) {
118 // TODO: some x86 cores describe the TSC and lapic clocks in cpuid
119
120 // Calibrate the TSC against the PIT, which should always be present
121 tsc_hz = pit_calibrate_tsc();
122 if (tsc_hz == 0) {
123 dprintf(CRITICAL, "PC: failed to calibrate TSC frequency\n");
124 goto out;
125 }
126 }
127
128 dprintf(INFO, "PC: TSC frequency %" PRIu64 "Hz\n", tsc_hz);
129
130 // Compute the ratio of TSC to timebase
131 fp_32_64_div_32_32(&tsc_to_timebase, 1000, tsc_hz);
132 dprintf(INFO, "PC: TSC to timebase ratio %u.%08u...\n",
133 tsc_to_timebase.l0, tsc_to_timebase.l32);
134
135 fp_32_64_div_32_32(&tsc_to_timebase_hires, 1000*1000, tsc_hz);
136 dprintf(INFO, "PC: TSC to hires timebase ratio %u.%08u...\n",
137 tsc_to_timebase_hires.l0, tsc_to_timebase_hires.l32);
138
139 fp_32_64_div_32_32(&timebase_to_tsc, tsc_hz, 1000);
140 dprintf(INFO, "PC: timebase to TSC ratio %u.%08u...\n",
141 timebase_to_tsc.l0, timebase_to_tsc.l32);
142
143 clock_source = CLOCK_SOURCE_TSC;
144 }
145 out:
146
147 // Set up the local apic for event timer interrupts
148 if (lapic_timer_init(use_invariant_tsc) == NO_ERROR) {
149 dprintf(INFO, "PC: using LAPIC timer for event timer\n");
150 use_lapic_timer = true;
151 }
152
153 // If we're not using the PIT for time base and using the LAPIC timer for events, stop the PIT.
154 if (use_lapic_timer && clock_source != CLOCK_SOURCE_PIT) {
155 pit_stop_timer();
156 }
157
158 #endif // !X86_LEGACY
159
160 dprintf(INFO, "PC: using %s clock source\n", clock_source_name());
161 }
162
163 LK_INIT_HOOK(pc_timer, pc_init_timer, LK_INIT_LEVEL_VM + 2);
164
platform_set_periodic_timer(platform_timer_callback callback,void * arg,lk_time_t interval)165 status_t platform_set_periodic_timer(platform_timer_callback callback, void *arg, lk_time_t interval) {
166 if (use_lapic_timer) {
167 PANIC_UNIMPLEMENTED;
168 }
169 return pit_set_periodic_timer(callback, arg, interval);
170 }
171
platform_set_oneshot_timer(platform_timer_callback callback,void * arg,lk_time_t interval)172 status_t platform_set_oneshot_timer(platform_timer_callback callback,
173 void *arg, lk_time_t interval) {
174 if (use_lapic_timer) {
175 return lapic_set_oneshot_timer(callback, arg, interval);
176 }
177 return pit_set_oneshot_timer(callback, arg, interval);
178 }
179
platform_stop_timer(void)180 void platform_stop_timer(void) {
181 if (use_lapic_timer) {
182 lapic_cancel_timer();
183 } else {
184 pit_cancel_timer();
185 }
186 }
187