Unit testing framework and devicetree framework refactoring (#50)
[lunaix-os.git] / lunaix-os / arch / x86 / hal / apic_timer.c
1 #include "apic_timer.h"
2 #include <hal/hwtimer.h>
3
4 #include <lunaix/clock.h>
5 #include <lunaix/compiler.h>
6 #include <lunaix/spike.h>
7 #include <lunaix/syslog.h>
8
9 #include <asm/x86_isrm.h>
10 #include "asm/soc/apic.h"
11
12 LOG_MODULE("timer(apic)")
13
14 #define LVT_ENTRY_TIMER(vector, mode) (LVT_DELIVERY_FIXED | mode | vector)
15 #define APIC_BASETICKS 0x100000
16
17 static void
18 temp_intr_routine_apic_timer(const struct hart_state* state)
19 {
20     struct hwtimer_pot* pot;
21
22     pot = (struct hwtimer_pot*)isrm_get_payload(state);
23     pot->systick_raw = (ticks_t)-1;
24 }
25
26 static void
27 apic_timer_tick_isr(const struct hart_state* state)
28 {
29     struct hwtimer_pot* pot;
30
31     pot = (struct hwtimer_pot*)isrm_get_payload(state);
32     pot->systick_raw++;
33
34     if (likely(__ptr(pot->callback))) {
35         pot->callback();
36     }
37 }
38
39 static void
40 __apic_timer_calibrate(struct hwtimer_pot* pot, u32_t hertz)
41 {
42     ticks_t base_freq = 0;
43
44     cpu_disable_interrupt();
45
46     // Setup APIC timer
47
48     // grab ourselves these irq numbers
49     u32_t iv_timer = isrm_ivexalloc(temp_intr_routine_apic_timer);
50     isrm_set_payload(iv_timer, __ptr(pot));
51
52     // Setup a one-shot timer, we will use this to measure the bus speed. So we
53     // can then calibrate apic timer to work at *nearly* accurate hz
54     apic_write_reg(APIC_TIMER_LVT,
55                    LVT_ENTRY_TIMER(iv_timer, LVT_TIMER_ONESHOT));
56
57     // Set divider to 64
58     apic_write_reg(APIC_TIMER_DCR, APIC_TIMER_DIV64);
59
60     /*
61         Timer calibration process - measure the APIC timer base frequency
62
63          step 1: setup a temporary isr for RTC timer which trigger at each tick
64                  (1024Hz)
65          step 2: setup a temporary isr for #APIC_TIMER_IV
66          step 3: setup the divider, APIC_TIMER_DCR
67          step 4: Startup RTC timer
68          step 5: Write a large value, v, to APIC_TIMER_ICR to start APIC timer
69        (this must be followed immediately after step 4) step 6: issue a write to
70        EOI and clean up.
71
72         When the APIC ICR counting down to 0 #APIC_TIMER_IV triggered, save the
73        rtc timer's counter, k, and disable RTC timer immediately (although the
74        RTC interrupts should be blocked by local APIC as we are currently busy
75        on handling #APIC_TIMER_IV)
76
77         So the apic timer frequency F_apic in Hz can be calculate as
78             v / F_apic = k / 1024
79             =>  F_apic = v / k * 1024
80
81     */
82
83     sysrtc->ops->set_proactive(sysrtc, true);
84     apic_write_reg(APIC_TIMER_ICR, APIC_BASETICKS); // start APIC timer
85
86     pot->systick_raw = 0;
87     cpu_enable_interrupt();
88
89     wait_until(!(pot->systick_raw + 1));
90
91     cpu_disable_interrupt();
92     isrm_ivfree(iv_timer);
93
94     sysrtc->ops->set_proactive(sysrtc, false);
95     
96     base_freq = sysrtc->live;
97     base_freq = APIC_BASETICKS / base_freq * sysrtc->base_freq;
98     pot->base_freq = base_freq;
99     pot->systick_raw = 0;
100
101     assert_msg(base_freq, "Fail to initialize timer (NOFREQ)");
102     INFO("hw: %u Hz; os: %u Hz", base_freq, hertz);
103
104     apic_write_reg(APIC_TIMER_ICR, base_freq / hertz);
105     
106     iv_timer = isrm_ivexalloc(apic_timer_tick_isr);
107     isrm_set_payload(iv_timer, __ptr(pot));
108     
109     apic_write_reg(
110         APIC_TIMER_LVT,
111         LVT_ENTRY_TIMER(iv_timer, LVT_TIMER_PERIODIC));
112 }
113
114 static struct hwtimer_pot_ops potops = {
115     .calibrate = __apic_timer_calibrate,
116 };
117
118 static int
119 __apic_timer_load(struct device_def* def)
120 {
121     struct device* timer;
122
123     timer = device_allocsys(NULL, NULL);
124
125     hwtimer_attach_potens(timer, HWTIMER_MAX_PRECEDENCE, &potops);
126     register_device_var(timer, &def->class, "apic-timer");
127     return 0;
128 }
129
130
131 static struct device_def x86_apic_timer = 
132 {
133     def_device_class(INTEL, TIME, TIMER_APIC),
134     def_device_name("Intel APIC Timer"),
135     
136     def_on_load(__apic_timer_load)
137 };
138 EXPORT_DEVICE(apic_timer, &x86_apic_timer, load_sysconf);