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/cake.h>
17 #include <lunaix/mm/valloc.h>
18 #include <lunaix/sched.h>
19 #include <lunaix/spike.h>
20 #include <lunaix/syslog.h>
21 #include <lunaix/timer.h>
23 #define LVT_ENTRY_TIMER(vector, mode) (LVT_DELIVERY_FIXED | mode | vector)
28 temp_intr_routine_rtc_tick(const isr_param* param);
31 temp_intr_routine_apic_timer(const isr_param* param);
34 timer_update(const isr_param* param);
36 static volatile struct lx_timer_context* timer_ctx = NULL;
38 // Don't optimize them! Took me an half hour to figure that out...
40 static volatile uint32_t rtc_counter = 0;
41 static volatile uint8_t apic_timer_done = 0;
43 static volatile uint32_t sched_ticks = 0;
44 static volatile uint32_t sched_ticks_counter = 0;
46 static struct cake_pile* timer_pile;
48 #define APIC_CALIBRATION_CONST 0x100000
53 timer_pile = cake_new_pile("timer", sizeof(struct lx_timer), 1, 0);
55 (struct lx_timer_context*)valloc(sizeof(struct lx_timer_context));
57 assert_msg(timer_ctx, "Fail to initialize timer contex");
59 timer_ctx->active_timers = (struct lx_timer*)cake_grab(timer_pile);
60 llist_init_head(timer_ctx->active_timers);
64 timer_init(uint32_t frequency)
68 cpu_disable_interrupt();
72 // Setup a one-shot timer, we will use this to measure the bus speed. So we
73 // can then calibrate apic timer to work at *nearly* accurate hz
74 apic_write_reg(APIC_TIMER_LVT,
75 LVT_ENTRY_TIMER(APIC_TIMER_IV, LVT_TIMER_ONESHOT));
78 apic_write_reg(APIC_TIMER_DCR, APIC_TIMER_DIV64);
81 Timer calibration process - measure the APIC timer base frequency
83 step 1: setup a temporary isr for RTC timer which trigger at each tick
85 step 2: setup a temporary isr for #APIC_TIMER_IV
86 step 3: setup the divider, APIC_TIMER_DCR
87 step 4: Startup RTC timer
88 step 5: Write a large value, v, to APIC_TIMER_ICR to start APIC timer
89 (this must be followed immediately after step 4) step 6: issue a write to
92 When the APIC ICR counting down to 0 #APIC_TIMER_IV triggered, save the
93 rtc timer's counter, k, and disable RTC timer immediately (although the
94 RTC interrupts should be blocked by local APIC as we are currently busy
95 on handling #APIC_TIMER_IV)
97 So the apic timer frequency F_apic in Hz can be calculate as
99 => F_apic = v / k * 1024
103 #ifdef __LUNAIXOS_DEBUG__
104 if (frequency < 1000) {
105 kprintf(KWARN "Frequency too low. Millisecond timer might be dodgy.");
109 timer_ctx->base_frequency = 0;
113 intr_subscribe(APIC_TIMER_IV, temp_intr_routine_apic_timer);
114 intr_subscribe(RTC_TIMER_IV, temp_intr_routine_rtc_tick);
116 rtc_enable_timer(); // start RTC timer
117 apic_write_reg(APIC_TIMER_ICR, APIC_CALIBRATION_CONST); // start APIC timer
119 // enable interrupt, just for our RTC start ticking!
120 cpu_enable_interrupt();
122 wait_until(apic_timer_done);
124 assert_msg(timer_ctx->base_frequency, "Fail to initialize timer (NOFREQ)");
126 kprintf(KINFO "Base frequency: %u Hz\n", timer_ctx->base_frequency);
128 timer_ctx->running_frequency = frequency;
129 timer_ctx->tphz = timer_ctx->base_frequency / frequency;
132 intr_unsubscribe(APIC_TIMER_IV, temp_intr_routine_apic_timer);
133 intr_unsubscribe(RTC_TIMER_IV, temp_intr_routine_rtc_tick);
135 apic_write_reg(APIC_TIMER_LVT,
136 LVT_ENTRY_TIMER(APIC_TIMER_IV, LVT_TIMER_PERIODIC));
137 intr_subscribe(APIC_TIMER_IV, timer_update);
139 apic_write_reg(APIC_TIMER_ICR, timer_ctx->tphz);
141 sched_ticks = timer_ctx->running_frequency / 1000 * SCHED_TIME_SLICE;
142 sched_ticks_counter = 0;
146 timer_run_second(uint32_t second,
147 void (*callback)(void*),
152 second * timer_ctx->running_frequency, callback, payload, flags);
156 timer_run_ms(uint32_t millisecond,
157 void (*callback)(void*),
161 return timer_run(timer_ctx->running_frequency / 1000 * millisecond,
168 timer_run(ticks_t ticks, void (*callback)(void*), void* payload, uint8_t flags)
170 struct lx_timer* timer = (struct lx_timer*)cake_grab(timer_pile);
175 timer->callback = callback;
176 timer->counter = ticks;
177 timer->deadline = ticks;
178 timer->payload = payload;
179 timer->flags = flags;
181 llist_append(timer_ctx->active_timers, &timer->link);
187 timer_update(const isr_param* param)
189 struct lx_timer *pos, *n;
190 struct lx_timer* timer_list_head = timer_ctx->active_timers;
192 llist_for_each(pos, n, &timer_list_head->link, link)
194 if (--(pos->counter)) {
198 pos->callback ? pos->callback(pos->payload) : 1;
200 if ((pos->flags & TIMER_MODE_PERIODIC)) {
201 pos->counter = pos->deadline;
203 llist_delete(&pos->link);
204 cake_release(timer_pile, pos);
208 sched_ticks_counter++;
210 if (sched_ticks_counter >= sched_ticks) {
211 sched_ticks_counter = 0;
219 sched_ticks_counter = sched_ticks;
223 temp_intr_routine_rtc_tick(const isr_param* param)
227 // dummy read on register C so RTC can send anther interrupt
228 // This strange behaviour observed in virtual box & bochs
229 (void)rtc_read_reg(RTC_REG_C);
233 temp_intr_routine_apic_timer(const isr_param* param)
235 timer_ctx->base_frequency =
236 APIC_CALIBRATION_CONST / rtc_counter * RTC_TIMER_BASE_FREQUENCY;
242 struct lx_timer_context*