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/time.h>
19 #include <lunaix/timer.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;
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 #define APIC_CALIBRATION_CONST 0x100000
48 (struct lx_timer_context*)lxmalloc(sizeof(struct lx_timer_context));
50 assert_msg(timer_ctx, "Fail to initialize timer contex");
52 timer_ctx->active_timers =
53 (struct lx_timer*)lxmalloc(sizeof(struct lx_timer));
54 llist_init_head(timer_ctx->active_timers);
58 timer_init(uint32_t frequency)
62 cpu_disable_interrupt();
66 // Setup a one-shot timer, we will use this to measure the bus speed. So we
68 // then calibrate apic timer to work at *nearly* accurate hz
69 apic_write_reg(APIC_TIMER_LVT,
70 LVT_ENTRY_TIMER(APIC_TIMER_IV, LVT_TIMER_ONESHOT));
73 apic_write_reg(APIC_TIMER_DCR, APIC_TIMER_DIV64);
76 Timer calibration process - measure the APIC timer base frequency
78 step 1: setup a temporary isr for RTC timer which trigger at each tick
80 step 2: setup a temporary isr for #APIC_TIMER_IV
81 step 3: setup the divider, APIC_TIMER_DCR
82 step 4: Startup RTC timer
83 step 5: Write a large value, v, to APIC_TIMER_ICR to start APIC timer (this must be
84 followed immediately after step 4)
85 step 6: issue a write to EOI and clean up.
87 When the APIC ICR counting down to 0 #APIC_TIMER_IV triggered, save the
88 rtc timer's counter, k, and disable RTC timer immediately (although the
89 RTC interrupts should be blocked by local APIC as we are currently busy
90 on handling #APIC_TIMER_IV)
92 So the apic timer frequency F_apic in Hz can be calculate as
94 => F_apic = v / k * 1024
98 timer_ctx->base_frequency = 0;
102 intr_subscribe(APIC_TIMER_IV, temp_intr_routine_apic_timer);
103 intr_subscribe(RTC_TIMER_IV, temp_intr_routine_rtc_tick);
105 rtc_enable_timer(); // start RTC timer
106 apic_write_reg(APIC_TIMER_ICR, APIC_CALIBRATION_CONST); // start APIC timer
108 // enable interrupt, just for our RTC start ticking!
109 cpu_enable_interrupt();
111 wait_until(apic_timer_done);
113 // cpu_disable_interrupt();
115 assert_msg(timer_ctx->base_frequency, "Fail to initialize timer (NOFREQ)");
117 kprintf(KINFO "Base frequency: %u Hz\n", timer_ctx->base_frequency);
119 timer_ctx->running_frequency = frequency;
120 timer_ctx->tick_interval = timer_ctx->base_frequency / frequency;
123 intr_unsubscribe(APIC_TIMER_IV, temp_intr_routine_apic_timer);
124 intr_unsubscribe(RTC_TIMER_IV, temp_intr_routine_rtc_tick);
126 apic_write_reg(APIC_TIMER_LVT,
127 LVT_ENTRY_TIMER(APIC_TIMER_IV, LVT_TIMER_PERIODIC));
128 intr_subscribe(APIC_TIMER_IV, timer_update);
130 apic_write_reg(APIC_TIMER_ICR, timer_ctx->tick_interval);
134 timer_run_second(uint32_t second, void (*callback)(void*), void* payload, uint8_t flags)
136 return timer_run(second * timer_ctx->running_frequency, callback, payload, flags);
140 timer_run(uint32_t ticks, void (*callback)(void*), void* payload, uint8_t flags)
142 struct lx_timer* timer = (struct lx_timer*)lxmalloc(sizeof(struct lx_timer));
144 if (!timer) return 0;
146 timer->callback = callback;
147 timer->counter = ticks;
148 timer->deadline = ticks;
149 timer->payload = payload;
150 timer->flags = flags;
152 llist_append(timer_ctx->active_timers, &timer->link);
158 timer_update(const isr_param* param)
160 struct lx_timer *pos, *n;
161 struct lx_timer* timer_list_head = timer_ctx->active_timers;
163 llist_for_each(pos, n, &timer_list_head->link, link)
165 if (--pos->counter) {
169 pos->callback ? pos->callback(pos->payload) : 1;
171 if (pos->flags & TIMER_MODE_PERIODIC) {
172 pos->counter = pos->deadline;
174 llist_delete(&pos->link);
181 temp_intr_routine_rtc_tick(const isr_param* param)
185 // dummy read on register C so RTC can send anther interrupt
186 // This strange behaviour observed in virtual box & bochs
187 (void)rtc_read_reg(RTC_REG_C);
191 temp_intr_routine_apic_timer(const isr_param* param)
193 timer_ctx->base_frequency =
194 APIC_CALIBRATION_CONST / rtc_counter * RTC_TIMER_BASE_FREQUENCY;