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
67 // then calibrate apic timer to work at *nearly* accurate hz
68 apic_write_reg(APIC_TIMER_LVT,
69 LVT_ENTRY_TIMER(APIC_TIMER_IV, LVT_TIMER_ONESHOT));
72 apic_write_reg(APIC_TIMER_DCR, APIC_TIMER_DIV64);
75 Timer calibration process - measure the APIC timer base frequency
77 step 1: setup a temporary isr for RTC timer which trigger at each tick
79 step 2: setup a temporary isr for #APIC_TIMER_IV
80 step 3: setup the divider, APIC_TIMER_DCR
81 step 4: Startup RTC timer
82 step 5: Write a large value, v, to APIC_TIMER_ICR to start APIC timer (this must be
83 followed immediately after step 4)
84 step 6: issue a write to EOI and clean up.
86 When the APIC ICR counting down to 0 #APIC_TIMER_IV triggered, save the
87 rtc timer's counter, k, and disable RTC timer immediately (although the
88 RTC interrupts should be blocked by local APIC as we are currently busy
89 on handling #APIC_TIMER_IV)
91 So the apic timer frequency F_apic in Hz can be calculate as
93 => F_apic = v / k * 1024
97 timer_ctx->base_frequency = 0;
101 intr_subscribe(APIC_TIMER_IV, temp_intr_routine_apic_timer);
102 intr_subscribe(RTC_TIMER_IV, temp_intr_routine_rtc_tick);
104 rtc_enable_timer(); // start RTC timer
105 apic_write_reg(APIC_TIMER_ICR, APIC_CALIBRATION_CONST); // start APIC timer
107 // enable interrupt, just for our RTC start ticking!
108 cpu_enable_interrupt();
110 wait_until(apic_timer_done);
112 // cpu_disable_interrupt();
114 assert_msg(timer_ctx->base_frequency, "Fail to initialize timer (NOFREQ)");
116 kprintf(KINFO "Base frequency: %u Hz\n", timer_ctx->base_frequency);
118 timer_ctx->running_frequency = frequency;
119 timer_ctx->tps = timer_ctx->base_frequency / frequency;
122 intr_unsubscribe(APIC_TIMER_IV, temp_intr_routine_apic_timer);
123 intr_unsubscribe(RTC_TIMER_IV, temp_intr_routine_rtc_tick);
125 apic_write_reg(APIC_TIMER_LVT,
126 LVT_ENTRY_TIMER(APIC_TIMER_IV, LVT_TIMER_PERIODIC));
127 intr_subscribe(APIC_TIMER_IV, timer_update);
129 apic_write_reg(APIC_TIMER_ICR, timer_ctx->tps);
133 timer_run_second(uint32_t second, void (*callback)(void*), void* payload, uint8_t flags)
135 return timer_run(second * timer_ctx->running_frequency, callback, payload, flags);
139 timer_run_ms(uint32_t millisecond, void (*callback)(void*), void* payload, uint8_t flags)
141 return timer_run(timer_ctx->running_frequency / 1000 * millisecond, callback, payload, flags);
145 timer_run(ticks_t ticks, void (*callback)(void*), void* payload, uint8_t flags)
147 struct lx_timer* timer = (struct lx_timer*)lxmalloc(sizeof(struct lx_timer));
149 if (!timer) return 0;
151 timer->callback = callback;
152 timer->counter = ticks;
153 timer->deadline = ticks;
154 timer->payload = payload;
155 timer->flags = flags;
157 llist_append(timer_ctx->active_timers, &timer->link);
163 timer_update(const isr_param* param)
165 struct lx_timer *pos, *n;
166 struct lx_timer* timer_list_head = timer_ctx->active_timers;
168 llist_for_each(pos, n, &timer_list_head->link, link)
170 if (--pos->counter) {
174 pos->callback ? pos->callback(pos->payload) : 1;
176 if (pos->flags & TIMER_MODE_PERIODIC) {
177 pos->counter = pos->deadline;
179 llist_delete(&pos->link);
186 temp_intr_routine_rtc_tick(const isr_param* param)
190 // dummy read on register C so RTC can send anther interrupt
191 // This strange behaviour observed in virtual box & bochs
192 (void)rtc_read_reg(RTC_REG_C);
196 temp_intr_routine_apic_timer(const isr_param* param)
198 timer_ctx->base_frequency =
199 APIC_CALIBRATION_CONST / rtc_counter * RTC_TIMER_BASE_FREQUENCY;
205 struct lx_timer_context*