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