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>
20 #define LVT_ENTRY_TIMER(vector, mode) (LVT_DELIVERY_FIXED | mode | vector)
26 temp_intr_routine_rtc_tick(const isr_param* param);
29 temp_intr_routine_apic_timer(const isr_param* param);
32 timer_update(const isr_param* param);
34 static volatile struct lx_timer_context* timer_ctx = NULL;
36 // Don't optimize them! Took me an half hour to figure that out...
38 static volatile uint32_t rtc_counter = 0;
39 static volatile uint8_t apic_timer_done = 0;
41 #define APIC_CALIBRATION_CONST 0x100000
47 (struct lx_timer_context*)lxmalloc(sizeof(struct lx_timer_context));
49 assert_msg(timer_ctx, "Fail to initialize timer contex");
51 timer_ctx->active_timers =
52 (struct lx_timer*)lxmalloc(sizeof(struct lx_timer));
53 llist_init_head(timer_ctx->active_timers);
57 timer_init(uint32_t frequency)
61 cpu_disable_interrupt();
65 // Setup a one-shot timer, we will use this to measure the bus speed. So we
66 // can then calibrate apic timer to work at *nearly* accurate hz
67 apic_write_reg(APIC_TIMER_LVT,
68 LVT_ENTRY_TIMER(APIC_TIMER_IV, LVT_TIMER_ONESHOT));
71 apic_write_reg(APIC_TIMER_DCR, APIC_TIMER_DIV64);
74 Timer calibration process - measure the APIC timer base frequency
76 step 1: setup a temporary isr for RTC timer which trigger at each tick
78 step 2: setup a temporary isr for #APIC_TIMER_IV
79 step 3: setup the divider, APIC_TIMER_DCR
80 step 4: Startup RTC timer
81 step 5: Write a large value, v, to APIC_TIMER_ICR to start APIC timer (this must be
82 followed immediately after step 4)
83 step 6: issue a write to EOI and clean up.
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)
90 So the apic timer frequency F_apic in Hz can be calculate as
92 => F_apic = v / k * 1024
96 #ifdef __LUNAIXOS_DEBUG__
97 if (frequency < 1000) {
98 kprintf(KWARN "Frequency too low. Millisecond timer might be dodgy.");
102 timer_ctx->base_frequency = 0;
106 intr_subscribe(APIC_TIMER_IV, temp_intr_routine_apic_timer);
107 intr_subscribe(RTC_TIMER_IV, temp_intr_routine_rtc_tick);
109 rtc_enable_timer(); // start RTC timer
110 apic_write_reg(APIC_TIMER_ICR, APIC_CALIBRATION_CONST); // start APIC timer
112 // enable interrupt, just for our RTC start ticking!
113 cpu_enable_interrupt();
115 wait_until(apic_timer_done);
118 assert_msg(timer_ctx->base_frequency, "Fail to initialize timer (NOFREQ)");
120 kprintf(KINFO "Base frequency: %u Hz\n", timer_ctx->base_frequency);
122 timer_ctx->running_frequency = frequency;
123 timer_ctx->tphz = timer_ctx->base_frequency / frequency;
126 intr_unsubscribe(APIC_TIMER_IV, temp_intr_routine_apic_timer);
127 intr_unsubscribe(RTC_TIMER_IV, temp_intr_routine_rtc_tick);
129 apic_write_reg(APIC_TIMER_LVT,
130 LVT_ENTRY_TIMER(APIC_TIMER_IV, LVT_TIMER_PERIODIC));
131 intr_subscribe(APIC_TIMER_IV, timer_update);
133 apic_write_reg(APIC_TIMER_ICR, timer_ctx->tphz);
137 timer_run_second(uint32_t second, void (*callback)(void*), void* payload, uint8_t flags)
139 return timer_run(second * timer_ctx->running_frequency, callback, payload, flags);
143 timer_run_ms(uint32_t millisecond, void (*callback)(void*), void* payload, uint8_t flags)
145 return timer_run(timer_ctx->running_frequency / 1000 * millisecond, callback, payload, flags);
149 timer_run(ticks_t ticks, void (*callback)(void*), void* payload, uint8_t flags)
151 struct lx_timer* timer = (struct lx_timer*)lxmalloc(sizeof(struct lx_timer));
153 if (!timer) return 0;
155 timer->callback = callback;
156 timer->counter = ticks;
157 timer->deadline = ticks;
158 timer->payload = payload;
159 timer->flags = flags;
161 llist_append(timer_ctx->active_timers, &timer->link);
167 timer_update(const isr_param* param)
169 struct lx_timer *pos, *n;
170 struct lx_timer* timer_list_head = timer_ctx->active_timers;
172 llist_for_each(pos, n, &timer_list_head->link, link)
174 if (--pos->counter) {
178 pos->callback ? pos->callback(pos->payload) : 1;
180 if (pos->flags & TIMER_MODE_PERIODIC) {
181 pos->counter = pos->deadline;
183 llist_delete(&pos->link);
190 temp_intr_routine_rtc_tick(const isr_param* param)
194 // dummy read on register C so RTC can send anther interrupt
195 // This strange behaviour observed in virtual box & bochs
196 (void)rtc_read_reg(RTC_REG_C);
200 temp_intr_routine_apic_timer(const isr_param* param)
202 timer_ctx->base_frequency =
203 APIC_CALIBRATION_CONST / rtc_counter * RTC_TIMER_BASE_FREQUENCY;
209 struct lx_timer_context*