diff --git a/Makefile b/Makefile index dd629d7..472d432 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,8 @@ build: rm -f *.o - x86_64-elf-gcc -g -c -I src src/mem/utils.c src/mem/gdt.c src/io/serial.c src/io/term.c src/io/printf.c src/kmain.c -Wall -Wextra -std=gnu99 -nostdlib -ffreestanding -fno-stack-protector -fno-stack-check -fno-PIC -ffunction-sections -fdata-sections -mcmodel=kernel + x86_64-elf-gcc -g -c -I src src/idt/idt.c src/mem/utils.c src/mem/gdt.c src/io/serial.c src/io/term.c src/io/printf.c src/kmain.c -Wall -Wextra -std=gnu99 -nostdlib -ffreestanding -fno-stack-protector -fno-stack-check -fno-PIC -ffunction-sections -fdata-sections -mcmodel=kernel objcopy -O elf64-x86-64 -B i386 -I binary zap-light16.psf zap-light16.o + nasm -f elf64 src/idt/idt.S -o idt_stub.o x86_64-elf-ld -o pepperk -T linker.ld *.o limine/limine: diff --git a/src/idt/idt.S b/src/idt/idt.S new file mode 100644 index 0000000..60be323 --- /dev/null +++ b/src/idt/idt.S @@ -0,0 +1,234 @@ +; Assembly stub for the IDT + +bits 64 + +extern interrupt_dispatch + +global interrupt_stub +global vector_0_handler +global vector_1_handler +global vector_2_handler +global vector_3_handler +global vector_4_handler +global vector_5_handler +global vector_6_handler +global vector_7_handler +global vector_8_handler +global vector_9_handler +global vector_10_handler +global vector_11_handler +global vector_12_handler +global vector_13_handler +global vector_14_handler +global vector_15_handler +global vector_16_handler +global vector_17_handler +global vector_18_handler +global vector_19_handler +global vector_20_handler +global vector_21_handler + +interrupt_stub: + ; We'll push all general-purpose registers to the stack, + ; so they're intact and don't bother the code that was + ; executed when the interrupt happened. + ; (except rsp because it will already be saved in the iret frame) + + push rax + push rbx + push rcx + push rdx + push rsi + push rdi + push rbp + push r8 + push r9 + push r10 + push r11 + push r12 + push r13 + push r14 + push r15 + + ; Put stack pointer as first argument of our function + mov rdi, rsp + call interrupt_dispatch + ; What the function returns (new stack pointer) is saved in rbp + mov rsp, rax + + pop r15 + pop r14 + pop r13 + pop r12 + pop r11 + pop r10 + pop r9 + pop r8 + pop rbp + pop rdi + pop rsi + pop rdx + pop rcx + pop rbx + pop rax + + ; Removing the error code and vector number so stack doesn't + ; get corrupted + add rsp, 16 + + ; Restore ss, rsp, rflags, cs, rip of code that was executing + ; before the interrupt + iret + +; Vector handlers will be 16-byte aligned so that we can loop over them +; like * 16 to get each one's address + +; Divide Error +align 16 +vector_0_handler: + ; error code (nothing, so we push a dummy 0 quadword, 64bits/8bytes long) + push qword 0 + ; vector number (so our interrupt stub knows which one it is) + push qword 0 + jmp interrupt_stub + +; Debug Exception +align 16 +vector_1_handler: + push qword 0 + push qword 1 + jmp interrupt_stub + +; NMI +align 16 +vector_2_handler: + push qword 0 + push qword 2 + jmp interrupt_stub + +; Breakpoint +align 16 +vector_3_handler: + push qword 0 + push qword 3 + jmp interrupt_stub + +; Overflow +align 16 +vector_4_handler: + push qword 0 + push qword 4 + jmp interrupt_stub + +; BOUND Range exceeded +align 16 +vector_5_handler: + push qword 0 + push qword 5 + jmp interrupt_stub + +; Invalid Opcode +align 16 +vector_6_handler: + push qword 0 + push qword 6 + jmp interrupt_stub + +; Device Not Available +align 16 +vector_7_handler: + push qword 0 + push qword 7 + jmp interrupt_stub + +; Double Fault +align 16 +vector_8_handler: + ; No error code, we only push vector number + push qword 1 + jmp interrupt_stub + +; Coprocessor Segment Overrun +align 16 +vector_9_handler: + push qword 9 + jmp interrupt_stub + +; Invalid TSS +align 16 +vector_10_handler: + push qword 10 + jmp interrupt_stub + +; Segment Not Present +align 16 +vector_11_handler: + push qword 11 + jmp interrupt_stub + +; Stack-Segment Fault +align 16 +vector_12_handler: + push qword 12 + jmp interrupt_stub + +; General Protection +align 16 +vector_13_handler: + push qword 13 + jmp interrupt_stub + +; Page Fault +align 16 +vector_14_handler: + push qword 14 + jmp interrupt_stub + +; Intel reserved +align 16 +vector_15_handler: + push qword 0 + push qword 15 + jmp interrupt_stub + +; x87 FPU Floating-Point Error +align 16 +vector_16_handler: + push qword 0 + push qword 16 + jmp interrupt_stub + +; Alignment Check +align 16 +vector_17_handler: + push qword 17 + jmp interrupt_stub + +; Machine Check +align 16 +vector_18_handler: + push qword 0 + push qword 18 + jmp interrupt_stub + +; SIMD Floating-Point Exception +align 16 +vector_19_handler: + push qword 0 + push qword 19 + jmp interrupt_stub + +; Virtualization Exception +align 16 +vector_20_handler: + push qword 0 + push qword 20 + jmp interrupt_stub + +; Control Protection Exception +align 16 +vector_21_handler: + push qword 21 + jmp interrupt_stub + +; The others are reserved (22->31) or external (32->255) interrupts \ No newline at end of file diff --git a/src/idt/idt.c b/src/idt/idt.c new file mode 100644 index 0000000..21237b5 --- /dev/null +++ b/src/idt/idt.c @@ -0,0 +1,127 @@ +#include "idt.h" +#include +#include +#include "../io/serial.h" + +struct interrupt_descriptor idt[256]; +struct idtr idt_reg; + +// Address to our first interrupt handler +extern char vector_0_handler[]; + +void idt_set_entry(uint8_t vector, void* handler, uint8_t dpl) +{ + uint64_t handler_addr = (uint64_t)handler; + + struct interrupt_descriptor* entry = &idt[vector]; + // Address is split in three parts so we right-shift progressively to get it all + entry->address_low = handler_addr & 0xFFFF; + entry->address_mid = (handler_addr >> 16) & 0xFFFF; + entry->address_high = handler_addr >> 32; + + // Kernel code selector (as set in GDT) + entry->selector = 0x8; + // Interrupt gate, present, DPL (having: max DPL = 3) + entry->flags = 0b1110 | ((dpl & 0b11) << 5) | (1 << 7); + // We won't use IST for now + entry->ist = 0; +} + +void idt_load(void* idt_addr) +{ + // "limit" = "size" = Size of the IDT - 1 byte = (16*256)-1 = 0xFFF + idt_reg.limit = 0xFFF; + idt_reg.base = (uint64_t)idt_addr; + asm volatile("lidt %0" :: "m"(idt_reg)); +} + +void idt_init() +{ + // We set 256 entries, but we have only the first few stubs. + // Undefined behavior? + for (size_t i=0; i<256; i++) + { + // Each vector handler is 16-byte aligned, so *16 = address of that handler + idt_set_entry(i, vector_0_handler + (i*16), 0); + } +} + +struct cpu_status_t* interrupt_dispatch(struct cpu_status_t* context) +{ + switch(context->vector_number) + { + // TODO: add serial logs for all interrupts + case 0: + serial_kputs("kernel: idt: Divide Error!\n"); + break; + case 1: + serial_kputs("kernel: idt: Debug Exception!\n"); + break; + case 2: + serial_kputs("kernel: idt: NMI Interrupt!\n"); + break; + case 3: + serial_kputs("kernel: idt: Breakpoint Interrupt!\n"); + break; + case 4: + serial_kputs("kernel: idt: Overflow Trap!\n"); + break; + case 5: + serial_kputs("kernel: idt: BOUND Range Exceeded!\n"); + break; + case 6: + serial_kputs("kernel: idt: Invalid Opcode!\n"); + break; + case 7: + serial_kputs("kernel: idt: Device Not Available!\n"); + break; + case 8: + serial_kputs("kernel: idt: Double Fault!\n"); + break; + case 9: + serial_kputs("kernel: idt: Coprocessor Segment Overrun!\n"); + break; + case 10: + serial_kputs("kernel: idt: Invalid TSS!\n"); + break; + case 11: + serial_kputs("kernel: idt: Segment Not Present!\n"); + break; + case 12: + serial_kputs("kernel: idt: Stack-Segment Fault!\n"); + break; + case 13: + serial_kputs("kernel: idt: General Protection Fault!\n"); + break; + case 14: + serial_kputs("kernel: idt: Page Fault!\n"); + break; + case 15: + serial_kputs("kernel: idt: Intel Reserved Interrupt! (Achievement unlocked: How Did We Get Here?)\n"); + break; + case 16: + serial_kputs("kernel: idt: x87 Floating-Point Error!\n"); + break; + case 17: + serial_kputs("kernel: idt: Alignment Check Fault!\n"); + break; + case 18: + serial_kputs("kernel: idt: Machine Check!\n"); + break; + case 19: + serial_kputs("kernel: idt: SIMD Floating-Point Exception!\n"); + break; + case 20: + serial_kputs("kernel: idt: Virtualization Exception!\n"); + break; + case 21: + serial_kputs("kernel: idt: Control Protection Exception!\n"); + break; + + default: + serial_kputs("kernel: idt: Unexpected interrupt\n"); + break; + } + + return context; +} \ No newline at end of file diff --git a/src/idt/idt.h b/src/idt/idt.h new file mode 100644 index 0000000..05ae631 --- /dev/null +++ b/src/idt/idt.h @@ -0,0 +1,54 @@ +#ifndef IDT_H +#define IDT_H + +#include + +struct interrupt_descriptor +{ + uint16_t address_low; + uint16_t selector; + uint8_t ist; + uint8_t flags; + uint16_t address_mid; + uint32_t address_high; + uint32_t reserved; +} __attribute__((packed)); + +struct idtr +{ + uint16_t limit; + uint64_t base; +} __attribute__((packed)); + +// All general-purpose registers (except rsp) as stored on the stack, +// plus the values we pushed (vector number, error code) and the iret frame +// In reverse order because the stack grows downwards. +struct cpu_status_t +{ + 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 rdi; + uint64_t rsi; + uint64_t rdx; + uint64_t rcx; + uint64_t rbx; + uint64_t rax; + + uint64_t vector_number; + uint64_t error_code; + + uint64_t iret_rip; + uint64_t iret_cs; + uint64_t iret_flags; + uint64_t iret_rsp; + uint64_t iret_ss; +}; + +#endif \ No newline at end of file diff --git a/src/mem/utils.c b/src/mem/utils.c index 87ec369..a27c898 100644 --- a/src/mem/utils.c +++ b/src/mem/utils.c @@ -1,4 +1,5 @@ #include +#include // We won't be linked to standard library, but still need the basic mem* functions // so everything goes allright with the compiler