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