4 * @brief Abstraction for Advanced Programmable Interrupts Controller (APIC)
8 * @copyright Copyright (c) 2022
16 #include <arch/x86/interrupts.h>
18 #include <lunaix/spike.h>
19 #include <lunaix/syslog.h>
32 // ensure that external interrupt is disabled
33 cpu_disable_interrupt();
35 // Make sure the APIC is there
36 // FUTURE: Use 8259 as fallback
37 assert_msg(cpu_has_apic(), "No APIC detected!");
39 // As we are going to use APIC, disable the old 8259 PIC
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)
50 ::"i"(IA32_APIC_BASE_MSR), "i"(IA32_APIC_ENABLE)
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);
58 kprintf(KINFO "ID: %x, Version: %x, Max LVT: %u\n",
61 (apic_ver >> 16) & 0xff);
63 // initialize the local vector table (LVT)
66 // initialize priority registers
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));
75 uint32_t spiv = apic_read_reg(APIC_SPIVR);
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);
81 // setup timer and performing calibrations
85 #define LVT_ENTRY_LINT0(vector) (LVT_DELIVERY_FIXED | vector)
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)
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));
103 temp_intr_routine_rtc_tick(const isr_param* param);
106 temp_intr_routine_apic_timer(const isr_param* param);
109 test_timer(const isr_param* param);
111 uint32_t apic_timer_base_freq = 0;
113 // Don't optimize them! Took me an half hour to figure that out...
115 volatile uint32_t rtc_counter = 0;
116 volatile uint8_t apic_timer_done = 0;
118 #define APIC_CALIBRATION_CONST 0x100000
123 cpu_disable_interrupt();
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));
132 apic_write_reg(APIC_TIMER_DCR, APIC_TIMER_DIV64);
135 Timer calibration process - measure the APIC timer base frequency
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.
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)
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
155 apic_timer_base_freq = 0;
159 intr_subscribe(APIC_TIMER_IV, temp_intr_routine_apic_timer);
160 intr_subscribe(RTC_TIMER_IV, temp_intr_routine_rtc_tick);
163 rtc_enable_timer(); // start RTC timer
164 apic_write_reg(APIC_TIMER_ICR, APIC_CALIBRATION_CONST); // start APIC timer
166 // enable interrupt, just for our RTC start ticking!
167 cpu_enable_interrupt();
169 wait_until(apic_timer_done);
171 // cpu_disable_interrupt();
173 assert_msg(apic_timer_base_freq, "Fail to initialize timer");
175 kprintf(KINFO "Timer base frequency: %u Hz\n", apic_timer_base_freq);
178 intr_unsubscribe(APIC_TIMER_IV, temp_intr_routine_apic_timer);
179 intr_unsubscribe(RTC_TIMER_IV, temp_intr_routine_rtc_tick);
181 // TODO: now setup timer with our custom frequency which we can derived from the base frequency
184 apic_write_reg(APIC_TIMER_LVT, LVT_ENTRY_TIMER(APIC_TIMER_IV, LVT_TIMER_PERIODIC));
185 intr_subscribe(APIC_TIMER_IV, test_timer);
187 apic_write_reg(APIC_TIMER_ICR, apic_timer_base_freq);
190 volatile rtc_datetime datetime;
193 test_timer(const isr_param* param) {
195 rtc_get_datetime(&datetime);
197 kprintf(KWARN "%u/%02u/%02u %02u:%02u:%02u\r",
207 temp_intr_routine_rtc_tick(const isr_param* param) {
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);
216 temp_intr_routine_apic_timer(const isr_param* param) {
217 apic_timer_base_freq = APIC_CALIBRATION_CONST / rtc_counter * RTC_TIMER_BASE_FREQUENCY;