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