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