assertions, interrupts

This commit is contained in:
Benji Dial 2025-12-20 15:37:24 -05:00
parent e04a9b82cf
commit cb91c25253
7 changed files with 489 additions and 30 deletions

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <stdint.h>
//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)());

31
kernel/include/panic.h Normal file
View file

@ -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 <https://www.gnu.org/licenses/>.
*/
[[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

View file

@ -15,10 +15,12 @@
* this program. If not, see <https://www.gnu.org/licenses/>. * this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
#include <interrupts.h>
#include <utility.h> #include <utility.h>
#include <initfs.h> #include <initfs.h>
#include <limine.h> #include <limine.h>
#include <paging.h> #include <paging.h>
#include <panic.h>
LIMINE_BASE_REVISION(3) LIMINE_BASE_REVISION(3)
@ -177,30 +179,12 @@ static uint64_t initfs_length;
[[noreturn]] static void with_kernel_page_tables() { [[noreturn]] static void with_kernel_page_tables() {
//test initfs //further initialization
set_initfs(initfs_start, initfs_length); set_initfs(initfs_start, initfs_length);
enable_interrupts();
const uint8_t *hello_start; while (1)
uint64_t hello_length; __asm__ ("hlt");
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();
} }

153
kernel/src/interrupts.asm Normal file
View file

@ -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 <https://www.gnu.org/licenses/>.
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

240
kernel/src/interrupts.c Normal file
View file

@ -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 <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;
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);
}

View file

@ -16,6 +16,7 @@
*/ */
#include <paging.h> #include <paging.h>
#include <panic.h>
#define MAX_PHYSICAL_GB 64ULL #define MAX_PHYSICAL_GB 64ULL
@ -102,16 +103,10 @@ void map_in_kernel_page_table(
int writable, int executable) { int writable, int executable) {
uint64_t virtual_base_u64 = (uint64_t)virtual_base; uint64_t virtual_base_u64 = (uint64_t)virtual_base;
assert(virtual_base_u64 >= 0xffffffffc0000000);
//should probably die in this case
if (virtual_base_u64 < 0xffffffffc0000000)
return;
uint64_t p1s_index = (virtual_base_u64 - 0xffffffffc0000000) >> 12; uint64_t p1s_index = (virtual_base_u64 - 0xffffffffc0000000) >> 12;
assert(kernel_p1s[p1s_index] == 0);
//should probably die in this case too
if (kernel_p1s[p1s_index] != 0)
return;
kernel_p1s[p1s_index] = kernel_p1s[p1s_index] =
physical_base | (writable ? 0x3 : 0x1) | physical_base | (writable ? 0x3 : 0x1) |

30
kernel/src/panic.c Normal file
View file

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