calcite/src/kernel/interrupts.c
2026-01-06 13:04:05 -05:00

320 lines
8.8 KiB
C

/* 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 <https://www.gnu.org/licenses/>.
*/
//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);
}