4 * @brief A simple timer implementation based on APIC with adjustable frequency
5 * and subscribable "timerlets"
9 * @copyright Copyright (c) 2022
12 #include <arch/x86/interrupts.h>
16 #include <lunaix/mm/kalloc.h>
17 #include <lunaix/sched.h>
18 #include <lunaix/spike.h>
19 #include <lunaix/syslog.h>
20 #include <lunaix/timer.h>
22 #define LVT_ENTRY_TIMER(vector, mode) (LVT_DELIVERY_FIXED | mode | vector)
27 temp_intr_routine_rtc_tick(const isr_param* param);
30 temp_intr_routine_apic_timer(const isr_param* param);
33 timer_update(const isr_param* param);
35 static volatile struct lx_timer_context* timer_ctx = NULL;
37 // Don't optimize them! Took me an half hour to figure that out...
39 static volatile uint32_t rtc_counter = 0;
40 static volatile uint8_t apic_timer_done = 0;
42 static volatile uint32_t sched_ticks = 0;
43 static volatile uint32_t sched_ticks_counter = 0;
45 #define APIC_CALIBRATION_CONST 0x100000
51 (struct lx_timer_context*)lxmalloc(sizeof(struct lx_timer_context));
53 assert_msg(timer_ctx, "Fail to initialize timer contex");
55 timer_ctx->active_timers =
56 (struct lx_timer*)lxmalloc(sizeof(struct lx_timer));
57 llist_init_head(timer_ctx->active_timers);
61 timer_init(uint32_t frequency)
65 cpu_disable_interrupt();
69 // Setup a one-shot timer, we will use this to measure the bus speed. So we
70 // can then calibrate apic timer to work at *nearly* accurate hz
71 apic_write_reg(APIC_TIMER_LVT,
72 LVT_ENTRY_TIMER(APIC_TIMER_IV, LVT_TIMER_ONESHOT));
75 apic_write_reg(APIC_TIMER_DCR, APIC_TIMER_DIV64);
78 Timer calibration process - measure the APIC timer base frequency
80 step 1: setup a temporary isr for RTC timer which trigger at each tick
82 step 2: setup a temporary isr for #APIC_TIMER_IV
83 step 3: setup the divider, APIC_TIMER_DCR
84 step 4: Startup RTC timer
85 step 5: Write a large value, v, to APIC_TIMER_ICR to start APIC timer
86 (this must be followed immediately after step 4) step 6: issue a write to
89 When the APIC ICR counting down to 0 #APIC_TIMER_IV triggered, save the
90 rtc timer's counter, k, and disable RTC timer immediately (although the
91 RTC interrupts should be blocked by local APIC as we are currently busy
92 on handling #APIC_TIMER_IV)
94 So the apic timer frequency F_apic in Hz can be calculate as
96 => F_apic = v / k * 1024
100 #ifdef __LUNAIXOS_DEBUG__
101 if (frequency < 1000) {
102 kprintf(KWARN "Frequency too low. Millisecond timer might be dodgy.");
106 timer_ctx->base_frequency = 0;
110 intr_subscribe(APIC_TIMER_IV, temp_intr_routine_apic_timer);
111 intr_subscribe(RTC_TIMER_IV, temp_intr_routine_rtc_tick);
113 rtc_enable_timer(); // start RTC timer
114 apic_write_reg(APIC_TIMER_ICR, APIC_CALIBRATION_CONST); // start APIC timer
116 // enable interrupt, just for our RTC start ticking!
117 cpu_enable_interrupt();
119 wait_until(apic_timer_done);
121 assert_msg(timer_ctx->base_frequency, "Fail to initialize timer (NOFREQ)");
123 kprintf(KINFO "Base frequency: %u Hz\n", timer_ctx->base_frequency);
125 timer_ctx->running_frequency = frequency;
126 timer_ctx->tphz = timer_ctx->base_frequency / frequency;
129 intr_unsubscribe(APIC_TIMER_IV, temp_intr_routine_apic_timer);
130 intr_unsubscribe(RTC_TIMER_IV, temp_intr_routine_rtc_tick);
132 apic_write_reg(APIC_TIMER_LVT,
133 LVT_ENTRY_TIMER(APIC_TIMER_IV, LVT_TIMER_PERIODIC));
134 intr_subscribe(APIC_TIMER_IV, timer_update);
136 apic_write_reg(APIC_TIMER_ICR, timer_ctx->tphz);
138 sched_ticks = timer_ctx->running_frequency / 1000 * SCHED_TIME_SLICE;
139 sched_ticks_counter = 0;
143 timer_run_second(uint32_t second,
144 void (*callback)(void*),
149 second * timer_ctx->running_frequency, callback, payload, flags);
153 timer_run_ms(uint32_t millisecond,
154 void (*callback)(void*),
158 return timer_run(timer_ctx->running_frequency / 1000 * millisecond,
165 timer_run(ticks_t ticks, void (*callback)(void*), void* payload, uint8_t flags)
167 struct lx_timer* timer =
168 (struct lx_timer*)lxmalloc(sizeof(struct lx_timer));
173 timer->callback = callback;
174 timer->counter = ticks;
175 timer->deadline = ticks;
176 timer->payload = payload;
177 timer->flags = flags;
179 llist_append(timer_ctx->active_timers, &timer->link);
185 timer_update(const isr_param* param)
187 struct lx_timer *pos, *n;
188 struct lx_timer* timer_list_head = timer_ctx->active_timers;
190 llist_for_each(pos, n, &timer_list_head->link, link)
192 if (--(pos->counter)) {
196 pos->callback ? pos->callback(pos->payload) : 1;
198 if ((pos->flags & TIMER_MODE_PERIODIC)) {
199 pos->counter = pos->deadline;
201 llist_delete(&pos->link);
206 sched_ticks_counter++;
208 if (sched_ticks_counter >= sched_ticks) {
209 sched_ticks_counter = 0;
217 sched_ticks_counter = sched_ticks;
221 temp_intr_routine_rtc_tick(const isr_param* param)
225 // dummy read on register C so RTC can send anther interrupt
226 // This strange behaviour observed in virtual box & bochs
227 (void)rtc_read_reg(RTC_REG_C);
231 temp_intr_routine_apic_timer(const isr_param* param)
233 timer_ctx->base_frequency =
234 APIC_CALIBRATION_CONST / rtc_counter * RTC_TIMER_BASE_FREQUENCY;
240 struct lx_timer_context*