Timer re-worked!
[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) step 2: setup a temporary isr for #APIC_TIMER_IV step 3: setup
80        the divider, APIC_TIMER_DCR step 4: Startup RTC timer step 5: Write a
81        large value, v, to APIC_TIMER_ICR to start APIC timer (this must be
82        followed immediately after step 4) step 6: issue a write to EOI and clean
83        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     timer_ctx->base_frequency = 0;
97     rtc_counter = 0;
98     apic_timer_done = 0;
99
100     intr_subscribe(APIC_TIMER_IV, temp_intr_routine_apic_timer);
101     intr_subscribe(RTC_TIMER_IV, temp_intr_routine_rtc_tick);
102
103     rtc_enable_timer();                                     // start RTC timer
104     apic_write_reg(APIC_TIMER_ICR, APIC_CALIBRATION_CONST); // start APIC timer
105
106     // enable interrupt, just for our RTC start ticking!
107     cpu_enable_interrupt();
108
109     wait_until(apic_timer_done);
110
111     // cpu_disable_interrupt();
112
113     assert_msg(timer_ctx->base_frequency, "Fail to initialize timer (NOFREQ)");
114
115     kprintf(KINFO "Base frequency: %u Hz\n", timer_ctx->base_frequency);
116
117     timer_ctx->running_frequency = frequency;
118     timer_ctx->tick_interval = timer_ctx->base_frequency / frequency;
119
120     // cleanup
121     intr_unsubscribe(APIC_TIMER_IV, temp_intr_routine_apic_timer);
122     intr_unsubscribe(RTC_TIMER_IV, temp_intr_routine_rtc_tick);
123
124     apic_write_reg(APIC_TIMER_LVT,
125                    LVT_ENTRY_TIMER(APIC_TIMER_IV, LVT_TIMER_PERIODIC));
126     intr_subscribe(APIC_TIMER_IV, timer_update);
127
128     apic_write_reg(APIC_TIMER_ICR, timer_ctx->tick_interval);
129 }
130
131 int
132 timer_run_second(uint32_t second, void (*callback)(void*), void* payload, uint8_t flags)
133 {
134     return timer_run(second * timer_ctx->running_frequency, callback, payload, flags);
135 }
136
137 int
138 timer_run(uint32_t ticks, void (*callback)(void*), void* payload, uint8_t flags)
139 {
140     struct lx_timer* timer = (struct lx_timer*)lxmalloc(sizeof(struct lx_timer));
141
142     if (!timer) return 0;
143
144     timer->callback = callback;
145     timer->counter = ticks;
146     timer->deadline = ticks;
147     timer->payload = payload;
148     timer->flags = flags;
149
150     llist_append(timer_ctx->active_timers, timer);
151
152     return 1;
153 }
154
155 static void
156 timer_update(const isr_param* param)
157 {
158     struct lx_timer *pos, *n;
159     struct lx_timer* timer_list_head = timer_ctx->active_timers;
160
161     llist_for_each(pos, n, &timer_list_head->link, link)
162     {
163         if (--pos->counter) {
164             continue;
165         }
166
167         pos->callback ? pos->callback(pos->payload) : 1;
168
169         if (pos->flags & TIMER_MODE_PERIODIC) {
170             pos->counter = pos->deadline;
171         } else {
172             llist_delete(pos);
173             lxfree(pos);
174         }
175     }
176 }
177
178 static void
179 temp_intr_routine_rtc_tick(const isr_param* param)
180 {
181     rtc_counter++;
182
183     // dummy read on register C so RTC can send anther interrupt
184     //  This strange behaviour observed in virtual box & bochs
185     (void)rtc_read_reg(RTC_REG_C);
186 }
187
188 static void
189 temp_intr_routine_apic_timer(const isr_param* param)
190 {
191     timer_ctx->base_frequency =
192       APIC_CALIBRATION_CONST / rtc_counter * RTC_TIMER_BASE_FREQUENCY;
193     apic_timer_done = 1;
194
195     rtc_disable_timer();
196 }