1 /*
2 * Copyright (c) 2009 Corey Tabaka
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/err.h>
10 #include <lk/reg.h>
11 #include <lk/debug.h>
12 #include <kernel/thread.h>
13 #include <kernel/spinlock.h>
14 #include <platform.h>
15 #include <platform/interrupts.h>
16 #include <platform/console.h>
17 #include <platform/timer.h>
18 #include <platform/pc.h>
19 #include "platform_p.h"
20 #include <arch/x86.h>
21
22 static platform_timer_callback t_callback;
23 static void *callback_arg;
24 static spin_lock_t lock;
25
26 static uint64_t next_trigger_time;
27 static uint64_t next_trigger_delta;
28 static uint64_t ticks_per_ms;
29
30 static uint64_t timer_delta_time;
31 static volatile uint64_t timer_current_time;
32
33 static uint16_t divisor;
34
35 #define INTERNAL_FREQ 1193182ULL
36 #define INTERNAL_FREQ_3X 3579546ULL
37
38 /* Maximum amount of time that can be program on the timer to schedule the next
39 * interrupt, in milliseconds */
40 #define MAX_TIMER_INTERVAL 55
41
42
43
platform_set_periodic_timer(platform_timer_callback callback,void * arg,lk_time_t interval)44 status_t platform_set_periodic_timer(platform_timer_callback callback, void *arg, lk_time_t interval) {
45 t_callback = callback;
46 callback_arg = arg;
47
48 next_trigger_delta = (uint64_t) interval << 32;
49 next_trigger_time = timer_current_time + next_trigger_delta;
50
51 return NO_ERROR;
52 }
53
current_time(void)54 lk_time_t current_time(void) {
55 lk_time_t time;
56
57 // XXX slight race
58 time = (lk_time_t) (timer_current_time >> 32);
59
60 return time;
61 }
62
current_time_hires(void)63 lk_bigtime_t current_time_hires(void) {
64 lk_bigtime_t time;
65
66 // XXX slight race
67 time = (lk_bigtime_t) ((timer_current_time >> 22) * 1000) >> 10;
68
69 return time;
70 }
os_timer_tick(void * arg)71 static enum handler_return os_timer_tick(void *arg) {
72 uint64_t delta;
73
74 timer_current_time += timer_delta_time;
75
76 lk_time_t time = current_time();
77 //lk_bigtime_t btime = current_time_hires();
78 //printf_xy(71, 0, WHITE, "%08u", (uint32_t) time);
79 //printf_xy(63, 1, WHITE, "%016llu", (uint64_t) btime);
80
81 if (t_callback && timer_current_time >= next_trigger_time) {
82 delta = timer_current_time - next_trigger_time;
83 next_trigger_time = timer_current_time + next_trigger_delta - delta;
84
85 return t_callback(callback_arg, time);
86 } else {
87 return INT_NO_RESCHEDULE;
88 }
89 }
90
set_pit_frequency(uint32_t frequency)91 static void set_pit_frequency(uint32_t frequency) {
92 uint32_t count, remainder;
93
94 /* figure out the correct divisor for the desired frequency */
95 if (frequency <= 18) {
96 count = 0xffff;
97 } else if (frequency >= INTERNAL_FREQ) {
98 count = 1;
99 } else {
100 count = INTERNAL_FREQ_3X / frequency;
101 remainder = INTERNAL_FREQ_3X % frequency;
102
103 if (remainder >= INTERNAL_FREQ_3X / 2) {
104 count += 1;
105 }
106
107 count /= 3;
108 remainder = count % 3;
109
110 if (remainder >= 1) {
111 count += 1;
112 }
113 }
114
115 divisor = count & 0xffff;
116
117 /*
118 * funky math that i don't feel like explaining. essentially 32.32 fixed
119 * point representation of the configured timer delta.
120 */
121 timer_delta_time = (3685982306ULL * count) >> 10;
122
123 //dprintf(DEBUG, "set_pit_frequency: dt=%016llx\n", timer_delta_time);
124 //dprintf(DEBUG, "set_pit_frequency: divisor=%04x\n", divisor);
125
126 /*
127 * setup the Programmable Interval Timer
128 * timer 0, mode 2, binary counter, LSB followed by MSB
129 */
130 outp(I8253_CONTROL_REG, 0x34);
131 outp(I8253_DATA_REG, divisor & 0xff); // LSB
132 outp(I8253_DATA_REG, divisor >> 8); // MSB
133 }
134
platform_init_timer(void)135 void platform_init_timer(void) {
136
137 timer_current_time = 0;
138 ticks_per_ms = INTERNAL_FREQ/1000;
139 set_pit_frequency(1000); // ~1ms granularity
140 register_int_handler(INT_PIT, &os_timer_tick, NULL);
141 unmask_interrupt(INT_PIT);
142 }
143
platform_halt_timers(void)144 static void platform_halt_timers(void) {
145 mask_interrupt(INT_PIT);
146 }
147
platform_set_oneshot_timer(platform_timer_callback callback,void * arg,lk_time_t interval)148 status_t platform_set_oneshot_timer(platform_timer_callback callback,
149 void *arg, lk_time_t interval) {
150
151 uint32_t count;
152
153 spin_lock_saved_state_t state;
154 spin_lock_irqsave(&lock, state);
155
156 t_callback = callback;
157 callback_arg = arg;
158
159
160 if (interval > MAX_TIMER_INTERVAL)
161 interval = MAX_TIMER_INTERVAL;
162 if (interval < 1) interval = 1;
163
164 count = ticks_per_ms * interval;
165
166 divisor = count & 0xffff;
167 timer_delta_time = (3685982306ULL * count) >> 10;
168 /* Program PIT in the software strobe configuration, to send one pulse
169 * after the count reach 0 */
170 outp(I8253_CONTROL_REG, 0x38);
171 outp(I8253_DATA_REG, divisor & 0xff); // LSB
172 outp(I8253_DATA_REG, divisor >> 8); // MSB
173
174
175 unmask_interrupt(INT_PIT);
176 spin_unlock_irqrestore(&lock, state);
177
178 return NO_ERROR;
179 }
180
platform_stop_timer(void)181 void platform_stop_timer(void) {
182 /* Enable interrupt mode that will stop the decreasing counter of the PIT */
183 outp(I8253_CONTROL_REG, 0x30);
184 return;
185 }
186