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