/* Calcite, src/kernel/interrupts.c * Copyright 2025 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 "panic.h" struct [[gnu::packed]] exception_parameter { 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; }; //referenced in interrupts.asm void isr_exception_c(struct exception_parameter *parameter) { (void)parameter; panic("TODO"); } 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 enable_interrupts_asm( struct gdt_descriptor *gdtr, struct idt_descriptor *idtr, uint16_t tssr); void enable_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; } enable_interrupts_asm(&the_gdt_descriptor, &the_idt_descriptor, 0x08); }