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