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