1 /*
2 * Copyright (c) 2009 Corey Tabaka
3 * Copyright (c) 2012-2019 LK Trusty Authors. All Rights Reserved.
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining
6 * a copy of this software and associated documentation files
7 * (the "Software"), to deal in the Software without restriction,
8 * including without limitation the rights to use, copy, modify, merge,
9 * publish, distribute, sublicense, and/or sell copies of the Software,
10 * and to permit persons to whom the Software is furnished to do so,
11 * subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be
14 * included in all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 */
24
25 #include <arch/x86.h>
26 #include <bits.h>
27 #include <debug.h>
28 #include <dev/interrupt/x86_interrupts.h>
29 #include <dev/timer/x86_pit.h>
30 #include <err.h>
31 #include <lib/fixed_point.h>
32 #include <lk/trace.h>
33 #include <platform/interrupts.h>
34 #include <platform/timer.h>
35
36 #define LOCAL_TRACE 0
37
38 static platform_timer_callback t_callback;
39
40 static struct fp_32_64 ms_per_tsc;
41 static struct fp_32_64 ns_per_tsc;
42 static struct fp_32_64 pit_ticks_per_ns;
43
44 /* The oscillator used by the PIT chip runs at 1.193182MHz */
45 #define INTERNAL_FREQ 1193182UL
46
47 /* Mode/Command register */
48 #define I8253_CONTROL_REG 0x43
49 /* Channel 0 data port */
50 #define I8253_DATA_REG 0x40
51
lk_time_ns_to_pit_ticks(lk_time_ns_t lk_time_ns)52 static uint64_t lk_time_ns_to_pit_ticks(lk_time_ns_t lk_time_ns)
53 {
54 return u64_mul_u64_fp32_64(lk_time_ns, pit_ticks_per_ns);
55 }
56
tsc_cnt_to_lk_time(uint64_t tsc_cnt)57 static lk_time_t tsc_cnt_to_lk_time(uint64_t tsc_cnt)
58 {
59 return u32_mul_u64_fp32_64(tsc_cnt, ms_per_tsc);
60 }
61
tsc_cnt_to_lk_time_ns(uint64_t tsc_cnt)62 static lk_time_ns_t tsc_cnt_to_lk_time_ns(uint64_t tsc_cnt)
63 {
64 return u64_mul_u64_fp32_64(tsc_cnt, ns_per_tsc);
65 }
66
serializing_instruction(void)67 static inline void serializing_instruction(void)
68 {
69 uint32_t unused = 0;
70
71 cpuid(unused, &unused, &unused, &unused, &unused);
72 }
73
is_constant_tsc_avail(void)74 static bool is_constant_tsc_avail(void)
75 {
76 uint32_t version_info;
77 uint32_t unused;
78 uint8_t family;
79 uint8_t model;
80
81 cpuid(X86_CPUID_VERSION_INFO, &version_info, &unused, &unused, &unused);
82 model = (version_info >> 4) & 0x0F;
83 family = (version_info >> 8) & 0x0F;
84
85 /*
86 * According to description in IDSM vol3 chapter 17.17, the time stamp
87 * counter of following processors increments at a constant rate.
88 * TSC would be stopped at deep ACPI C-states.
89 */
90 return !!(((family == 0x0F) && (model > 0x03)) ||
91 ((family == 0x06) && (model == 0x0E)) ||
92 ((family == 0x06) && (model == 0x0F)) ||
93 ((family == 0x06) && (model == 0x17)) ||
94 ((family == 0x06) && (model == 0x1C)));
95 }
96
is_invariant_tsc_avail(void)97 static bool is_invariant_tsc_avail(void)
98 {
99 uint32_t invariant_tsc;
100 uint32_t unused;
101
102 cpuid(0x80000007, &unused, &unused, &unused, &invariant_tsc);
103
104 /*
105 * According to description in IDSM vol3 chapter 17.17, the invariant
106 * TSC will run at a constant rate in all ACPI P-, C- and T-states.
107 */
108 return !!BIT(invariant_tsc, 8);
109 }
110
current_time_ns(void)111 lk_time_ns_t current_time_ns(void)
112 {
113 return tsc_cnt_to_lk_time_ns(__rdtsc());
114 }
115
current_time(void)116 lk_time_t current_time(void)
117 {
118 return tsc_cnt_to_lk_time(__rdtsc());
119 }
120
x86_pit_timer_interrupt_handler(void * arg)121 static enum handler_return x86_pit_timer_interrupt_handler(void* arg)
122 {
123 if (t_callback) {
124 return t_callback(arg, current_time_ns());
125 } else {
126 return INT_NO_RESCHEDULE;
127 }
128 }
129
x86_pit_init_conversion_factors(void)130 static void x86_pit_init_conversion_factors(void)
131 {
132 uint64_t begin, end;
133 uint8_t status = 0;
134 uint16_t ticks_per_ms = INTERNAL_FREQ / 1000;
135
136 fp_32_64_div_32_32(&pit_ticks_per_ns, INTERNAL_FREQ, 1000000000);
137
138 /* Set PIT mode to count down and set OUT pin high when count reaches 0 */
139 outp(I8253_CONTROL_REG, 0x30);
140
141 /*
142 * According to ISDM vol3 chapter 17.17:
143 * The RDTSC instruction is not serializing or ordered with other
144 * instructions. Subsequent instructions may begin execution before
145 * the RDTSC instruction operation is performed.
146 *
147 * Insert serializing instruction before and after RDTSC to make
148 * calibration more accurate.
149 */
150 serializing_instruction();
151 begin = __rdtsc();
152 serializing_instruction();
153
154 /* Write LSB in counter 0 */
155 outp(I8253_DATA_REG, ticks_per_ms & 0xff);
156 /* Write MSB in counter 0 */
157 outp(I8253_DATA_REG, ticks_per_ms >> 8);
158
159 do {
160 /* Read-back command, count MSB, counter 0 */
161 outp(I8253_CONTROL_REG, 0xE2);
162 /* Wait till OUT pin goes high and null count goes low */
163 status = inp(I8253_DATA_REG);
164 } while ((status & 0xC0) != 0x80);
165
166 /* Make sure all instructions above executed and submitted */
167 serializing_instruction();
168 end = __rdtsc();
169 serializing_instruction();
170
171 /* Enable interrupt mode that will stop the decreasing counter of the PIT */
172 outp(I8253_CONTROL_REG, 0x30);
173
174 fp_32_64_div_32_32(&ms_per_tsc, 1, (end - begin));
175 fp_32_64_div_32_32(&ns_per_tsc, 1000 * 1000, (end - begin));
176 }
177
x86_init_pit(void)178 void x86_init_pit(void)
179 {
180 if(!is_invariant_tsc_avail() && !is_constant_tsc_avail()) {
181 dprintf(INFO, "CAUTION: Current time is inaccurate!\n");
182 }
183
184 x86_pit_init_conversion_factors();
185
186 register_int_handler(INT_PIT, &x86_pit_timer_interrupt_handler, NULL);
187 unmask_interrupt(INT_PIT);
188 }
189
platform_set_oneshot_timer(platform_timer_callback callback,lk_time_ns_t time_ns_abs)190 status_t platform_set_oneshot_timer(platform_timer_callback callback,
191 lk_time_ns_t time_ns_abs)
192 {
193 uint64_t pit_ticks;
194 uint32_t pit_ticks_clamped;
195 uint16_t divisor;
196 lk_time_ns_t time_ns_rel = time_ns_abs - current_time_ns();
197
198 t_callback = callback;
199
200 pit_ticks = lk_time_ns_to_pit_ticks(time_ns_rel);
201 /* Clamp ticks to 1 - 0x10000. 0 in the 16 bit counter means 0x10000 */
202 pit_ticks_clamped = MAX(1, MIN(pit_ticks, UINT16_MAX + 1));
203 divisor = pit_ticks_clamped & UINT16_MAX;
204
205 LTRACEF("time_ns_abs %" PRIu64 " -> time_ns_rel %" PRIu64
206 " -> pit_ticks %" PRIu64 " -> pit_ticks_clamped %" PRIu32
207 " -> pit_ticks %" PRIu16 "\n",
208 time_ns_abs, time_ns_rel, pit_ticks, pit_ticks_clamped, divisor);
209
210 /*
211 * Program PIT in the software strobe configuration, to send one pulse
212 * after the count reach 0
213 */
214 outp(I8253_CONTROL_REG, 0x38);
215 outp(I8253_DATA_REG, divisor & 0xff);
216 outp(I8253_DATA_REG, divisor >> 8);
217
218 return NO_ERROR;
219 }
220
platform_stop_timer(void)221 void platform_stop_timer(void)
222 {
223 LTRACE;
224 /* Enable interrupt mode that will stop the decreasing counter of the PIT */
225 outp(I8253_CONTROL_REG, 0x30);
226 }
227