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