diff --git a/kernel/include/interrupts.h b/kernel/include/interrupts.h
new file mode 100644
index 0000000..d5b075e
--- /dev/null
+++ b/kernel/include/interrupts.h
@@ -0,0 +1,26 @@
+/* Calcite, kernel/include/interrupts.h
+ * 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 .
+ */
+
+#pragma once
+
+#include
+
+//switches to kernel gdt and idt, maps and unmasks irqs, enables interrupts
+void enable_interrupts();
+
+//the handler should have normal C linkage
+void set_irq_handler(uint8_t irq_line, void (*handler)());
diff --git a/kernel/include/panic.h b/kernel/include/panic.h
new file mode 100644
index 0000000..8555ab3
--- /dev/null
+++ b/kernel/include/panic.h
@@ -0,0 +1,31 @@
+/* Calcite, kernel/include/panic.h
+ * 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 .
+ */
+
+[[noreturn]] void panic_core(
+ const char *file, const char *function, int line, const char *message);
+
+#define panic(message) panic_core(__FILE__, __func__, __LINE__, message);
+
+#ifdef NDEBUG
+#define assert(condition) ((void)0)
+#else
+#define assert(condition) \
+ { \
+ if (!(condition)) \
+ panic("assertion failed: " #condition) \
+ }
+#endif
diff --git a/kernel/src/entry.c b/kernel/src/entry.c
index 6a8771b..724c0fa 100644
--- a/kernel/src/entry.c
+++ b/kernel/src/entry.c
@@ -15,10 +15,12 @@
* this program. If not, see .
*/
+#include
#include
#include
#include
#include
+#include
LIMINE_BASE_REVISION(3)
@@ -177,30 +179,12 @@ static uint64_t initfs_length;
[[noreturn]] static void with_kernel_page_tables() {
- //test initfs
+ //further initialization
set_initfs(initfs_start, initfs_length);
+ enable_interrupts();
- const uint8_t *hello_start;
- uint64_t hello_length;
- look_up_initfs_file("resx/hello.txt", &hello_start, &hello_length);
-
- if (hello_length != 7)
- die();
-
- for (int i = 0; i < 7; ++i)
- if (hello_start[i] != "Hello!\n"[i])
- die();
-
- //test framebuffer
-
- for (int y = 0; y < fb_height; ++y)
- for (int x = 0; x < fb_width; ++x) {
- fb_base[y * fb_pitch + x * 4] = y * 256 / fb_height;
- fb_base[y * fb_pitch + x * 4 + 1] = x * 256 / fb_width;
- fb_base[y * fb_pitch + x * 4 + 2] = 0;
- }
-
- die();
+ while (1)
+ __asm__ ("hlt");
}
diff --git a/kernel/src/interrupts.asm b/kernel/src/interrupts.asm
new file mode 100644
index 0000000..4740556
--- /dev/null
+++ b/kernel/src/interrupts.asm
@@ -0,0 +1,153 @@
+ ; Calcite, kernel/src/interrupts.asm
+ ; 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 .
+
+
+bits 64
+
+;both defined in interrupts.c
+extern isr_exception_c
+extern isr_irq_c
+
+section .rodata
+
+;referenced in interrupts.c
+global isrs
+isrs:
+%assign n 0
+%rep 48
+ dq isr_ %+ n
+%assign n n + 1
+%endrep
+
+section .text
+
+%assign n 0
+
+%rep 32
+isr_ %+ n:
+%if n <= 0x07 || n = 0x09 || (n >= 0x0f && n <= 0x1c && n <> 0x11 && n <> 0x15) || n = 0x1f
+ push qword 0
+%endif
+ push qword n
+ jmp isr_exception_common
+%assign n n + 1
+%endrep
+
+%rep 16
+isr_ %+ n:
+ push rdi
+ mov rdi, n - 32
+ jmp isr_irq_common
+%assign n n + 1
+%endrep
+
+isr_exception_common:
+ push rax
+ push rbx
+ push rcx
+ push rdx
+ push rdi
+ push rsi
+ push rbp
+ push r8
+ push r9
+ push r10
+ push r11
+ push r12
+ push r13
+ push r14
+ push r15
+ mov rdi, rsp
+ jmp isr_exception_c
+
+isr_irq_common:
+ push rax
+ push rcx
+ push rdx
+ push rsi
+ push r8
+ push r9
+ push r10
+ push r11
+
+ test dil, 0x08
+ jnz .high
+
+ call isr_irq_c
+ mov al, 0x20
+ out 0x20, al
+ jmp .common_end
+
+.high:
+ call isr_irq_c
+ mov al, 0x20
+ out 0xa0, al
+ out 0x20, al
+
+.common_end:
+ pop r11
+ pop r10
+ pop r9
+ pop r8
+ pop rsi
+ pop rdx
+ pop rcx
+ pop rax
+ pop rdi
+
+ iretq
+
+;referenced in interrupts.c
+;rdi is pointer to gdt descriptor
+;rsi is pointer to idt descriptor
+;dx is offset of tss into gdt
+global enable_interrupts_asm
+enable_interrupts_asm:
+
+ ;load tables
+
+ lgdt [rdi]
+ ltr dx
+ lidt [rsi]
+
+ ;initialize interrupt controllers
+
+ mov al, 0x11
+ out 0x20, al
+ mov al, 0x20
+ out 0x21, al
+ mov al, 0x04
+ out 0x21, al
+ mov al, 0x01
+ out 0x21, al
+ mov al, 0x00
+ out 0x21, al
+
+ mov al, 0x11
+ out 0xa0, al
+ mov al, 0x28
+ out 0xa1, al
+ mov al, 0x02
+ out 0xa1, al
+ mov al, 0x01
+ out 0xa1, al
+ mov al, 0x00
+ out 0xa1, al
+
+ ;enable interrupts and return
+
+ sti
+ ret
diff --git a/kernel/src/interrupts.c b/kernel/src/interrupts.c
new file mode 100644
index 0000000..28483e3
--- /dev/null
+++ b/kernel/src/interrupts.c
@@ -0,0 +1,240 @@
+/* Calcite, kernel/src/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
+#include
+
+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;
+ the_tss.ist2 = (uint64_t)&interrupt_stack_two;
+
+ //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 *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);
+
+}
diff --git a/kernel/src/paging.c b/kernel/src/paging.c
index 8450e11..f03bff4 100644
--- a/kernel/src/paging.c
+++ b/kernel/src/paging.c
@@ -16,6 +16,7 @@
*/
#include
+#include
#define MAX_PHYSICAL_GB 64ULL
@@ -102,16 +103,10 @@ void map_in_kernel_page_table(
int writable, int executable) {
uint64_t virtual_base_u64 = (uint64_t)virtual_base;
-
- //should probably die in this case
- if (virtual_base_u64 < 0xffffffffc0000000)
- return;
+ assert(virtual_base_u64 >= 0xffffffffc0000000);
uint64_t p1s_index = (virtual_base_u64 - 0xffffffffc0000000) >> 12;
-
- //should probably die in this case too
- if (kernel_p1s[p1s_index] != 0)
- return;
+ assert(kernel_p1s[p1s_index] == 0);
kernel_p1s[p1s_index] =
physical_base | (writable ? 0x3 : 0x1) |
diff --git a/kernel/src/panic.c b/kernel/src/panic.c
new file mode 100644
index 0000000..ad9f0e6
--- /dev/null
+++ b/kernel/src/panic.c
@@ -0,0 +1,30 @@
+/* Calcite, kernel/src/panic.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 .
+ */
+
+[[noreturn]] void panic_core(
+ const char *file, const char *function, int line, const char *message) {
+
+ //TODO
+
+ (void)file;
+ (void)function;
+ (void)line;
+ (void)message;
+ while (1)
+ __asm__ ("hlt");
+
+}