252 lines
6.3 KiB
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);
|
|
|
|
}
|