diff --git a/Makefile b/Makefile index c26c999..90b358f 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ build: rm -f *.o - x86_64-elf-gcc -g -c -I src 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/mem/utils.h 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 x86_64-elf-ld -o pepperk -T linker.ld *.o @@ -27,7 +27,7 @@ build-iso: limine/limine build ./limine/limine bios-install pepper.iso debug: - qemu-system-x86_64 -drive file=pepper.iso -s -S & + qemu-system-x86_64 -drive file=pepper.iso -s -S -d int -no-reboot & gdb pepperk --command=debug.gdb run: build-iso diff --git a/debug.gdb b/debug.gdb index 1042ec7..8518633 100644 --- a/debug.gdb +++ b/debug.gdb @@ -1 +1,2 @@ -target remote localhost:1234 \ No newline at end of file +target remote localhost:1234 +set disassembly-flavor intel \ No newline at end of file diff --git a/src/kmain.c b/src/kmain.c index b570eca..7a65ab6 100644 --- a/src/kmain.c +++ b/src/kmain.c @@ -4,6 +4,8 @@ #include "io/term.h" #include "io/printf.h" #include "io/serial.h" +#include "mem/gdt.h" +#include "mem/utils.h" // Limine version used __attribute__((used, section(".limine_requests"))) @@ -24,74 +26,6 @@ static volatile LIMINE_REQUESTS_END_MARKER; struct limine_framebuffer* framebuffer; -// We won't be linked to standard library, but still need the basic mem* functions -// so everything goes allright with the compiler - -// We use the "restrict" keyword on pointers so that the compiler knows it can -// do more optimization on them (and as it's a much used function, it's good to -// be able to do that) -void* memcpy(void* restrict dest, const void* restrict src, size_t n) -{ - uint8_t* restrict pdest = (uint8_t* restrict)dest; - const uint8_t* restrict psrc = (const uint8_t* restrict)src; - - for (size_t i=0; i dest) - { - for (size_t i=0; i0; i--) - { - pdest[i-1] = psrc[i-1]; - } - } - return dest; -} - -int memcmp(const void* s1, const void* s2, size_t n) -{ - const uint8_t* p1 = (const uint8_t*)s1; - const uint8_t* p2 = (const uint8_t*)s2; - - for (size_t i=0; i + +// Descriptors are 8-byte wide (64bits) +// So the selectors will be (in bytes): 0x0, 0x8, 0x10, 0x18, etc.. +uint64_t gdt_entries[NUM_GDT_ENTRIES]; +struct GDTR gdtr; + +static void gdt_load() +{ + asm("lgdt %0" : : "m"(gdtr)); +} + +static void gdt_flush() +{ + // Here, 0x8 is the kernel code selector + // and 0x10 is the kernel data selector + asm volatile ( + "mov $0x10, %%ax \n" // Reload segments with kernel data selector + "mov %%ax, %%ds \n" + "mov %%ax, %%es \n" + "mov %%ax, %%fs \n" + "mov %%ax, %%gs \n" + "mov %%ax, %%ss \n" + + "pushq $0x8 \n" // CS reload + "lea 1f(%%rip), %%rax \n" + "push %%rax \n" + "lretq \n" + "1: \n" // Execution continues here after CS reload + : + : + : "rax", "memory" + ); +} + +void gdt_init() +{ + // Null descriptor (required) + gdt_entries[0] = 0; + + // Kernel code segment + uint64_t kernel_code = 0; + kernel_code |= 0b1101 << 8; // Selector type: accessed, read-enable, no conforming + kernel_code |= 1 << 12; // not a system descriptor + kernel_code |= 0 << 13; // DPL field = 0 + kernel_code |= 1 << 15; // Present + kernel_code |= 1 << 21; // Long mode + + // Left shift 32 bits so we place our stuff in the upper 32 bits of the descriptor. + // The lower 32 bits contain limit and part of base and therefore are ignored in Long Mode + // (because we'll use paging; segmentation is used only for legacy) + gdt_entries[1] = kernel_code << 32; + + uint64_t kernel_data = 0; + kernel_data |= 0b0011 << 8; + kernel_data |= 1 << 12; + kernel_data |= 0 << 13; + kernel_data |= 1 << 15; + kernel_data |= 1 << 21; + + gdt_entries[2] = kernel_data << 32; + + // We re-use the kernel descriptors here, and just update their DPL fields + // (Descriptor privilege level) from ring 0 -> to ring 3 (userspace) + uint64_t user_code = kernel_code | (3 << 13); + gdt_entries[3] = user_code; + + uint64_t user_data = kernel_data | (3 << 13); + gdt_entries[4] = user_data; + + // The -1 subtraction is some wizardry explained in the OSDev wiki -> GDT + gdtr.limit = NUM_GDT_ENTRIES * sizeof(uint64_t) - 1; + gdtr.address = (uint64_t)gdt_entries; + + // Load the GDT we created, flush the old one + gdt_load(); + gdt_flush(); +} \ No newline at end of file diff --git a/src/mem/gdt.h b/src/mem/gdt.h new file mode 100644 index 0000000..3c15b3a --- /dev/null +++ b/src/mem/gdt.h @@ -0,0 +1,26 @@ +#ifndef GDT_H +#define GDT_H + +#include + +// We're using the GDT for segmentation, but as we want to target Long Mode, +// we'll only use this as a requirement for paging, not more. +// This means base 0 and no limit (whole address space) + +#define NUM_GDT_ENTRIES 5 + +#define NULL_SELECTOR 0x00 +#define KERNEL_CODE_SEGMENT 0x08 +#define KERNEL_DATA_SEGMENT 0x10 +#define USER_CODE_SEGMENT 0x18 +#define USER_DATA_SEGMENT 0x20 + +struct GDTR +{ + uint16_t limit; + uint64_t address; +} __attribute__((packed)); + +void gdt_init(); + +#endif \ No newline at end of file diff --git a/src/mem/utils.c b/src/mem/utils.c new file mode 100644 index 0000000..87ec369 --- /dev/null +++ b/src/mem/utils.c @@ -0,0 +1,69 @@ +#include + +// We won't be linked to standard library, but still need the basic mem* functions +// so everything goes allright with the compiler + +// We use the "restrict" keyword on pointers so that the compiler knows it can +// do more optimization on them (and as it's a much used function, it's good to +// be able to do that) +void* memcpy(void* restrict dest, const void* restrict src, size_t n) +{ + uint8_t* restrict pdest = (uint8_t* restrict)dest; + const uint8_t* restrict psrc = (const uint8_t* restrict)src; + + for (size_t i=0; i dest) + { + for (size_t i=0; i0; i--) + { + pdest[i-1] = psrc[i-1]; + } + } + return dest; +} + +int memcmp(const void* s1, const void* s2, size_t n) +{ + const uint8_t* p1 = (const uint8_t*)s1; + const uint8_t* p2 = (const uint8_t*)s2; + + for (size_t i=0; i + +void* memcpy(void* restrict dest, const void* restrict src, size_t n); +void* memset(void* s, int c, size_t n); +void* memmove(void *dest, const void* src, size_t n); +int memcmp(const void* s1, const void* s2, size_t n); + +#endif \ No newline at end of file diff --git a/src/mem/utils.h.gch b/src/mem/utils.h.gch new file mode 100644 index 0000000..576a5af Binary files /dev/null and b/src/mem/utils.h.gch differ