calcite/src/kernel/interrupts.c

252 lines
6.3 KiB
C

/* 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 <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 "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);
}