4 * @brief A simple timer implementation based on APIC with adjustable frequency and subscribable "timerlets"
8 * @copyright Copyright (c) 2022
11 #include <arch/x86/interrupts.h>
15 #include <lunaix/mm/kalloc.h>
16 #include <lunaix/spike.h>
17 #include <lunaix/syslog.h>
18 #include <lunaix/timer.h>
19 #include <lunaix/sched.h>
21 #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);
62 timer_init(uint32_t frequency)
66 cpu_disable_interrupt();
70 // Setup a one-shot timer, we will use this to measure the bus speed. So we
71 // can then calibrate apic timer to work at *nearly* accurate hz
72 apic_write_reg(APIC_TIMER_LVT,
73 LVT_ENTRY_TIMER(APIC_TIMER_IV, LVT_TIMER_ONESHOT));
76 apic_write_reg(APIC_TIMER_DCR, APIC_TIMER_DIV64);
79 Timer calibration process - measure the APIC timer base frequency
81 step 1: setup a temporary isr for RTC timer which trigger at each tick
83 step 2: setup a temporary isr for #APIC_TIMER_IV
84 step 3: setup the divider, APIC_TIMER_DCR
85 step 4: Startup RTC timer
86 step 5: Write a large value, v, to APIC_TIMER_ICR to start APIC timer (this must be
87 followed immediately after step 4)
88 step 6: issue a write to EOI and clean up.
90 When the APIC ICR counting down to 0 #APIC_TIMER_IV triggered, save the
91 rtc timer's counter, k, and disable RTC timer immediately (although the
92 RTC interrupts should be blocked by local APIC as we are currently busy
93 on handling #APIC_TIMER_IV)
95 So the apic timer frequency F_apic in Hz can be calculate as
97 => F_apic = v / k * 1024
101 #ifdef __LUNAIXOS_DEBUG__
102 if (frequency < 1000) {
103 kprintf(KWARN "Frequency too low. Millisecond timer might be dodgy.");
107 timer_ctx->base_frequency = 0;
111 intr_subscribe(APIC_TIMER_IV, temp_intr_routine_apic_timer);
112 intr_subscribe(RTC_TIMER_IV, temp_intr_routine_rtc_tick);
114 rtc_enable_timer(); // start RTC timer
115 apic_write_reg(APIC_TIMER_ICR, APIC_CALIBRATION_CONST); // start APIC timer
117 // enable interrupt, just for our RTC start ticking!
118 cpu_enable_interrupt();
120 wait_until(apic_timer_done);
123 assert_msg(timer_ctx->base_frequency, "Fail to initialize timer (NOFREQ)");
125 kprintf(KINFO "Base frequency: %u Hz\n", timer_ctx->base_frequency);
127 timer_ctx->running_frequency = frequency;
128 timer_ctx->tphz = timer_ctx->base_frequency / frequency;
131 intr_unsubscribe(APIC_TIMER_IV, temp_intr_routine_apic_timer);
132 intr_unsubscribe(RTC_TIMER_IV, temp_intr_routine_rtc_tick);
134 apic_write_reg(APIC_TIMER_LVT,
135 LVT_ENTRY_TIMER(APIC_TIMER_IV, LVT_TIMER_PERIODIC));
136 intr_subscribe(APIC_TIMER_IV, timer_update);
138 apic_write_reg(APIC_TIMER_ICR, timer_ctx->tphz);
140 sched_ticks = timer_ctx->running_frequency / 1000 * SCHED_TIME_SLICE;
141 sched_ticks_counter = 0;
145 timer_run_second(uint32_t second, void (*callback)(void*), void* payload, uint8_t flags)
147 return timer_run(second * timer_ctx->running_frequency, callback, payload, flags);
151 timer_run_ms(uint32_t millisecond, void (*callback)(void*), void* payload, uint8_t flags)
153 return timer_run(timer_ctx->running_frequency / 1000 * millisecond, callback, payload, flags);
157 timer_run(ticks_t ticks, void (*callback)(void*), void* payload, uint8_t flags)
159 struct lx_timer* timer = (struct lx_timer*)lxmalloc(sizeof(struct lx_timer));
161 if (!timer) return 0;
163 timer->callback = callback;
164 timer->counter = ticks;
165 timer->deadline = ticks;
166 timer->payload = payload;
167 timer->flags = flags;
169 llist_append(timer_ctx->active_timers, &timer->link);
175 timer_update(const isr_param* param)
177 struct lx_timer *pos, *n;
178 struct lx_timer* timer_list_head = timer_ctx->active_timers;
180 llist_for_each(pos, n, &timer_list_head->link, link)
182 if (--pos->counter) {
186 pos->callback ? pos->callback(pos->payload) : 1;
188 if (pos->flags & TIMER_MODE_PERIODIC) {
189 pos->counter = pos->deadline;
191 llist_delete(&pos->link);
196 sched_ticks_counter++;
198 if (sched_ticks_counter >= sched_ticks) {
199 sched_ticks_counter = 0;
205 temp_intr_routine_rtc_tick(const isr_param* param)
209 // dummy read on register C so RTC can send anther interrupt
210 // This strange behaviour observed in virtual box & bochs
211 (void)rtc_read_reg(RTC_REG_C);
215 temp_intr_routine_apic_timer(const isr_param* param)
217 timer_ctx->base_frequency =
218 APIC_CALIBRATION_CONST / rtc_counter * RTC_TIMER_BASE_FREQUENCY;
224 struct lx_timer_context*