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