Kernel address space isolation and make the kernel heap global to all processes.
[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 int
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 int
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 int
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 0;
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 1;
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             continue;
184         }
185
186         pos->callback ? pos->callback(pos->payload) : 1;
187
188         if (pos->flags & TIMER_MODE_PERIODIC) {
189             pos->counter = pos->deadline;
190         } else {
191             llist_delete(&pos->link);
192             lxfree(pos);
193         }
194     }
195     
196     sched_ticks_counter++;
197
198     if (sched_ticks_counter >= sched_ticks) {
199         sched_ticks_counter = 0;
200         schedule();
201     }
202 }
203
204 static void
205 temp_intr_routine_rtc_tick(const isr_param* param)
206 {
207     rtc_counter++;
208
209     // dummy read on register C so RTC can send anther interrupt
210     //  This strange behaviour observed in virtual box & bochs
211     (void)rtc_read_reg(RTC_REG_C);
212 }
213
214 static void
215 temp_intr_routine_apic_timer(const isr_param* param)
216 {
217     timer_ctx->base_frequency =
218       APIC_CALIBRATION_CONST / rtc_counter * RTC_TIMER_BASE_FREQUENCY;
219     apic_timer_done = 1;
220
221     rtc_disable_timer();
222 }
223
224 struct lx_timer_context* 
225 timer_context() {
226     return timer_ctx;
227 }