X-Git-Url: https://scm.lunaixsky.com/lunaix-os.git/blobdiff_plain/37fb1e9cee287c9ae8c065ff517c508eb5f9d7dd..05b7549a0f980efa33265a091a5174a78851ce05:/lunaix-os/hal/apic.c?ds=sidebyside diff --git a/lunaix-os/hal/apic.c b/lunaix-os/hal/apic.c new file mode 100644 index 0000000..c74d3f9 --- /dev/null +++ b/lunaix-os/hal/apic.c @@ -0,0 +1,221 @@ +/** + * @file apic.c + * @author Lunaixsky + * @brief Abstraction for Advanced Programmable Interrupts Controller (APIC) + * @version 0.1 + * @date 2022-03-06 + * + * @copyright Copyright (c) 2022 + * + */ +#include +#include +#include +#include + +#include + +#include +#include + +LOG_MODULE("APIC") + +void +apic_setup_timer(); + +void +apic_setup_lvts(); + +void +init_apic() +{ + // ensure that external interrupt is disabled + cpu_disable_interrupt(); + + // Make sure the APIC is there + // FUTURE: Use 8259 as fallback + assert_msg(cpu_has_apic(), "No APIC detected!"); + + // As we are going to use APIC, disable the old 8259 PIC + pic_disable(); + + // Hardware enable the APIC + // By setting bit 11 of IA32_APIC_BASE register + // Note: After this point, you can't disable then re-enable it until a reset (i.e., reboot) + asm volatile ( + "movl %0, %%ecx\n" + "rdmsr\n" + "orl %1, %%eax\n" + "wrmsr\n" + ::"i"(IA32_APIC_BASE_MSR), "i"(IA32_APIC_ENABLE) + : "eax", "ecx", "edx" + ); + + // Print the basic information of our current local APIC + uint32_t apic_id = apic_read_reg(APIC_IDR) >> 24; + uint32_t apic_ver = apic_read_reg(APIC_VER); + + kprintf(KINFO "ID: %x, Version: %x, Max LVT: %u\n", + apic_id, + apic_ver & 0xff, + (apic_ver >> 16) & 0xff); + + // initialize the local vector table (LVT) + apic_setup_lvts(); + + // initialize priority registers + + // set the task priority to the lowest possible, so all external interrupts are acceptable + // Note, the lowest possible priority class is 2, not 0, 1, as they are reserved for + // internal interrupts (vector 0-31, and each p-class resposible for 16 vectors). + // See Intel Manual Vol. 3A, 10-29 + apic_write_reg(APIC_TPR, APIC_PRIORITY(2, 0)); + + // enable APIC + uint32_t spiv = apic_read_reg(APIC_SPIVR); + + // install our handler for spurious interrupt. + spiv = (spiv & ~0xff) | APIC_SPIV_APIC_ENABLE | APIC_SPIV_IV; + apic_write_reg(APIC_SPIVR, spiv); + + // setup timer and performing calibrations + apic_setup_timer(); +} + +#define LVT_ENTRY_LINT0(vector) (LVT_DELIVERY_FIXED | vector) + +// Pin LINT#1 is configured for relaying NMI, but we masked it here as I think +// it is too early for that +// LINT#1 *must* be edge trigged (Intel manual vol3. 10-14) +#define LVT_ENTRY_LINT1 (LVT_DELIVERY_NMI | LVT_MASKED | LVT_TRIGGER_EDGE) +#define LVT_ENTRY_ERROR(vector) (LVT_DELIVERY_FIXED | vector) +#define LVT_ENTRY_TIMER(vector, mode) (LVT_DELIVERY_FIXED | mode | vector) + +void +apic_setup_lvts() +{ + apic_write_reg(APIC_LVT_LINT0, LVT_ENTRY_LINT0(APIC_LINT0_IV)); + apic_write_reg(APIC_LVT_LINT1, LVT_ENTRY_LINT1); + apic_write_reg(APIC_LVT_ERROR, LVT_ENTRY_ERROR(APIC_ERROR_IV)); +} + +void +temp_intr_routine_rtc_tick(const isr_param* param); + +void +temp_intr_routine_apic_timer(const isr_param* param); + +void +test_timer(const isr_param* param); + +uint32_t apic_timer_base_freq = 0; + +// Don't optimize them! Took me an half hour to figure that out... + +volatile uint32_t rtc_counter = 0; +volatile uint8_t apic_timer_done = 0; + +#define APIC_CALIBRATION_CONST 0x100000 + +void +apic_setup_timer() +{ + cpu_disable_interrupt(); + + // Setup APIC timer + + // Setup a one-shot timer, we will use this to measure the bus speed. So we can + // then calibrate apic timer to work at *nearly* accurate hz + apic_write_reg(APIC_TIMER_LVT, LVT_ENTRY_TIMER(APIC_TIMER_IV, LVT_TIMER_ONESHOT)); + + // Set divider to 64 + apic_write_reg(APIC_TIMER_DCR, APIC_TIMER_DIV64); + + /* + Timer calibration process - measure the APIC timer base frequency + + step 1: setup a temporary isr for RTC timer which trigger at each tick (1024Hz) + step 2: setup a temporary isr for #APIC_TIMER_IV + step 3: setup the divider, APIC_TIMER_DCR + step 4: Startup RTC timer + step 5: Write a large value, v, to APIC_TIMER_ICR to start APIC timer + (this must be followed immediately after step 4) + step 6: issue a write to EOI and clean up. + + When the APIC ICR counting down to 0 #APIC_TIMER_IV triggered, save the rtc timer's + counter, k, and disable RTC timer immediately (although the RTC interrupts should be + blocked by local APIC as we are currently busy on handling #APIC_TIMER_IV) + + So the apic timer frequency F_apic in Hz can be calculate as + v / F_apic = k / 1024 + => F_apic = v / k * 1024 + + */ + + apic_timer_base_freq = 0; + rtc_counter = 0; + apic_timer_done = 0; + + intr_subscribe(APIC_TIMER_IV, temp_intr_routine_apic_timer); + intr_subscribe(RTC_TIMER_IV, temp_intr_routine_rtc_tick); + + + rtc_enable_timer(); // start RTC timer + apic_write_reg(APIC_TIMER_ICR, APIC_CALIBRATION_CONST); // start APIC timer + + // enable interrupt, just for our RTC start ticking! + cpu_enable_interrupt(); + + wait_until(apic_timer_done); + + // cpu_disable_interrupt(); + + assert_msg(apic_timer_base_freq, "Fail to initialize timer"); + + kprintf(KINFO "Timer base frequency: %u Hz\n", apic_timer_base_freq); + + // cleanup + intr_unsubscribe(APIC_TIMER_IV, temp_intr_routine_apic_timer); + intr_unsubscribe(RTC_TIMER_IV, temp_intr_routine_rtc_tick); + + // TODO: now setup timer with our custom frequency which we can derived from the base frequency + // we measured + + apic_write_reg(APIC_TIMER_LVT, LVT_ENTRY_TIMER(APIC_TIMER_IV, LVT_TIMER_PERIODIC)); + intr_subscribe(APIC_TIMER_IV, test_timer); + + apic_write_reg(APIC_TIMER_ICR, apic_timer_base_freq); +} + +volatile rtc_datetime datetime; + +void +test_timer(const isr_param* param) { + + rtc_get_datetime(&datetime); + + kprintf(KWARN "%u/%02u/%02u %02u:%02u:%02u\r", + datetime.year, + datetime.month, + datetime.day, + datetime.hour, + datetime.minute, + datetime.second); +} + +void +temp_intr_routine_rtc_tick(const isr_param* param) { + rtc_counter++; + + // dummy read on register C so RTC can send anther interrupt + // This strange behaviour observed in virtual box & bochs + (void) rtc_read_reg(RTC_REG_C); +} + +void +temp_intr_routine_apic_timer(const isr_param* param) { + apic_timer_base_freq = APIC_CALIBRATION_CONST / rtc_counter * RTC_TIMER_BASE_FREQUENCY; + apic_timer_done = 1; + + rtc_disable_timer(); +} \ No newline at end of file