fix: incorrect settings of msi registers.
[lunaix-os.git] / lunaix-os / kernel / time / timer.c
1 /**
2  * @file timer.c
3  * @author Lunaixsky
4  * @brief A simple timer implementation based on APIC with adjustable frequency
5  * and subscribable "timerlets"
6  * @version 0.1
7  * @date 2022-03-12
8  *
9  * @copyright Copyright (c) 2022
10  *
11  */
12 #include <arch/x86/interrupts.h>
13 #include <hal/apic.h>
14 #include <hal/rtc.h>
15
16 #include <lunaix/mm/kalloc.h>
17 #include <lunaix/sched.h>
18 #include <lunaix/spike.h>
19 #include <lunaix/syslog.h>
20 #include <lunaix/timer.h>
21
22 #define LVT_ENTRY_TIMER(vector, mode) (LVT_DELIVERY_FIXED | mode | vector)
23
24 LOG_MODULE("TIMER");
25
26 static void
27 temp_intr_routine_rtc_tick(const isr_param* param);
28
29 static void
30 temp_intr_routine_apic_timer(const isr_param* param);
31
32 static void
33 timer_update(const isr_param* param);
34
35 static volatile struct lx_timer_context* timer_ctx = NULL;
36
37 // Don't optimize them! Took me an half hour to figure that out...
38
39 static volatile uint32_t rtc_counter = 0;
40 static volatile uint8_t apic_timer_done = 0;
41
42 static volatile uint32_t sched_ticks = 0;
43 static volatile uint32_t sched_ticks_counter = 0;
44
45 #define APIC_CALIBRATION_CONST 0x100000
46
47 void
48 timer_init_context()
49 {
50     timer_ctx =
51       (struct lx_timer_context*)lxmalloc(sizeof(struct lx_timer_context));
52
53     assert_msg(timer_ctx, "Fail to initialize timer contex");
54
55     timer_ctx->active_timers =
56       (struct lx_timer*)lxmalloc(sizeof(struct lx_timer));
57     llist_init_head(timer_ctx->active_timers);
58 }
59
60 void
61 timer_init(uint32_t frequency)
62 {
63     timer_init_context();
64
65     cpu_disable_interrupt();
66
67     // Setup APIC timer
68
69     // Setup a one-shot timer, we will use this to measure the bus speed. So we
70     // can then calibrate apic timer to work at *nearly* accurate hz
71     apic_write_reg(APIC_TIMER_LVT,
72                    LVT_ENTRY_TIMER(APIC_TIMER_IV, LVT_TIMER_ONESHOT));
73
74     // Set divider to 64
75     apic_write_reg(APIC_TIMER_DCR, APIC_TIMER_DIV64);
76
77     /*
78         Timer calibration process - measure the APIC timer base frequency
79
80          step 1: setup a temporary isr for RTC timer which trigger at each tick
81                  (1024Hz)
82          step 2: setup a temporary isr for #APIC_TIMER_IV
83          step 3: setup the divider, APIC_TIMER_DCR
84          step 4: Startup RTC timer
85          step 5: Write a large value, v, to APIC_TIMER_ICR to start APIC timer
86        (this must be followed immediately after step 4) step 6: issue a write to
87        EOI and clean up.
88
89         When the APIC ICR counting down to 0 #APIC_TIMER_IV triggered, save the
90        rtc timer's counter, k, and disable RTC timer immediately (although the
91        RTC interrupts should be blocked by local APIC as we are currently busy
92        on handling #APIC_TIMER_IV)
93
94         So the apic timer frequency F_apic in Hz can be calculate as
95             v / F_apic = k / 1024
96             =>  F_apic = v / k * 1024
97
98     */
99
100 #ifdef __LUNAIXOS_DEBUG__
101     if (frequency < 1000) {
102         kprintf(KWARN "Frequency too low. Millisecond timer might be dodgy.");
103     }
104 #endif
105
106     timer_ctx->base_frequency = 0;
107     rtc_counter = 0;
108     apic_timer_done = 0;
109
110     intr_subscribe(APIC_TIMER_IV, temp_intr_routine_apic_timer);
111     intr_subscribe(RTC_TIMER_IV, temp_intr_routine_rtc_tick);
112
113     rtc_enable_timer();                                     // start RTC timer
114     apic_write_reg(APIC_TIMER_ICR, APIC_CALIBRATION_CONST); // start APIC timer
115
116     // enable interrupt, just for our RTC start ticking!
117     cpu_enable_interrupt();
118
119     wait_until(apic_timer_done);
120
121     assert_msg(timer_ctx->base_frequency, "Fail to initialize timer (NOFREQ)");
122
123     kprintf(KINFO "Base frequency: %u Hz\n", timer_ctx->base_frequency);
124
125     timer_ctx->running_frequency = frequency;
126     timer_ctx->tphz = timer_ctx->base_frequency / frequency;
127
128     // cleanup
129     intr_unsubscribe(APIC_TIMER_IV, temp_intr_routine_apic_timer);
130     intr_unsubscribe(RTC_TIMER_IV, temp_intr_routine_rtc_tick);
131
132     apic_write_reg(APIC_TIMER_LVT,
133                    LVT_ENTRY_TIMER(APIC_TIMER_IV, LVT_TIMER_PERIODIC));
134     intr_subscribe(APIC_TIMER_IV, timer_update);
135
136     apic_write_reg(APIC_TIMER_ICR, timer_ctx->tphz);
137
138     sched_ticks = timer_ctx->running_frequency / 1000 * SCHED_TIME_SLICE;
139     sched_ticks_counter = 0;
140 }
141
142 struct lx_timer*
143 timer_run_second(uint32_t second,
144                  void (*callback)(void*),
145                  void* payload,
146                  uint8_t flags)
147 {
148     return timer_run(
149       second * timer_ctx->running_frequency, callback, payload, flags);
150 }
151
152 struct lx_timer*
153 timer_run_ms(uint32_t millisecond,
154              void (*callback)(void*),
155              void* payload,
156              uint8_t flags)
157 {
158     return timer_run(timer_ctx->running_frequency / 1000 * millisecond,
159                      callback,
160                      payload,
161                      flags);
162 }
163
164 struct lx_timer*
165 timer_run(ticks_t ticks, void (*callback)(void*), void* payload, uint8_t flags)
166 {
167     struct lx_timer* timer =
168       (struct lx_timer*)lxmalloc(sizeof(struct lx_timer));
169
170     if (!timer)
171         return NULL;
172
173     timer->callback = callback;
174     timer->counter = ticks;
175     timer->deadline = ticks;
176     timer->payload = payload;
177     timer->flags = flags;
178
179     llist_append(timer_ctx->active_timers, &timer->link);
180
181     return timer;
182 }
183
184 static void
185 timer_update(const isr_param* param)
186 {
187     struct lx_timer *pos, *n;
188     struct lx_timer* timer_list_head = timer_ctx->active_timers;
189
190     llist_for_each(pos, n, &timer_list_head->link, link)
191     {
192         if (--(pos->counter)) {
193             continue;
194         }
195
196         pos->callback ? pos->callback(pos->payload) : 1;
197
198         if ((pos->flags & TIMER_MODE_PERIODIC)) {
199             pos->counter = pos->deadline;
200         } else {
201             llist_delete(&pos->link);
202             lxfree(pos);
203         }
204     }
205
206     sched_ticks_counter++;
207
208     if (sched_ticks_counter >= sched_ticks) {
209         sched_ticks_counter = 0;
210         schedule();
211     }
212 }
213
214 void
215 sched_yield()
216 {
217     sched_ticks_counter = sched_ticks;
218 }
219
220 static void
221 temp_intr_routine_rtc_tick(const isr_param* param)
222 {
223     rtc_counter++;
224
225     // dummy read on register C so RTC can send anther interrupt
226     //  This strange behaviour observed in virtual box & bochs
227     (void)rtc_read_reg(RTC_REG_C);
228 }
229
230 static void
231 temp_intr_routine_apic_timer(const isr_param* param)
232 {
233     timer_ctx->base_frequency =
234       APIC_CALIBRATION_CONST / rtc_counter * RTC_TIMER_BASE_FREQUENCY;
235     apic_timer_done = 1;
236
237     rtc_disable_timer();
238 }
239
240 struct lx_timer_context*
241 timer_context()
242 {
243     return timer_ctx;
244 }