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/isrm.h>
17 #include <lunaix/mm/cake.h>
18 #include <lunaix/mm/valloc.h>
19 #include <lunaix/sched.h>
20 #include <lunaix/spike.h>
21 #include <lunaix/syslog.h>
22 #include <lunaix/timer.h>
24 #include <hal/acpi/acpi.h>
26 #define LVT_ENTRY_TIMER(vector, mode) (LVT_DELIVERY_FIXED | mode | vector)
31 temp_intr_routine_rtc_tick(const isr_param* param);
34 temp_intr_routine_apic_timer(const isr_param* param);
37 timer_update(const isr_param* param);
39 static volatile struct lx_timer_context* timer_ctx = NULL;
41 // Don't optimize them! Took me an half hour to figure that out...
43 static volatile uint32_t rtc_counter = 0;
44 static volatile uint8_t apic_timer_done = 0;
46 static volatile uint32_t sched_ticks = 0;
47 static volatile uint32_t sched_ticks_counter = 0;
49 static struct cake_pile* timer_pile;
51 #define APIC_CALIBRATION_CONST 0x100000
56 timer_pile = cake_new_pile("timer", sizeof(struct lx_timer), 1, 0);
58 (struct lx_timer_context*)valloc(sizeof(struct lx_timer_context));
60 assert_msg(timer_ctx, "Fail to initialize timer contex");
62 timer_ctx->active_timers = (struct lx_timer*)cake_grab(timer_pile);
63 llist_init_head(&timer_ctx->active_timers->link);
67 timer_init(uint32_t frequency)
71 cpu_disable_interrupt();
75 // Remap the IRQ 8 (rtc timer's vector) to RTC_TIMER_IV in ioapic
76 // (Remarks IRQ 8 is pin INTIN8)
77 // See IBM PC/AT Technical Reference 1-10 for old RTC IRQ
78 // See Intel's Multiprocessor Specification for IRQ - IOAPIC INTIN
81 // grab ourselves these irq numbers
82 uint32_t iv_rtc = isrm_bindirq(PC_AT_IRQ_RTC, temp_intr_routine_rtc_tick);
83 uint32_t iv_timer = isrm_ivexalloc(temp_intr_routine_apic_timer);
85 // Setup a one-shot timer, we will use this to measure the bus speed. So we
86 // can then calibrate apic timer to work at *nearly* accurate hz
87 apic_write_reg(APIC_TIMER_LVT,
88 LVT_ENTRY_TIMER(iv_timer, LVT_TIMER_ONESHOT));
91 apic_write_reg(APIC_TIMER_DCR, APIC_TIMER_DIV64);
94 Timer calibration process - measure the APIC timer base frequency
96 step 1: setup a temporary isr for RTC timer which trigger at each tick
98 step 2: setup a temporary isr for #APIC_TIMER_IV
99 step 3: setup the divider, APIC_TIMER_DCR
100 step 4: Startup RTC timer
101 step 5: Write a large value, v, to APIC_TIMER_ICR to start APIC timer
102 (this must be followed immediately after step 4) step 6: issue a write to
105 When the APIC ICR counting down to 0 #APIC_TIMER_IV triggered, save the
106 rtc timer's counter, k, and disable RTC timer immediately (although the
107 RTC interrupts should be blocked by local APIC as we are currently busy
108 on handling #APIC_TIMER_IV)
110 So the apic timer frequency F_apic in Hz can be calculate as
111 v / F_apic = k / 1024
112 => F_apic = v / k * 1024
116 #ifdef __LUNAIXOS_DEBUG__
117 if (frequency < 1000) {
118 kprintf(KWARN "Frequency too low. Millisecond timer might be dodgy.");
122 timer_ctx->base_frequency = 0;
126 rtc_enable_timer(); // start RTC timer
127 apic_write_reg(APIC_TIMER_ICR, APIC_CALIBRATION_CONST); // start APIC timer
129 // enable interrupt, just for our RTC start ticking!
130 cpu_enable_interrupt();
132 wait_until(apic_timer_done);
134 assert_msg(timer_ctx->base_frequency, "Fail to initialize timer (NOFREQ)");
137 KINFO "hw: %u Hz; os: %u Hz\n", timer_ctx->base_frequency, frequency);
139 timer_ctx->running_frequency = frequency;
140 timer_ctx->tphz = timer_ctx->base_frequency / frequency;
143 isrm_ivfree(iv_timer);
148 LVT_ENTRY_TIMER(isrm_ivexalloc(timer_update), LVT_TIMER_PERIODIC));
150 apic_write_reg(APIC_TIMER_ICR, timer_ctx->tphz);
152 sched_ticks = timer_ctx->running_frequency / 1000 * SCHED_TIME_SLICE;
153 sched_ticks_counter = 0;
157 timer_run_second(uint32_t second,
158 void (*callback)(void*),
163 second * timer_ctx->running_frequency, callback, payload, flags);
167 timer_run_ms(uint32_t millisecond,
168 void (*callback)(void*),
172 return timer_run(timer_ctx->running_frequency / 1000 * millisecond,
179 timer_run(ticks_t ticks, void (*callback)(void*), void* payload, uint8_t flags)
181 struct lx_timer* timer = (struct lx_timer*)cake_grab(timer_pile);
186 timer->callback = callback;
187 timer->counter = ticks;
188 timer->deadline = ticks;
189 timer->payload = payload;
190 timer->flags = flags;
192 llist_append(&timer_ctx->active_timers->link, &timer->link);
198 timer_update(const isr_param* param)
200 struct lx_timer *pos, *n;
201 struct lx_timer* timer_list_head = timer_ctx->active_timers;
203 llist_for_each(pos, n, &timer_list_head->link, link)
205 if (--(pos->counter)) {
209 pos->callback ? pos->callback(pos->payload) : 1;
211 if ((pos->flags & TIMER_MODE_PERIODIC)) {
212 pos->counter = pos->deadline;
214 llist_delete(&pos->link);
215 cake_release(timer_pile, pos);
219 sched_ticks_counter++;
221 if (sched_ticks_counter >= sched_ticks) {
222 sched_ticks_counter = 0;
228 temp_intr_routine_rtc_tick(const isr_param* param)
232 // dummy read on register C so RTC can send anther interrupt
233 // This strange behaviour observed in virtual box & bochs
234 (void)rtc_read_reg(RTC_REG_C);
238 temp_intr_routine_apic_timer(const isr_param* param)
240 timer_ctx->base_frequency =
241 APIC_CALIBRATION_CONST / rtc_counter * RTC_TIMER_BASE_FREQUENCY;
247 struct lx_timer_context*