/* Calcite, src/kernel/interrupts.c * Copyright 2025-2026 Benji Dial * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ //relevant sources: // amd64 architecture programmer's manual, volume 2: system programming // https://osdev.wiki/wiki/Global_Descriptor_Table // https://osdev.wiki/wiki/Interrupt_Descriptor_Table // https://osdev.wiki/wiki/Task_State_Segment #include "interrupts.h" #include "process.h" #include "paging.h" #include "debug.h" //interrupts.asm depends on layout struct [[gnu::packed]] exception_parameter { uint64_t cr3; uint64_t cr2; uint64_t r15; uint64_t r14; uint64_t r13; uint64_t r12; uint64_t r11; uint64_t r10; uint64_t r9; uint64_t r8; uint64_t rbp; uint64_t rsi; uint64_t rdi; uint64_t rdx; uint64_t rcx; uint64_t rbx; uint64_t rax; uint64_t exception_number; //0 if exception pushes no error code uint64_t error_code; uint64_t rip; uint64_t cs; uint64_t rflags; uint64_t rsp; uint64_t ss; }; //names are from amd64 manual mentioned above const char *exception_names[32] = { "divide-by-zero-error", "debug", "non-maskable-interrupt", "breakpoint", "overflow", "bound-range", "invalid-opcode", "device-not-available", "double-fault", 0, "invalid-tss", "segment-not-present", "stack", "general-protection", "page-fault", 0, "x87 floating-point exception-pending", "alignment-check", "machine-check", "simd floating-point", 0, 0, 0, 0, 0, 0, 0, 0, "hypervisor injection exception", "vmm communication exception", "security exception", 0 }; [[noreturn]] static void unhandled_exception(const struct exception_parameter *parameter) { assert(parameter->exception_number < 32 && exception_names[parameter->exception_number] != 0) log("unhandled exception") log(" exception name = %s", exception_names[parameter->exception_number]) log(" error code = 0x%h", parameter->error_code, 16) log(" cr2 = 0x%h", parameter->cr2, 16) log(" cr3 = 0x%h", parameter->cr3, 16) log(" cs = 0x%h", parameter->cs, 16) log(" rip = 0x%h", parameter->rip, 16) log(" ss = 0x%h", parameter->ss, 16) log(" rsp = 0x%h", parameter->rsp, 16) log(" rflags = 0x%h", parameter->rflags, 16) log(" rax = 0x%h", parameter->rax, 16) log(" rbx = 0x%h", parameter->rbx, 16) log(" rcx = 0x%h", parameter->rcx, 16) log(" rdx = 0x%h", parameter->rdx, 16) log(" rdi = 0x%h", parameter->rdi, 16) log(" rsi = 0x%h", parameter->rsi, 16) log(" rbp = 0x%h", parameter->rbp, 16) log(" r8 = 0x%h", parameter->r8, 16) log(" r9 = 0x%h", parameter->r9, 16) log(" r10 = 0x%h", parameter->r10, 16) log(" r11 = 0x%h", parameter->r11, 16) log(" r12 = 0x%h", parameter->r12, 16) log(" r13 = 0x%h", parameter->r13, 16) log(" r14 = 0x%h", parameter->r14, 16) log(" r15 = 0x%h", parameter->r15, 16) log("halting") while (1) __asm__ ("hlt"); } //referenced in interrupts.asm void isr_exception_c(const struct exception_parameter *parameter) { if (running_thread != 0 && parameter->exception_number == 0x0e && (parameter->error_code & 0x1) == 0x0 && parameter->cr2 >= (uint64_t)running_thread->stack_bottom + 4096 && parameter->cr2 < (uint64_t)running_thread->stack_top) { void *vma = (void *)(parameter->cr2 & 0xfffffffffffff000); unmap_page_for_process(running_thread->process, vma); uint64_t pma = take_free_physical_page(); map_page_for_process( running_thread->process, pma, vma, 1, 0, 1); } else unhandled_exception(parameter); } void (*irq_handlers[16])() = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; //referenced in interrupts.asm void isr_irq_c(uint64_t irq_number) { if (irq_handlers[irq_number]) (*irq_handlers[irq_number])(); } void set_irq_handler(uint8_t irq_line, void (*handler)(void)) { assert(irq_line < 16); irq_handlers[irq_line] = handler; } enum { GDT_AB_CODE_DATA_ACCESSED = 0x01, GDT_AB_DATA_WRITABLE = 0x02, GDT_AB_TYPE_TSS = 0x09, GDT_AB_TYPE_DATA = 0x10, GDT_AB_TYPE_CODE = 0x18, GDT_AB_DPL_ZERO = 0x00, GDT_AB_DPL_THREE = 0x60, GDT_AB_PRESENT = 0x80, GDT_FLAG_LONG = 0x20 }; struct [[gnu::packed]] gdt_entry { uint32_t zero; uint8_t zero_; uint8_t access_byte; uint8_t flags; uint8_t zero__; }; struct [[gnu::packed]] gdt_entry_system { uint16_t limit_0; uint16_t base_0; uint8_t base_16; uint8_t access_byte; uint8_t flags; uint8_t base_24; uint32_t base_32; uint32_t zero; }; static struct gdt_entry the_gdt[7]; struct [[gnu::packed]] gdt_descriptor { uint16_t limit; void *base; }; static struct gdt_descriptor the_gdt_descriptor = { .limit = 7 * sizeof(struct gdt_entry) - 1, .base = the_gdt }; struct [[gnu::packed]] tss { uint32_t zero; uint64_t rsp0; uint64_t rsp1; uint64_t rsp2; uint64_t zero_; uint64_t ist1; uint64_t ist2; uint64_t ist3; uint64_t ist4; uint64_t ist5; uint64_t ist6; uint64_t ist7; uint64_t zero__; uint16_t zero___; uint16_t io_permission_map_offset; }; static struct tss the_tss; //maybe these should be allocated dynamically with guard pages? #define INTERRUPT_STACK_SIZE (16 << 20) static uint8_t interrupt_stack_one[INTERRUPT_STACK_SIZE]; static uint8_t interrupt_stack_two[INTERRUPT_STACK_SIZE]; enum { //"interrupt" here just means disable interrupts on entry. //in our case, we use this for both irq's and exceptions. IDT_TYPE_INTERRUPT = 0x0e, IDT_TYPE_DPL_ZERO = 0x00, IDT_TYPE_DPL_THREE = 0x60, IDT_TYPE_PRESENT = 0x80 }; struct [[gnu::packed]] idt_entry { uint16_t offset_0; uint16_t segment_selector; uint8_t interrupt_stack_index; uint8_t type; uint16_t offset_16; uint32_t offset_32; uint32_t zero; }; static struct idt_entry the_idt[256]; struct [[gnu::packed]] idt_descriptor { uint16_t limit; void *base; }; static struct idt_descriptor the_idt_descriptor = { .limit = 256 * sizeof(struct idt_entry) - 1, .base = the_idt }; //defined in interrupts.asm extern void *isrs[48]; //defined in interrupts.asm void init_interrupts_asm( struct gdt_descriptor *gdtr, struct idt_descriptor *idtr, uint16_t tssr); void init_interrupts() { the_tss.ist1 = (uint64_t)interrupt_stack_one + INTERRUPT_STACK_SIZE; the_tss.ist2 = (uint64_t)interrupt_stack_two + INTERRUPT_STACK_SIZE; //having the table past the end of the tss will lead to a gpf when the //cpu tries to access it, which is the same effect as a disabled port. the_tss.io_permission_map_offset = 256; uint64_t tss_base = (uint64_t)&the_tss; uint16_t tss_limit = sizeof(struct tss) - 1; struct gdt_entry_system *tss_entry = (struct gdt_entry_system *)&the_gdt[1]; tss_entry->limit_0 = tss_limit; tss_entry->base_0 = tss_base & 0xffff; tss_entry->base_16 = (tss_base >> 16) & 0xff; tss_entry->access_byte = GDT_AB_TYPE_TSS | GDT_AB_DPL_ZERO | GDT_AB_PRESENT; tss_entry->flags = GDT_FLAG_LONG; tss_entry->base_24 = (tss_base >> 24) & 0xff; tss_entry->base_32 = tss_base >> 32; struct gdt_entry *user_data_entry = &the_gdt[3]; user_data_entry->access_byte = GDT_AB_CODE_DATA_ACCESSED | GDT_AB_DATA_WRITABLE | GDT_AB_TYPE_DATA | GDT_AB_DPL_THREE | GDT_AB_PRESENT; user_data_entry->flags = GDT_FLAG_LONG; struct gdt_entry *user_code_entry = &the_gdt[4]; user_code_entry->access_byte = GDT_AB_CODE_DATA_ACCESSED | GDT_AB_TYPE_CODE | GDT_AB_DPL_THREE | GDT_AB_PRESENT; user_code_entry->flags = GDT_FLAG_LONG; struct gdt_entry *kernel_code_entry = &the_gdt[5]; kernel_code_entry->access_byte = GDT_AB_CODE_DATA_ACCESSED | GDT_AB_TYPE_CODE | GDT_AB_DPL_ZERO | GDT_AB_PRESENT; kernel_code_entry->flags = GDT_FLAG_LONG; struct gdt_entry *kernel_data_entry = &the_gdt[6]; kernel_data_entry->access_byte = GDT_AB_CODE_DATA_ACCESSED | GDT_AB_DATA_WRITABLE | GDT_AB_TYPE_DATA | GDT_AB_DPL_ZERO | GDT_AB_PRESENT; kernel_data_entry->flags = GDT_FLAG_LONG; for (int i = 0; i < 48; ++i) { uint64_t offset = (uint64_t)isrs[i]; the_idt[i].offset_0 = offset & 0xffff; the_idt[i].segment_selector = 0x0028; the_idt[i].interrupt_stack_index = i < 32 ? 0x02 : 0x01; the_idt[i].type = IDT_TYPE_INTERRUPT | IDT_TYPE_DPL_ZERO | IDT_TYPE_PRESENT; the_idt[i].offset_16 = (offset >> 16) & 0xffff; the_idt[i].offset_32 = offset >> 32; } init_interrupts_asm(&the_gdt_descriptor, &the_idt_descriptor, 0x08); }