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