refactor: use a more decent physical memory map
[lunaix-os.git] / lunaix-os / hal / timer / apic_timer.c
1 #include <hal/apic_timer.h>
2 #include <hal/hwrtc.h>
3 #include <hal/hwtimer.h>
4
5 #include <lunaix/compiler.h>
6 #include <lunaix/isrm.h>
7 #include <lunaix/spike.h>
8 #include <lunaix/syslog.h>
9
10 #include <sys/apic.h>
11
12 LOG_MODULE("APIC_TIMER")
13
14 #define LVT_ENTRY_TIMER(vector, mode) (LVT_DELIVERY_FIXED | mode | vector)
15 #define APIC_CALIBRATION_CONST 0x100000
16
17 // Don't optimize them! Took me an half hour to figure that out...
18
19 static volatile u32_t rtc_counter = 0;
20 static volatile u8_t apic_timer_done = 0;
21 static volatile ticks_t base_freq = 0;
22 static volatile ticks_t systicks = 0;
23
24 static timer_tick_cb tick_cb = NULL;
25
26 static void
27 __rtc_on_tick_cb(struct hwrtc* param)
28 {
29     rtc_counter++;
30 }
31
32 static void
33 temp_intr_routine_apic_timer(const isr_param* param)
34 {
35     base_freq = APIC_CALIBRATION_CONST / rtc_counter * current_rtc->base_freq;
36     apic_timer_done = 1;
37 }
38
39 static void
40 apic_timer_tick_isr(const isr_param* param)
41 {
42     systicks++;
43
44     if (likely((ptr_t)tick_cb)) {
45         tick_cb();
46     }
47 }
48
49 static int
50 apic_timer_check(struct hwtimer_context* hwt)
51 {
52     // TODO check whether apic timer is supported
53     return 1;
54 }
55
56 static ticks_t
57 apic_get_systicks()
58 {
59     return systicks;
60 }
61
62 static ticks_t
63 apic_get_base_freq()
64 {
65     return base_freq;
66 }
67
68 void
69 apic_timer_init(struct hwtimer_context* timer,
70                 u32_t hertz,
71                 timer_tick_cb timer_cb)
72 {
73     ticks_t frequency = hertz;
74     tick_cb = timer_cb;
75
76     cpu_disable_interrupt();
77
78     // Setup APIC timer
79
80     // Remap the IRQ 8 (rtc timer's vector) to RTC_TIMER_IV in ioapic
81     //       (Remarks IRQ 8 is pin INTIN8)
82     //       See IBM PC/AT Technical Reference 1-10 for old RTC IRQ
83     //       See Intel's Multiprocessor Specification for IRQ - IOAPIC INTIN
84     //       mapping config.
85
86     // grab ourselves these irq numbers
87     u32_t iv_timer = isrm_ivexalloc(temp_intr_routine_apic_timer);
88
89     // Setup a one-shot timer, we will use this to measure the bus speed. So we
90     // can then calibrate apic timer to work at *nearly* accurate hz
91     apic_write_reg(APIC_TIMER_LVT,
92                    LVT_ENTRY_TIMER(iv_timer, LVT_TIMER_ONESHOT));
93
94     // Set divider to 64
95     apic_write_reg(APIC_TIMER_DCR, APIC_TIMER_DIV64);
96
97     /*
98         Timer calibration process - measure the APIC timer base frequency
99
100          step 1: setup a temporary isr for RTC timer which trigger at each tick
101                  (1024Hz)
102          step 2: setup a temporary isr for #APIC_TIMER_IV
103          step 3: setup the divider, APIC_TIMER_DCR
104          step 4: Startup RTC timer
105          step 5: Write a large value, v, to APIC_TIMER_ICR to start APIC timer
106        (this must be followed immediately after step 4) step 6: issue a write to
107        EOI and clean up.
108
109         When the APIC ICR counting down to 0 #APIC_TIMER_IV triggered, save the
110        rtc timer's counter, k, and disable RTC timer immediately (although the
111        RTC interrupts should be blocked by local APIC as we are currently busy
112        on handling #APIC_TIMER_IV)
113
114         So the apic timer frequency F_apic in Hz can be calculate as
115             v / F_apic = k / 1024
116             =>  F_apic = v / k * 1024
117
118     */
119
120 #ifdef __LUNAIXOS_DEBUG__
121     if (frequency < 1000) {
122         kprintf(KWARN "Frequency too low. Millisecond timer might be dodgy.");
123     }
124 #endif
125
126     rtc_counter = 0;
127     apic_timer_done = 0;
128
129     current_rtc->do_ticking(current_rtc, __rtc_on_tick_cb);
130     apic_write_reg(APIC_TIMER_ICR, APIC_CALIBRATION_CONST); // start APIC timer
131
132     // enable interrupt, just for our RTC start ticking!
133     cpu_enable_interrupt();
134
135     wait_until(apic_timer_done);
136
137     current_rtc->end_ticking(current_rtc);
138
139     cpu_disable_interrupt();
140
141     assert_msg(base_freq, "Fail to initialize timer (NOFREQ)");
142
143     kprintf(KINFO "hw: %u Hz; os: %u Hz\n", base_freq, frequency);
144
145     // cleanup
146     isrm_ivfree(iv_timer);
147
148     apic_write_reg(
149       APIC_TIMER_LVT,
150       LVT_ENTRY_TIMER(isrm_ivexalloc(apic_timer_tick_isr), LVT_TIMER_PERIODIC));
151
152     timer->base_freq = base_freq;
153
154     ticks_t tphz = base_freq / frequency;
155     apic_write_reg(APIC_TIMER_ICR, tphz);
156 }
157
158 struct hwtimer_context*
159 apic_hwtimer_context()
160 {
161     static struct hwtimer_context apic_hwt = { .name = "apic_timer",
162                                                .init = apic_timer_init,
163                                                .supported = apic_timer_check,
164                                                .systicks = apic_get_systicks };
165
166     return &apic_hwt;
167 }