c74d3f91b38425033c494e5d931967ebf0edbd8a
[lunaix-os.git] / lunaix-os / hal / apic.c
1 /**
2  * @file apic.c
3  * @author Lunaixsky
4  * @brief Abstraction for Advanced Programmable Interrupts Controller (APIC)
5  * @version 0.1
6  * @date 2022-03-06
7  *
8  * @copyright Copyright (c) 2022
9  *
10  */
11 #include <hal/apic.h>
12 #include <hal/cpu.h>
13 #include <hal/pic.h>
14 #include <hal/rtc.h>
15
16 #include <arch/x86/interrupts.h>
17
18 #include <lunaix/spike.h>
19 #include <lunaix/syslog.h>
20
21 LOG_MODULE("APIC")
22
23 void
24 apic_setup_timer();
25
26 void
27 apic_setup_lvts();
28
29 void
30 init_apic()
31 {
32     // ensure that external interrupt is disabled
33     cpu_disable_interrupt();
34
35     // Make sure the APIC is there
36     //  FUTURE: Use 8259 as fallback
37     assert_msg(cpu_has_apic(), "No APIC detected!");
38
39     // As we are going to use APIC, disable the old 8259 PIC
40     pic_disable();
41
42     // Hardware enable the APIC
43     // By setting bit 11 of IA32_APIC_BASE register
44     // Note: After this point, you can't disable then re-enable it until a reset (i.e., reboot)
45     asm volatile (
46         "movl %0, %%ecx\n"
47         "rdmsr\n"
48         "orl %1, %%eax\n"
49         "wrmsr\n"
50         ::"i"(IA32_APIC_BASE_MSR), "i"(IA32_APIC_ENABLE)
51         : "eax", "ecx", "edx"
52     );
53
54     // Print the basic information of our current local APIC
55     uint32_t apic_id = apic_read_reg(APIC_IDR) >> 24;
56     uint32_t apic_ver = apic_read_reg(APIC_VER);
57
58     kprintf(KINFO "ID: %x, Version: %x, Max LVT: %u\n",
59            apic_id,
60            apic_ver & 0xff,
61            (apic_ver >> 16) & 0xff);
62
63     // initialize the local vector table (LVT)
64     apic_setup_lvts();
65
66     // initialize priority registers
67     
68     // set the task priority to the lowest possible, so all external interrupts are acceptable
69     //   Note, the lowest possible priority class is 2, not 0, 1, as they are reserved for
70     //   internal interrupts (vector 0-31, and each p-class resposible for 16 vectors). 
71     //   See Intel Manual Vol. 3A, 10-29
72     apic_write_reg(APIC_TPR, APIC_PRIORITY(2, 0));
73
74     // enable APIC
75     uint32_t spiv = apic_read_reg(APIC_SPIVR);
76
77     // install our handler for spurious interrupt.
78     spiv = (spiv & ~0xff) | APIC_SPIV_APIC_ENABLE  | APIC_SPIV_IV;
79     apic_write_reg(APIC_SPIVR, spiv);
80
81     // setup timer and performing calibrations
82     apic_setup_timer();
83 }
84
85 #define LVT_ENTRY_LINT0(vector)           (LVT_DELIVERY_FIXED | vector)
86
87 // Pin LINT#1 is configured for relaying NMI, but we masked it here as I think
88 //  it is too early for that
89 // LINT#1 *must* be edge trigged (Intel manual vol3. 10-14)
90 #define LVT_ENTRY_LINT1                   (LVT_DELIVERY_NMI | LVT_MASKED | LVT_TRIGGER_EDGE)
91 #define LVT_ENTRY_ERROR(vector)           (LVT_DELIVERY_FIXED | vector)
92 #define LVT_ENTRY_TIMER(vector, mode)     (LVT_DELIVERY_FIXED | mode | vector)
93
94 void
95 apic_setup_lvts()
96 {
97     apic_write_reg(APIC_LVT_LINT0, LVT_ENTRY_LINT0(APIC_LINT0_IV));
98     apic_write_reg(APIC_LVT_LINT1, LVT_ENTRY_LINT1);
99     apic_write_reg(APIC_LVT_ERROR, LVT_ENTRY_ERROR(APIC_ERROR_IV));
100 }
101
102 void
103 temp_intr_routine_rtc_tick(const isr_param* param);
104
105 void
106 temp_intr_routine_apic_timer(const isr_param* param);
107
108 void
109 test_timer(const isr_param* param);
110
111 uint32_t apic_timer_base_freq = 0;
112
113 // Don't optimize them! Took me an half hour to figure that out...
114
115 volatile uint32_t rtc_counter = 0;
116 volatile uint8_t apic_timer_done = 0;
117
118 #define APIC_CALIBRATION_CONST  0x100000
119
120 void
121 apic_setup_timer()
122 {
123     cpu_disable_interrupt();
124     
125     // Setup APIC timer
126
127     // Setup a one-shot timer, we will use this to measure the bus speed. So we can
128     //   then calibrate apic timer to work at *nearly* accurate hz
129     apic_write_reg(APIC_TIMER_LVT,  LVT_ENTRY_TIMER(APIC_TIMER_IV, LVT_TIMER_ONESHOT));
130     
131     // Set divider to 64
132     apic_write_reg(APIC_TIMER_DCR, APIC_TIMER_DIV64);
133
134     /*
135         Timer calibration process - measure the APIC timer base frequency
136
137          step 1: setup a temporary isr for RTC timer which trigger at each tick (1024Hz)
138          step 2: setup a temporary isr for #APIC_TIMER_IV
139          step 3: setup the divider, APIC_TIMER_DCR
140          step 4: Startup RTC timer
141          step 5: Write a large value, v, to APIC_TIMER_ICR to start APIC timer 
142                  (this must be followed immediately after step 4)
143          step 6: issue a write to EOI and clean up.
144
145         When the APIC ICR counting down to 0 #APIC_TIMER_IV triggered, save the rtc timer's 
146         counter, k, and disable RTC timer immediately (although the RTC interrupts should be 
147         blocked by local APIC as we are currently busy on handling #APIC_TIMER_IV)
148
149         So the apic timer frequency F_apic in Hz can be calculate as
150             v / F_apic = k / 1024
151             =>  F_apic = v / k * 1024
152
153     */
154
155     apic_timer_base_freq = 0;
156     rtc_counter = 0;
157     apic_timer_done = 0;
158
159     intr_subscribe(APIC_TIMER_IV, temp_intr_routine_apic_timer);
160     intr_subscribe(RTC_TIMER_IV, temp_intr_routine_rtc_tick);
161
162         
163     rtc_enable_timer();                                         // start RTC timer
164     apic_write_reg(APIC_TIMER_ICR, APIC_CALIBRATION_CONST);     // start APIC timer
165     
166     // enable interrupt, just for our RTC start ticking!
167     cpu_enable_interrupt();
168
169     wait_until(apic_timer_done);
170     
171     // cpu_disable_interrupt();
172
173     assert_msg(apic_timer_base_freq, "Fail to initialize timer");
174
175     kprintf(KINFO "Timer base frequency: %u Hz\n", apic_timer_base_freq);
176
177     // cleanup
178     intr_unsubscribe(APIC_TIMER_IV, temp_intr_routine_apic_timer);
179     intr_unsubscribe(RTC_TIMER_IV, temp_intr_routine_rtc_tick);
180
181     // TODO: now setup timer with our custom frequency which we can derived from the base frequency
182     //          we measured
183
184     apic_write_reg(APIC_TIMER_LVT,  LVT_ENTRY_TIMER(APIC_TIMER_IV, LVT_TIMER_PERIODIC));
185     intr_subscribe(APIC_TIMER_IV, test_timer);
186     
187     apic_write_reg(APIC_TIMER_ICR, apic_timer_base_freq);
188 }
189
190 volatile rtc_datetime datetime;
191
192 void
193 test_timer(const isr_param* param) {
194
195     rtc_get_datetime(&datetime);
196
197     kprintf(KWARN "%u/%02u/%02u %02u:%02u:%02u\r",
198            datetime.year,
199            datetime.month,
200            datetime.day,
201            datetime.hour,
202            datetime.minute,
203            datetime.second);
204 }
205
206 void
207 temp_intr_routine_rtc_tick(const isr_param* param) {
208     rtc_counter++;
209
210     // dummy read on register C so RTC can send anther interrupt
211     //  This strange behaviour observed in virtual box & bochs 
212     (void) rtc_read_reg(RTC_REG_C);
213 }
214
215 void
216 temp_intr_routine_apic_timer(const isr_param* param) {
217     apic_timer_base_freq = APIC_CALIBRATION_CONST / rtc_counter * RTC_TIMER_BASE_FREQUENCY;
218     apic_timer_done = 1;
219
220     rtc_disable_timer();
221 }