Decoupling Architectural-specific Code (#35)
[lunaix-os.git] / lunaix-os / arch / i386 / hal / apic_timer.c
diff --git a/lunaix-os/arch/i386/hal/apic_timer.c b/lunaix-os/arch/i386/hal/apic_timer.c
new file mode 100644 (file)
index 0000000..9ea0be6
--- /dev/null
@@ -0,0 +1,161 @@
+#include "apic_timer.h"
+#include <hal/hwtimer.h>
+
+#include <lunaix/clock.h>
+#include <lunaix/compiler.h>
+#include <lunaix/generic/isrm.h>
+#include <lunaix/spike.h>
+#include <lunaix/syslog.h>
+
+#include "sys/apic.h"
+
+LOG_MODULE("APIC_TIMER")
+
+#define LVT_ENTRY_TIMER(vector, mode) (LVT_DELIVERY_FIXED | mode | vector)
+#define APIC_BASETICKS 0x100000
+
+// Don't optimize them! Took me an half hour to figure that out...
+
+static volatile u8_t apic_timer_done = 0;
+static volatile ticks_t base_freq = 0;
+static volatile ticks_t systicks = 0;
+
+static timer_tick_cb tick_cb = NULL;
+
+static void
+temp_intr_routine_apic_timer(const struct hart_state* state)
+{
+    apic_timer_done = 1;
+}
+
+static void
+apic_timer_tick_isr(const struct hart_state* state)
+{
+    systicks++;
+
+    if (likely((ptr_t)tick_cb)) {
+        tick_cb();
+    }
+}
+
+static int
+apic_timer_check(struct hwtimer* hwt)
+{
+    // TODO check whether apic timer is supported
+    return 1;
+}
+
+static ticks_t
+apic_get_systicks()
+{
+    return systicks;
+}
+
+static ticks_t
+apic_get_base_freq()
+{
+    return base_freq;
+}
+
+void
+apic_timer_init(struct hwtimer* timer, u32_t hertz, timer_tick_cb timer_cb)
+{
+    ticks_t frequency = hertz;
+    tick_cb = timer_cb;
+
+    cpu_disable_interrupt();
+
+    // Setup APIC timer
+
+    // Remap the IRQ 8 (rtc timer's vector) to RTC_TIMER_IV in ioapic
+    //       (Remarks IRQ 8 is pin INTIN8)
+    //       See IBM PC/AT Technical Reference 1-10 for old RTC IRQ
+    //       See Intel's Multiprocessor Specification for IRQ - IOAPIC INTIN
+    //       mapping config.
+
+    // grab ourselves these irq numbers
+    u32_t iv_timer = isrm_ivexalloc(temp_intr_routine_apic_timer);
+
+    // Setup a one-shot timer, we will use this to measure the bus speed. So we
+    // can then calibrate apic timer to work at *nearly* accurate hz
+    apic_write_reg(APIC_TIMER_LVT,
+                   LVT_ENTRY_TIMER(iv_timer, LVT_TIMER_ONESHOT));
+
+    // Set divider to 64
+    apic_write_reg(APIC_TIMER_DCR, APIC_TIMER_DIV64);
+
+    /*
+        Timer calibration process - measure the APIC timer base frequency
+
+         step 1: setup a temporary isr for RTC timer which trigger at each tick
+                 (1024Hz)
+         step 2: setup a temporary isr for #APIC_TIMER_IV
+         step 3: setup the divider, APIC_TIMER_DCR
+         step 4: Startup RTC timer
+         step 5: Write a large value, v, to APIC_TIMER_ICR to start APIC timer
+       (this must be followed immediately after step 4) step 6: issue a write to
+       EOI and clean up.
+
+        When the APIC ICR counting down to 0 #APIC_TIMER_IV triggered, save the
+       rtc timer's counter, k, and disable RTC timer immediately (although the
+       RTC interrupts should be blocked by local APIC as we are currently busy
+       on handling #APIC_TIMER_IV)
+
+        So the apic timer frequency F_apic in Hz can be calculate as
+            v / F_apic = k / 1024
+            =>  F_apic = v / k * 1024
+
+    */
+
+#ifdef __LUNAIXOS_DEBUG__
+    if (frequency < 1000) {
+        WARN("Frequency too low. Millisecond timer might be dodgy.");
+    }
+#endif
+
+    apic_timer_done = 0;
+
+    sysrtc->cls_mask(sysrtc);
+    apic_write_reg(APIC_TIMER_ICR, APIC_BASETICKS); // start APIC timer
+
+    // enable interrupt, just for our RTC start ticking!
+    cpu_enable_interrupt();
+
+    wait_until(apic_timer_done);
+
+    cpu_disable_interrupt();
+
+    sysrtc->set_mask(sysrtc);
+
+    base_freq = sysrtc->get_counts(sysrtc);
+    base_freq = APIC_BASETICKS / base_freq * sysrtc->base_freq;
+
+    assert_msg(base_freq, "Fail to initialize timer (NOFREQ)");
+
+    kprintf("hw: %u Hz; os: %u Hz", base_freq, frequency);
+
+    // cleanup
+    isrm_ivfree(iv_timer);
+
+    ticks_t tphz = base_freq / frequency;
+    timer->base_freq = base_freq;
+    apic_write_reg(APIC_TIMER_ICR, tphz);
+
+    apic_write_reg(
+      APIC_TIMER_LVT,
+      LVT_ENTRY_TIMER(isrm_ivexalloc(apic_timer_tick_isr), LVT_TIMER_PERIODIC));
+}
+
+struct hwtimer*
+apic_hwtimer_context()
+{
+    static struct hwtimer apic_hwt = {
+        .name = "apic_timer",
+        .class = DEVCLASSV(DEVIF_SOC, DEVFN_TIME, DEV_TIMER, DEV_TIMER_APIC),
+        .init = apic_timer_init,
+        .supported = apic_timer_check,
+        .systicks = apic_get_systicks
+    };
+
+    return &apic_hwt;
+}