summaryrefslogtreecommitdiff
path: root/src/kernel/arch
diff options
context:
space:
mode:
Diffstat (limited to 'src/kernel/arch')
-rw-r--r--src/kernel/arch/generic.h17
-rw-r--r--src/kernel/arch/i386/boot.c33
-rw-r--r--src/kernel/arch/i386/boot.s16
-rw-r--r--src/kernel/arch/i386/gdt.h16
-rw-r--r--src/kernel/arch/i386/gdt/farjump.s9
-rw-r--r--src/kernel/arch/i386/gdt/gdt.c127
-rw-r--r--src/kernel/arch/i386/interrupts/idt.c75
-rw-r--r--src/kernel/arch/i386/interrupts/idt.h3
-rw-r--r--src/kernel/arch/i386/interrupts/isr.c24
-rw-r--r--src/kernel/arch/i386/interrupts/isr.h15
-rw-r--r--src/kernel/arch/i386/log.c6
-rw-r--r--src/kernel/arch/i386/multiboot.h31
-rw-r--r--src/kernel/arch/i386/multiboot.s19
-rw-r--r--src/kernel/arch/i386/sysenter.h2
-rw-r--r--src/kernel/arch/i386/sysenter.s31
-rw-r--r--src/kernel/arch/i386/tty.c36
-rw-r--r--src/kernel/arch/i386/tty.h6
-rw-r--r--src/kernel/arch/log.h28
18 files changed, 494 insertions, 0 deletions
diff --git a/src/kernel/arch/generic.h b/src/kernel/arch/generic.h
new file mode 100644
index 0000000..17980fd
--- /dev/null
+++ b/src/kernel/arch/generic.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#include <kernel/arch/log.h>
+
+// i have no idea where else to put it
+// some code assumes that it's a power of 2
+#define PAGE_SIZE 4096
+
+// src/arch/i386/boot.s
+extern void stack_top;
+
+__attribute__((noreturn))
+void halt_cpu();
+
+// src/arch/i386/sysenter.s
+void sysexit(void (*fun)(), void *stack_top);
+void sysenter_setup();
diff --git a/src/kernel/arch/i386/boot.c b/src/kernel/arch/i386/boot.c
new file mode 100644
index 0000000..2bb18be
--- /dev/null
+++ b/src/kernel/arch/i386/boot.c
@@ -0,0 +1,33 @@
+#include <kernel/arch/generic.h>
+#include <kernel/arch/i386/gdt.h>
+#include <kernel/arch/i386/interrupts/idt.h>
+#include <kernel/arch/i386/multiboot.h>
+#include <kernel/arch/i386/sysenter.h>
+#include <kernel/arch/i386/tty.h>
+#include <kernel/main.h>
+#include <kernel/panic.h>
+
+void kmain_early(struct multiboot_info *multiboot) {
+ struct kmain_info info;
+
+ // setup some basic stuff
+ tty_clear();
+ log_const("gdt...");
+ gdt_init();
+ log_const("idt...");
+ idt_init();
+ log_const("sysenter...");
+ sysenter_setup();
+
+ { // find the init module
+ struct multiboot_mod *module = &multiboot->mods[0];
+ if (multiboot->mods_count < 1) {
+ log_const("can't find init! ");
+ panic();
+ }
+ info.init.at = module->start;
+ info.init.size = module->end - module->start;
+ }
+
+ kmain(info);
+}
diff --git a/src/kernel/arch/i386/boot.s b/src/kernel/arch/i386/boot.s
new file mode 100644
index 0000000..74de9b7
--- /dev/null
+++ b/src/kernel/arch/i386/boot.s
@@ -0,0 +1,16 @@
+.section .text
+.global _start
+.type _start, @function
+_start:
+ mov $stack_top, %esp
+ push %ebx // address of the Multiboot struct
+ call kmain_early
+
+.global halt_cpu
+.type halt_cpu, @function
+halt_cpu:
+ cli
+1: hlt
+ jmp 1b
+
+.size _start, . - _start
diff --git a/src/kernel/arch/i386/gdt.h b/src/kernel/arch/i386/gdt.h
new file mode 100644
index 0000000..bcb9870
--- /dev/null
+++ b/src/kernel/arch/i386/gdt.h
@@ -0,0 +1,16 @@
+#pragma once
+
+enum {
+ SEG_null,
+ // order dictated by SYSENTER
+ SEG_r0code,
+ SEG_r0data,
+ SEG_r3code,
+ SEG_r3data,
+ SEG_TSS,
+
+ SEG_end
+};
+
+void gdt_init();
+void gdt_farjump(int segment);
diff --git a/src/kernel/arch/i386/gdt/farjump.s b/src/kernel/arch/i386/gdt/farjump.s
new file mode 100644
index 0000000..85d8ba5
--- /dev/null
+++ b/src/kernel/arch/i386/gdt/farjump.s
@@ -0,0 +1,9 @@
+.section .text
+.global gdt_farjump
+.type gdt_farjump, @function
+gdt_farjump:
+ /* retf pops off the return address and code segment off the stack.
+ * it turns out that in the i386 cdecl calling convention they're in
+ * the correct place already.
+ */
+ retf
diff --git a/src/kernel/arch/i386/gdt/gdt.c b/src/kernel/arch/i386/gdt/gdt.c
new file mode 100644
index 0000000..2935e6e
--- /dev/null
+++ b/src/kernel/arch/i386/gdt/gdt.c
@@ -0,0 +1,127 @@
+#include <kernel/arch/generic.h>
+#include <kernel/arch/i386/gdt.h>
+#include <kernel/util.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+
+struct gdt_entry {
+ uint64_t limit_low : 16;
+ uint64_t base_low : 24;
+ uint64_t accessed : 1; // set by the processor
+ // CODE | DATA
+ uint64_t rw : 1; // readable? | writeable?
+ uint64_t conforming : 1; // conforming? | expands down?
+ uint64_t code : 1; // 1 | 0
+
+ uint64_t codeordata : 1; // 1 for everything other than TSS and LDT
+ uint64_t ring : 2;
+ uint64_t present : 1; // always 1
+ uint64_t limit_high : 4;
+ uint64_t available : 1; // ???
+ uint64_t long_mode : 1;
+ uint64_t x32 : 1;
+ uint64_t gran : 1; // 1 - 4kb, 0 - 1b
+ uint64_t base_high : 8;
+} __attribute__((packed));
+
+struct tss_entry {
+ uint32_t _unused0;
+ uint32_t esp0; // kernel mode stack pointer
+ uint32_t ss0; // kernel mode stack segment
+ uint8_t _unused1[0x5c];
+} __attribute__((packed));
+
+struct lgdt_arg {
+ uint16_t limit;
+ uint32_t base;
+} __attribute__((packed));
+
+static struct gdt_entry GDT[SEG_end];
+static struct tss_entry TSS;
+static struct lgdt_arg lgdt_arg; // probably doesn't need to be global
+
+static void gdt_fillout(struct gdt_entry* entry, uint8_t ring, bool code);
+static void gdt_prepare();
+static void gdt_load();
+
+
+static void gdt_fillout(struct gdt_entry* entry, uint8_t ring, bool code) {
+ *entry = (struct gdt_entry) {
+ // set up the identity mapping
+ .limit_low = 0xFFFF,
+ .limit_high = 0xF,
+ .gran = 1, // 4KB * 0xFFFFF = (almost) 4GB
+ .base_low = 0,
+ .base_high = 0,
+
+ .ring = ring,
+ .code = code,
+
+ .accessed = 0,
+ .rw = 1,
+ .conforming = 0,
+ .codeordata = 1,
+ .present = 1,
+ .long_mode = 0, // ???
+ .available = 1, // ???
+ .x32 = 1,
+ };
+}
+
+static void gdt_prepare() {
+ GDT[SEG_null].present = 0;
+
+ gdt_fillout(&GDT[SEG_r0code], 0, true);
+ gdt_fillout(&GDT[SEG_r0data], 0, false);
+ gdt_fillout(&GDT[SEG_r3code], 3, true);
+ gdt_fillout(&GDT[SEG_r3data], 3, false);
+
+ // tss
+ memset(&TSS, 0, sizeof(TSS));
+ TSS.ss0 = SEG_r0data << 3; // kernel data segment
+ TSS.esp0 = (uintptr_t) &stack_top;
+
+ GDT[SEG_TSS] = (struct gdt_entry) {
+ .limit_low = sizeof(TSS),
+ .limit_high = sizeof(TSS) >> 16,
+ .gran = 0,
+ .base_low = (uintptr_t) &TSS,
+ .base_high = ((uintptr_t) &TSS) >> 24,
+
+ .accessed = 1, // 1 for TSS
+ .rw = 0, // 1 busy / 0 not busy
+ .conforming = 0, // 0 for TSS
+ .code = 1, // 32bit
+ .codeordata = 0, // is a system entry
+ .ring = 3,
+ .present = 1,
+ .available = 0, // 0 for TSS
+ .long_mode = 0,
+ .x32 = 0, // idk
+ };
+}
+
+static void gdt_load() {
+ lgdt_arg.limit = sizeof(GDT) - 1;
+ lgdt_arg.base = (uintptr_t) &GDT;
+ asm("lgdt (%0)"
+ : : "r" (&lgdt_arg) : "memory");
+
+ asm("ltr %%ax"
+ : : "a" (SEG_TSS << 3 | 3) : "memory");
+
+ // update all segment registers
+ gdt_farjump(SEG_r0code << 3);
+ asm("mov %0, %%ds;"
+ "mov %0, %%ss;"
+ "mov %0, %%es;"
+ "mov %0, %%fs;"
+ "mov %0, %%gs;"
+ : : "r" (SEG_r0data << 3) : "memory");
+}
+
+void gdt_init() {
+ gdt_prepare();
+ gdt_load();
+}
diff --git a/src/kernel/arch/i386/interrupts/idt.c b/src/kernel/arch/i386/interrupts/idt.c
new file mode 100644
index 0000000..5573928
--- /dev/null
+++ b/src/kernel/arch/i386/interrupts/idt.c
@@ -0,0 +1,75 @@
+#include <kernel/arch/i386/gdt.h>
+#include <kernel/arch/i386/interrupts/idt.h>
+#include <kernel/arch/i386/interrupts/isr.h>
+#include <kernel/panic.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+struct idt_entry {
+ uint16_t offset_low ;
+ uint16_t code_seg ;
+ uint8_t zero ; // unused, has to be 0
+ uint8_t type : 4; // 16/32 bit, task/interrupt/task gate
+ uint8_t storage : 1; // 0 for interrupt/trap gates
+ uint8_t ring : 2;
+ uint8_t present : 1;
+ uint16_t offset_high ;
+} __attribute__((packed));
+
+// is exactly the same as lgdt_arg, i should combine them into a single struct
+// later
+struct lidt_arg {
+ uint16_t limit;
+ uint32_t base;
+} __attribute__((packed));
+
+
+static struct idt_entry IDT[256];
+static struct lidt_arg lidt_arg;
+
+static inline void idt_add(uint8_t num, bool user, void (*isr));
+static void idt_prepare();
+static void idt_load();
+static void idt_test();
+
+
+static inline void idt_add(uint8_t num, bool user, void (*isr)) {
+ uintptr_t offset = (uintptr_t) isr;
+
+ IDT[num] = (struct idt_entry) {
+ .offset_low = offset,
+ .offset_high = offset >> 16,
+ .code_seg = SEG_r0code << 3,
+ .zero = 0,
+ .present = 1,
+ .ring = user ? 3 : 0,
+ .storage = 0,
+ .type = 0xE, // 32-bit interrupt gate
+ };
+}
+
+static void idt_prepare() {
+ for (int i = 0; i < 256; i++)
+ IDT[i].present = 0;
+
+ idt_add(0x08, false, isr_double_fault);
+ idt_add(0x0d, false, isr_general_protection_fault);
+ idt_add(0x34, false, isr_test_interrupt);
+}
+
+static void idt_load() {
+ lidt_arg.limit = sizeof(IDT) - 1;
+ lidt_arg.base = (uintptr_t) &IDT;
+ asm("lidt (%0)" : : "r" (&lidt_arg) : "memory");
+}
+
+static void idt_test() {
+ asm("int $0x34" : : : "memory");
+ if (!isr_test_interrupt_called) panic();
+}
+
+void idt_init() {
+ idt_prepare();
+ idt_load();
+ idt_test();
+}
diff --git a/src/kernel/arch/i386/interrupts/idt.h b/src/kernel/arch/i386/interrupts/idt.h
new file mode 100644
index 0000000..5627657
--- /dev/null
+++ b/src/kernel/arch/i386/interrupts/idt.h
@@ -0,0 +1,3 @@
+#pragma once
+
+void idt_init();
diff --git a/src/kernel/arch/i386/interrupts/isr.c b/src/kernel/arch/i386/interrupts/isr.c
new file mode 100644
index 0000000..d2e3d0f
--- /dev/null
+++ b/src/kernel/arch/i386/interrupts/isr.c
@@ -0,0 +1,24 @@
+#include <kernel/arch/i386/interrupts/isr.h>
+#include <kernel/arch/log.h>
+#include <kernel/panic.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+bool isr_test_interrupt_called = false;
+
+__attribute__((interrupt))
+void isr_double_fault(struct interrupt_frame *frame) {
+ log_const("#DF");
+ panic();
+}
+
+__attribute__((interrupt))
+void isr_general_protection_fault(struct interrupt_frame *frame) {
+ log_const("#GP");
+ panic();
+}
+
+__attribute__((interrupt))
+void isr_test_interrupt(struct interrupt_frame *frame) {
+ isr_test_interrupt_called = true;
+}
diff --git a/src/kernel/arch/i386/interrupts/isr.h b/src/kernel/arch/i386/interrupts/isr.h
new file mode 100644
index 0000000..150fc46
--- /dev/null
+++ b/src/kernel/arch/i386/interrupts/isr.h
@@ -0,0 +1,15 @@
+#pragma once
+#include <stdbool.h>
+
+struct interrupt_frame;
+
+extern bool isr_test_interrupt_called; // used in the self-test in idt.c
+
+__attribute__((interrupt))
+void isr_double_fault(struct interrupt_frame *frame);
+
+__attribute__((interrupt))
+void isr_general_protection_fault(struct interrupt_frame *frame);
+
+__attribute__((interrupt))
+void isr_test_interrupt(struct interrupt_frame *frame);
diff --git a/src/kernel/arch/i386/log.c b/src/kernel/arch/i386/log.c
new file mode 100644
index 0000000..84f8b04
--- /dev/null
+++ b/src/kernel/arch/i386/log.c
@@ -0,0 +1,6 @@
+#include <kernel/arch/i386/tty.h>
+#include <kernel/arch/log.h>
+
+void log_write(const char *buf, size_t len) {
+ tty_write(buf, len);
+}
diff --git a/src/kernel/arch/i386/multiboot.h b/src/kernel/arch/i386/multiboot.h
new file mode 100644
index 0000000..f030247
--- /dev/null
+++ b/src/kernel/arch/i386/multiboot.h
@@ -0,0 +1,31 @@
+#pragma once
+#include <stdint.h>
+
+// TODO assert that pointers have 4 bytes.
+
+struct multiboot_mod {
+ void *start;
+ void *end;
+ const char *str;
+ uint32_t _reserved;
+} __attribute__((packed));
+
+struct multiboot_info {
+ uint32_t flag_mem : 1;
+ uint32_t flag_boot_device : 1;
+ uint32_t flag_cmdline : 1;
+ uint32_t flag_mods : 1;
+ uint32_t _flag_other : 28; // unimplemented
+
+ uint32_t mem_lower;
+ uint32_t mem_upper;
+
+ uint32_t boot_device;
+
+ const char *cmdline;
+
+ uint32_t mods_count;
+ struct multiboot_mod *mods;
+
+ // [...]
+} __attribute__((packed));
diff --git a/src/kernel/arch/i386/multiboot.s b/src/kernel/arch/i386/multiboot.s
new file mode 100644
index 0000000..db502cc
--- /dev/null
+++ b/src/kernel/arch/i386/multiboot.s
@@ -0,0 +1,19 @@
+.set MAGIC, 0x1BADB002
+/* TODO set bss_end_addr, so the init module doesn't get overriden by the stack */
+
+/* 1<<0 - align modules on page boundaries.
+ 1<<16 - enable manual addressing */
+.set FLAGS, 1<<0 | 1<<16
+.set CHECKSUM, -(MAGIC + FLAGS)
+
+.section .multiboot
+.align 4
+multiboot_header:
+ .long MAGIC
+ .long FLAGS
+ .long CHECKSUM
+ .long multiboot_header // header_addr
+ .long multiboot_header // load_addr
+ .long _data_end // load_end_addr
+ .long _bss_end // bss_end_addr
+ .long _start // entry_addr
diff --git a/src/kernel/arch/i386/sysenter.h b/src/kernel/arch/i386/sysenter.h
new file mode 100644
index 0000000..b531fe8
--- /dev/null
+++ b/src/kernel/arch/i386/sysenter.h
@@ -0,0 +1,2 @@
+#pragma once
+void sysenter_setup();
diff --git a/src/kernel/arch/i386/sysenter.s b/src/kernel/arch/i386/sysenter.s
new file mode 100644
index 0000000..666c0f0
--- /dev/null
+++ b/src/kernel/arch/i386/sysenter.s
@@ -0,0 +1,31 @@
+/* arch/i386/gdt.c */
+.set SEG_r0code, 1
+.set SEG_r3code, 3
+.set SEG_r3data, 4
+
+.set IA32_SYSENTER_CS, 0x174
+
+.section .text
+.global sysexit
+.type sysexit, @function
+sysexit:
+ pop %ecx
+ pop %edx
+
+ mov $(SEG_r3data << 3 | 3), %ax
+ mov %ax, %ds
+ mov %ax, %es
+ mov %ax, %fs
+ mov %ax, %gs
+
+ sysexit
+
+
+.global sysenter_setup
+.type sysenter_setup, @function
+sysenter_setup:
+ xor %edx, %edx
+ mov $(SEG_r0code << 3), %eax
+ mov $IA32_SYSENTER_CS, %ecx
+ wrmsr
+ ret
diff --git a/src/kernel/arch/i386/tty.c b/src/kernel/arch/i386/tty.c
new file mode 100644
index 0000000..b9ebdb6
--- /dev/null
+++ b/src/kernel/arch/i386/tty.c
@@ -0,0 +1,36 @@
+#include <kernel/arch/i386/tty.h>
+
+struct vga_cell {
+ unsigned char c;
+ unsigned char style;
+} __attribute__((__packed__));
+
+static const size_t vga_len = 80 * 25;
+static struct vga_cell *vga = (void*) 0xB8000;
+static size_t vga_pos = 0;
+
+static void tty_scroll() {
+ for (int i = 0; i < vga_len - 80; i++) {
+ vga[i] = vga[i + 80];
+ }
+ vga_pos -= 80;
+}
+
+void tty_putchar(char c)
+{
+ if (vga_pos >= vga_len - 80) tty_scroll();
+ vga[vga_pos++].c = c;
+}
+
+void tty_write(const char *buf, size_t len)
+{
+ for (size_t i = 0; i < len; i++) {
+ tty_putchar(buf[i]);
+ }
+}
+
+void tty_clear() {
+ for (size_t i = 0; i < vga_len; i++)
+ vga[i].c = ' ';
+ vga_pos = 0;
+}
diff --git a/src/kernel/arch/i386/tty.h b/src/kernel/arch/i386/tty.h
new file mode 100644
index 0000000..cbb8efc
--- /dev/null
+++ b/src/kernel/arch/i386/tty.h
@@ -0,0 +1,6 @@
+#pragma once
+#include <stddef.h>
+
+void tty_putchar(char c);
+void tty_write(const char *buf, size_t len);
+void tty_clear();
diff --git a/src/kernel/arch/log.h b/src/kernel/arch/log.h
new file mode 100644
index 0000000..0a5d5a0
--- /dev/null
+++ b/src/kernel/arch/log.h
@@ -0,0 +1,28 @@
+#pragma once
+#include <stddef.h>
+
+void log_write(const char *buf, size_t len);
+
+inline void log_hex(const char *buf, size_t len) {
+ char hex[2];
+ for (size_t i = 0; i < len; i++) {
+ hex[0] = (buf[i] & 0xF0) >> 4;
+ hex[0] += '0';
+ if (hex[0] > '9')
+ hex[0] += 'a' - '9' - 1;
+
+ hex[1] = buf[i] & 0xF;
+ hex[1] += '0';
+ if (hex[1] > '9')
+ hex[1] += 'a' - '9' - 1;
+
+ log_write(hex, 2);
+ }
+}
+
+// used for static strings
+#define log_const(str) log_write(str, sizeof(str) - 1)
+
+// very hacky, shouldn't be actually used - only for debugging
+// prints backwards
+#define log_var_dont_use(var) log_hex((void*)&var, sizeof(var))