summaryrefslogtreecommitdiff
path: root/src/kernel/arch/amd64
diff options
context:
space:
mode:
authordzwdz2022-07-16 13:33:00 +0200
committerdzwdz2022-07-16 13:33:00 +0200
commit912d2e3c7eb1baa71dda2c0a28aa5809eaa96f27 (patch)
tree4e27f3538466d5fd63a311d50916039a7a15a485 /src/kernel/arch/amd64
parent1eeb66af44ab335888410d716d604e569f20866e (diff)
amd64: barely boot into kernel code
Diffstat (limited to 'src/kernel/arch/amd64')
-rw-r--r--src/kernel/arch/amd64/32/32130
-rw-r--r--src/kernel/arch/amd64/32/boot.s86
-rw-r--r--src/kernel/arch/amd64/32/farjump.s8
-rw-r--r--src/kernel/arch/amd64/32/gdt.c118
-rw-r--r--src/kernel/arch/amd64/32/gdt.h17
-rw-r--r--src/kernel/arch/amd64/32/paging.c131
-rw-r--r--src/kernel/arch/amd64/ata.c151
-rw-r--r--src/kernel/arch/amd64/ata.h7
-rw-r--r--src/kernel/arch/amd64/boot.c51
-rw-r--r--src/kernel/arch/amd64/boot.h4
-rw-r--r--src/kernel/arch/amd64/boot64.s13
-rw-r--r--src/kernel/arch/amd64/debug.c18
-rw-r--r--src/kernel/arch/amd64/driver/fsroot.c146
-rw-r--r--src/kernel/arch/amd64/driver/fsroot.h2
-rw-r--r--src/kernel/arch/amd64/driver/ps2.c61
-rw-r--r--src/kernel/arch/amd64/driver/ps2.h5
-rw-r--r--src/kernel/arch/amd64/driver/serial.c114
-rw-r--r--src/kernel/arch/amd64/driver/serial.h10
-rw-r--r--src/kernel/arch/amd64/interrupts/idt.c71
-rw-r--r--src/kernel/arch/amd64/interrupts/idt.h3
-rw-r--r--src/kernel/arch/amd64/interrupts/irq.c33
-rw-r--r--src/kernel/arch/amd64/interrupts/irq.h6
-rw-r--r--src/kernel/arch/amd64/interrupts/isr.c48
-rw-r--r--src/kernel/arch/amd64/interrupts/isr.h8
-rw-r--r--src/kernel/arch/amd64/interrupts/isr_stub.s81
-rw-r--r--src/kernel/arch/amd64/multiboot.h29
-rw-r--r--src/kernel/arch/amd64/multiboot.s12
-rw-r--r--src/kernel/arch/amd64/pagedir.c213
-rw-r--r--src/kernel/arch/amd64/port_io.h22
-rw-r--r--src/kernel/arch/amd64/registers.h18
-rw-r--r--src/kernel/arch/amd64/sysenter.c27
-rw-r--r--src/kernel/arch/amd64/sysenter.h8
-rw-r--r--src/kernel/arch/amd64/sysenter.s93
-rw-r--r--src/kernel/arch/amd64/tty/tty.c26
-rw-r--r--src/kernel/arch/amd64/tty/tty.h7
-rw-r--r--src/kernel/arch/amd64/tty/vga.c33
36 files changed, 1810 insertions, 0 deletions
diff --git a/src/kernel/arch/amd64/32/32 b/src/kernel/arch/amd64/32/32
new file mode 100644
index 0000000..9bd97e6
--- /dev/null
+++ b/src/kernel/arch/amd64/32/32
@@ -0,0 +1,130 @@
+#include <kernel/arch/generic.h>
+#include <kernel/arch/amd64/gdt.h>
+#include <shared/mem.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+extern char _isr_mini_stack;
+
+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));
+
+__attribute__((section(".shared")))
+static struct gdt_entry GDT[SEG_end];
+__attribute__((section(".shared")))
+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(void);
+static void gdt_load(void);
+
+
+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(void) {
+ 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) &_isr_mini_stack;
+
+ 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(void) {
+ 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(void) {
+ gdt_prepare();
+ gdt_load();
+}
diff --git a/src/kernel/arch/amd64/32/boot.s b/src/kernel/arch/amd64/32/boot.s
new file mode 100644
index 0000000..eb33c28
--- /dev/null
+++ b/src/kernel/arch/amd64/32/boot.s
@@ -0,0 +1,86 @@
+.section .text
+.global _start
+.type _start, @function
+_start:
+ mov $_stack_top, %esp
+ push %ebx // save the address of the multiboot struct
+
+ mov $0x80000000, %eax // check CPUID extended functions
+ cpuid
+ cmp $0x80000001, %eax
+ jb panic_early
+
+ mov $0x80000001, %eax
+ cpuid
+ test $(1<<29), %edx // check long mode support
+ jz panic_early
+
+ mov %cr4, %eax
+ or $(1<<5), %eax // PAE
+ mov %eax, %cr4
+
+ call pml4_identity_init
+ mov $pml4_identity, %eax
+ mov %eax, %cr3
+
+ mov $0xC0000080, %ecx // EFER MSR
+ rdmsr
+ or $(1 | 1<<8 | 1<<11), %eax // sysenter | long mode | NX
+ wrmsr
+
+ mov %cr0, %eax
+ or $0x80000000, %eax
+ mov %eax, %cr0
+
+ call gdt_init
+ lgdt (lgdt_arg)
+
+ pop %edi
+
+ // TODO import gdt.h for globals
+ mov $(2<<3), %eax
+ mov %eax, %ds // SEG_r0data
+ mov %eax, %ss
+ mov %eax, %es
+ mov %eax, %fs
+ mov %eax, %gs
+
+ ljmp $(1<<3), $boot64 // SEG_r0code
+
+panic_early:
+ // output a vga Fuck
+ movl $0x4F754F46, 0xB872A
+ movl $0x4F6B4F63, 0xB872E
+ jmp cpu_halt
+
+// TODO not part of anything yet
+ call sysenter_setup
+ // TODO will fail
+ push %ebx // address of the Multiboot struct
+ call kmain_early
+
+.global cpu_shutdown
+.type cpu_shutdown, @function
+cpu_shutdown:
+/* This quits QEMU. While I couldn't find this officially documented anywhere,
+ * it is used by QEMU in tests/tcg/i386/system/boot.S (as of commit 40d6ee), so
+ * I assume that this is safe-ish to use */
+ mov $0x604, %edx
+ mov $0x2000, %eax
+ outw %ax, %dx
+
+.global cpu_halt
+.type cpu_halt, @function
+cpu_halt:
+ cli
+1: hlt
+ jmp 1b
+
+
+.global cpu_pause
+.type cpu_pause, @function
+cpu_pause:
+ sti
+ hlt
+ cli
+ ret
diff --git a/src/kernel/arch/amd64/32/farjump.s b/src/kernel/arch/amd64/32/farjump.s
new file mode 100644
index 0000000..2885d2b
--- /dev/null
+++ b/src/kernel/arch/amd64/32/farjump.s
@@ -0,0 +1,8 @@
+.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/amd64/32/gdt.c b/src/kernel/arch/amd64/32/gdt.c
new file mode 100644
index 0000000..2229330
--- /dev/null
+++ b/src/kernel/arch/amd64/32/gdt.c
@@ -0,0 +1,118 @@
+#include <kernel/arch/amd64/32/gdt.h>
+#include <kernel/arch/generic.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+extern char _isr_mini_stack;
+
+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 reserved0;
+ uint64_t rsp[3];
+ uint64_t ist[8];
+ uint64_t reserved2;
+ uint16_t reserved3;
+ uint16_t iopb;
+} __attribute__((packed));
+
+struct lgdt_arg {
+ uint16_t limit;
+ uint32_t base;
+} __attribute__((packed));
+
+__attribute__((section(".shared")))
+struct gdt_entry GDT[SEG_end];
+__attribute__((section(".shared")))
+struct tss_entry TSS;
+
+struct lgdt_arg lgdt_arg;
+
+
+static void *memset32(void *s, int c, size_t n) {
+ uint8_t *s2 = s;
+ for (size_t i = 0; i < n; i++)
+ s2[i] = c;
+ return s;
+}
+
+
+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,
+ .base_low = 0,
+ .base_high = 0,
+
+ .ring = ring,
+ .code = code,
+
+ .accessed = 0,
+ .rw = 1,
+ .conforming = 0,
+ .codeordata = 1,
+ .present = 1,
+ .long_mode = 1,
+ .available = 1,
+ .x32 = 0,
+ };
+}
+
+void gdt_init(void) {
+ 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);
+
+ lgdt_arg.limit = sizeof(GDT) - 1;
+ lgdt_arg.base = (uint64_t) &GDT;
+
+
+ memset32(&TSS, 0, sizeof(TSS));
+ for (int i = 0; i < 3; i++)
+ TSS.rsp[i] = (uint64_t) &_isr_mini_stack;
+ TSS.ist[1] = (uint64_t) &_isr_mini_stack;
+
+ uint64_t tss_addr = (uint64_t) &TSS;
+ GDT[SEG_TSS] = (struct gdt_entry) {
+ .limit_low = sizeof(TSS),
+ .limit_high = sizeof(TSS) >> 16,
+ .gran = 0,
+ .base_low = tss_addr,
+ .base_high = tss_addr >> 24,
+
+ .accessed = 1,
+ .rw = 0,
+ .conforming = 0,
+ .code = 1,
+ .codeordata = 0,
+ .ring = 0, // was 3 pre-port
+ .present = 1,
+ .available = 1,
+ .long_mode = 0,
+ .x32 = 0,
+ };
+ *((uint64_t*)&GDT[SEG_TSS2]) = (tss_addr >> 32);
+}
diff --git a/src/kernel/arch/amd64/32/gdt.h b/src/kernel/arch/amd64/32/gdt.h
new file mode 100644
index 0000000..fbaf681
--- /dev/null
+++ b/src/kernel/arch/amd64/32/gdt.h
@@ -0,0 +1,17 @@
+#pragma once
+
+enum {
+ SEG_null,
+ // order dictated by SYSENTER
+ SEG_r0code,
+ SEG_r0data,
+ SEG_r3code,
+ SEG_r3data,
+ SEG_TSS,
+ SEG_TSS2,
+
+ SEG_end
+};
+
+void gdt_init(void);
+void gdt_farjump(int segment);
diff --git a/src/kernel/arch/amd64/32/paging.c b/src/kernel/arch/amd64/32/paging.c
new file mode 100644
index 0000000..975dd98
--- /dev/null
+++ b/src/kernel/arch/amd64/32/paging.c
@@ -0,0 +1,131 @@
+#include <stddef.h>
+#include <stdint.h>
+
+struct pml4e {
+ uint64_t present : 1;
+ uint64_t writeable : 1;
+ uint64_t user : 1;
+ uint64_t writethru : 1;
+
+ uint64_t uncached : 1;
+ uint64_t accessed : 1;
+ uint64_t _unused1 : 1;
+ uint64_t reserved : 1; // always 0
+
+ uint64_t _unused2 : 3;
+ uint64_t _unused3 : 1; // HLAT thing
+
+ uint64_t address : 40;
+
+ uint64_t _unused4 : 11;
+ uint64_t noexec : 1;
+} __attribute__((packed));
+
+struct pdpte { // page directory pointer table entry, 1gb page | 512 * pde
+ uint64_t present : 1;
+ uint64_t writeable : 1;
+ uint64_t user : 1;
+ uint64_t writethru : 1;
+
+ uint64_t uncached : 1;
+ uint64_t accessed : 1;
+ uint64_t _unused1 : 1;
+ uint64_t large : 1; // 1gb page
+
+ uint64_t _unused2 : 3;
+ uint64_t _unused3 : 1; // HLAT
+
+ uint64_t address : 40;
+
+ uint64_t _unused4 : 11;
+ uint64_t noexec : 1;
+} __attribute__((packed));
+
+struct pde { // page directory entry, 2mb page | 512 * pte
+ uint64_t present : 1;
+ uint64_t writeable : 1;
+ uint64_t user : 1;
+ uint64_t writethru : 1;
+
+ uint64_t uncached : 1;
+ uint64_t accessed : 1;
+ uint64_t dirty : 1; // only if large
+ uint64_t large : 1; // 2mb
+
+ uint64_t global : 1; // only if large ; TODO enable CR4.PGE
+ uint64_t _unused2 : 2;
+ uint64_t _unused3 : 1; // HLAT
+
+ uint64_t address : 40; // highest bit - PAT
+
+ uint64_t _unused4 : 7;
+ uint64_t pke : 4;
+ uint64_t noexec : 1;
+} __attribute__((packed));
+
+struct pte { // page table entry, 4kb page
+ uint64_t present : 1;
+ uint64_t writeable : 1;
+ uint64_t user : 1;
+ uint64_t writethru : 1;
+
+ uint64_t uncached : 1;
+ uint64_t accessed : 1;
+ uint64_t dirty : 1;
+ uint64_t pat : 1;
+
+ uint64_t global : 1; // TODO enable CR4.PGE
+ uint64_t _unused2 : 2;
+ uint64_t _unused3 : 1; // HLAT
+
+ uint64_t address : 40;
+
+ uint64_t _unused4 : 7;
+ uint64_t pke : 4;
+ uint64_t noexec : 1;
+} __attribute__((packed));
+
+__attribute__((aligned(4096)))
+struct pml4e pml4_identity[512];
+
+__attribute__((aligned(4096)))
+struct pdpte pdpte_low[512]; // 0-512gb
+
+__attribute__((aligned(4096)))
+struct pde pde_low[512]; // 0-1gb
+
+
+static void *memset32(void *s, int c, size_t n) {
+ uint8_t *s2 = s;
+ for (size_t i = 0; i < n; i++)
+ s2[i] = c;
+ return s;
+}
+
+
+void pml4_identity_init(void) {
+ memset32(pml4_identity, 0, sizeof pml4_identity);
+ memset32(pdpte_low, 0, sizeof pdpte_low);
+ memset32(pde_low, 0, sizeof pde_low);
+
+ pml4_identity[0] = (struct pml4e) {
+ .present = 1,
+ .writeable = 1,
+ .address = ((uintptr_t)pdpte_low) >> 12,
+ };
+
+ pdpte_low[0] = (struct pdpte) {
+ .present = 1,
+ .writeable = 1,
+ .address = ((uintptr_t)pde_low) >> 12,
+ };
+
+ for (int i = 0; i < 512; i++) {
+ pde_low[i] = (struct pde) {
+ .present = 1,
+ .writeable = 1,
+ .large = 1,
+ .address = (i * 2 * 1024 * 1024) >> 12,
+ };
+ }
+}
diff --git a/src/kernel/arch/amd64/ata.c b/src/kernel/arch/amd64/ata.c
new file mode 100644
index 0000000..371730e
--- /dev/null
+++ b/src/kernel/arch/amd64/ata.c
@@ -0,0 +1,151 @@
+#include <kernel/arch/amd64/ata.h>
+#include <kernel/arch/amd64/port_io.h>
+#include <kernel/panic.h>
+#include <stdbool.h>
+
+static struct {
+ enum {
+ DEV_UNKNOWN,
+ DEV_PATA,
+ DEV_PATAPI,
+ } type;
+ uint32_t sectors;
+} ata_drives[4];
+
+enum {
+ DATA = 0,
+ FEAT = 1,
+ SCNT = 2,
+ LBAlo = 3,
+ LBAmid = 4,
+ LBAhi = 5,
+ DRV = 6,
+ CMD = 7,
+ STATUS = 7,
+
+ /* note: the OSDev wiki uses a different base port for the control port
+ * however i can just use this offset and stuff will just work tm */
+ CTRL = 0x206,
+}; // offsets
+
+// get I/O port base for drive
+static uint16_t ata_iobase(int drive) {
+ bool secondary = drive&2;
+ return secondary ? 0x170 : 0x1F0;
+}
+
+static void ata_400ns(void) {
+ uint16_t base = ata_iobase(0); // doesn't matter
+ for (int i = 0; i < 4; i++)
+ port_in8(base + STATUS);
+}
+
+static void ata_driveselect(int drive, int block) {
+ uint8_t v = 0xE0;
+ if (drive&1) // slave?
+ v |= 0x10; // set drive number bit
+ // TODO account for block
+ port_out8(ata_iobase(drive) + DRV, v);
+}
+
+static void ata_softreset(int drive) {
+ uint16_t iobase = ata_iobase(drive);
+ port_out8(iobase + CTRL, 4);
+ port_out8(iobase + CTRL, 0);
+ ata_400ns();
+
+ uint16_t timeout = 10000;
+ while (--timeout) { // TODO separate polling function
+ uint8_t v = port_in8(iobase + STATUS);
+ if (v & 0x80) continue; // still BSY, continue
+ if (v & 0x40) break; // RDY, break
+ // TODO check for ERR
+ }
+}
+
+static void ata_detecttype(int drive) {
+ ata_softreset(drive);
+ ata_driveselect(drive, 0);
+ ata_400ns();
+ switch (port_in8(ata_iobase(drive) + LBAmid)) {
+ case 0:
+ ata_drives[drive].type = DEV_PATA;
+ break;
+ case 0x14:
+ ata_drives[drive].type = DEV_PATAPI;
+ break;
+ default:
+ ata_drives[drive].type = DEV_UNKNOWN;
+ break;
+ }
+}
+
+static bool ata_identify(int drive) {
+ uint16_t iobase = ata_iobase(drive);
+ uint16_t data[256];
+ uint8_t v;
+
+ ata_driveselect(drive, 0);
+ for (int i = 2; i < 6; i++)
+ port_out8(iobase + i, 0);
+ switch (ata_drives[drive].type) {
+ case DEV_PATA:
+ port_out8(iobase + CMD, 0xEC); // IDENTIFY
+ break;
+ case DEV_PATAPI:
+ port_out8(iobase + CMD, 0xA1); // IDENTIFY PACKET DEVICE
+ break;
+ default: panic_invalid_state();
+ }
+
+ v = port_in8(iobase + STATUS);
+ if (v == 0) return false; // nonexistent drive
+ while (port_in8(iobase + STATUS) & 0x80);
+
+ /* pool until bit 3 (DRQ) or 0 (ERR) is set */
+ while (!((v = port_in8(iobase + STATUS) & 0x9)));
+ if (v & 1) return false; /* ERR was set, bail */
+
+ for (int i = 0; i < 256; i++)
+ data[i] = port_in16(iobase);
+ ata_drives[drive].sectors = data[60] | (data[61] << 16);
+ return true;
+}
+
+void ata_init(void) {
+ for (int i = 0; i < 4; i++) {
+ ata_detecttype(i);
+ if (ata_drives[i].type == DEV_PATA)
+ ata_identify(i);
+ }
+}
+
+bool ata_available(int drive) {
+ return ata_drives[drive].type != DEV_UNKNOWN;
+}
+
+int ata_read(int drive, uint32_t lba, void *buf) {
+ assert(ata_drives[drive].type == DEV_PATA);
+ int iobase = ata_iobase(drive);
+
+ ata_driveselect(drive, lba);
+ port_out8(iobase + FEAT, 0); // supposedly pointless
+ port_out8(iobase + SCNT, 1); // sector count
+ port_out8(iobase + LBAlo, lba);
+ port_out8(iobase + LBAmid, lba >> 8);
+ port_out8(iobase + LBAhi, lba >> 16);
+ port_out8(iobase + CMD, 0x20); // READ SECTORS
+
+ for (;;) { // TODO separate polling function
+ uint8_t v = port_in8(iobase + STATUS);
+ if (v & 0x80) continue; // still BSY, continue
+ if (v & 0x40) break; // RDY, break
+ // TODO check for ERR
+ }
+
+ uint16_t *b = buf;
+ for (int i = 0; i < 256; i++)
+ b[i] = port_in16(iobase);
+
+ return 512;
+}
diff --git a/src/kernel/arch/amd64/ata.h b/src/kernel/arch/amd64/ata.h
new file mode 100644
index 0000000..82f4f81
--- /dev/null
+++ b/src/kernel/arch/amd64/ata.h
@@ -0,0 +1,7 @@
+#pragma once
+#include <stdbool.h>
+#include <stdint.h>
+
+void ata_init(void);
+bool ata_available(int drive);
+int ata_read(int drive, uint32_t lba, void *buf);
diff --git a/src/kernel/arch/amd64/boot.c b/src/kernel/arch/amd64/boot.c
new file mode 100644
index 0000000..0e61ed3
--- /dev/null
+++ b/src/kernel/arch/amd64/boot.c
@@ -0,0 +1,51 @@
+#include <kernel/arch/generic.h>
+#include <kernel/arch/amd64/32/gdt.h>
+#include <kernel/arch/amd64/ata.h>
+#include <kernel/arch/amd64/boot.h>
+#include <kernel/arch/amd64/driver/fsroot.h>
+#include <kernel/arch/amd64/driver/ps2.h>
+#include <kernel/arch/amd64/driver/serial.h>
+#include <kernel/arch/amd64/interrupts/idt.h>
+#include <kernel/arch/amd64/interrupts/irq.h>
+#include <kernel/arch/amd64/multiboot.h>
+#include <kernel/arch/amd64/tty/tty.h>
+#include <kernel/main.h>
+#include <kernel/mem/alloc.h>
+#include <kernel/panic.h>
+
+static void find_init(struct multiboot_info *multiboot, struct kmain_info *info)
+{
+ struct multiboot_mod *module = (void*)multiboot->mods;
+ kprintf("mods count 0x%x", multiboot->mods_count);
+ if (multiboot->mods_count < 1) {
+ kprintf("can't find init! ");
+ panic_invalid_state();
+ }
+ info->init.at = module->start;
+ info->init.size = module->end - module->start;
+}
+
+void kmain_early(struct multiboot_info *multiboot) {
+ struct kmain_info info;
+
+ tty_init();
+ kprintf("idt...");
+ idt_init();
+ kprintf("irq...");
+ irq_init();
+
+ info.memtop = (void*) (multiboot->mem_upper * 1024);
+ find_init(multiboot, &info);
+ kprintf("mem...\n");
+ mem_init(&info);
+
+ kprintf("rootfs...");
+ vfs_root_init();
+ ps2_init();
+ serial_init();
+
+ kprintf("ata...");
+ ata_init();
+
+ kmain(info);
+}
diff --git a/src/kernel/arch/amd64/boot.h b/src/kernel/arch/amd64/boot.h
new file mode 100644
index 0000000..98adff5
--- /dev/null
+++ b/src/kernel/arch/amd64/boot.h
@@ -0,0 +1,4 @@
+#pragma once
+#include <kernel/arch/amd64/multiboot.h>
+
+void kmain_early(struct multiboot_info *multiboot);
diff --git a/src/kernel/arch/amd64/boot64.s b/src/kernel/arch/amd64/boot64.s
new file mode 100644
index 0000000..1dca0ca
--- /dev/null
+++ b/src/kernel/arch/amd64/boot64.s
@@ -0,0 +1,13 @@
+.global boot64
+boot64:
+ lgdt (lgdt_arg) // try reloading gdt again
+ mov $(5 << 3 | 3), %ax // SEG_TSS
+ ltr %ax
+
+ xchgw %bx, %bx
+ push %rdi // preserve multiboot struct
+ call sysenter_setup
+ pop %rdi
+
+ // multiboot struct in %rdi
+ jmp kmain_early
diff --git a/src/kernel/arch/amd64/debug.c b/src/kernel/arch/amd64/debug.c
new file mode 100644
index 0000000..52127da
--- /dev/null
+++ b/src/kernel/arch/amd64/debug.c
@@ -0,0 +1,18 @@
+#include <kernel/arch/generic.h>
+
+void *debug_caller(size_t depth) {
+ void **rbp;
+ asm("mov %%rbp, %0"
+ : "=r" (rbp));
+ while (depth--) {
+ if (!rbp) return NULL;
+ rbp = *rbp;
+ }
+ return rbp[1];
+}
+
+void debug_stacktrace(void) {
+ for (size_t i = 0; i < 16; i++) {
+ kprintf(" k/%08x\n", (uintptr_t)debug_caller(i));
+ }
+}
diff --git a/src/kernel/arch/amd64/driver/fsroot.c b/src/kernel/arch/amd64/driver/fsroot.c
new file mode 100644
index 0000000..02a4a21
--- /dev/null
+++ b/src/kernel/arch/amd64/driver/fsroot.c
@@ -0,0 +1,146 @@
+#include <kernel/arch/amd64/ata.h>
+#include <kernel/mem/virt.h>
+#include <kernel/panic.h>
+#include <kernel/proc.h>
+#include <kernel/util.h>
+#include <kernel/arch/amd64/driver/fsroot.h>
+#include <shared/mem.h>
+#include <stdbool.h>
+
+enum {
+ HANDLE_ROOT,
+ HANDLE_VGA,
+ HANDLE_ATA_ROOT,
+ HANDLE_ATA,
+ _SKIP = HANDLE_ATA + 4,
+};
+
+static bool exacteq(struct vfs_request *req, const char *str) {
+ size_t len = strlen(str);
+ assert(req->input.kern);
+ return req->input.len == len && !memcmp(req->input.buf_kern, str, len);
+}
+
+/* truncates the length */
+static void req_preprocess(struct vfs_request *req, size_t max_len) {
+ if (req->offset < 0) {
+ // TODO negative offsets
+ req->offset = 0;
+ }
+
+ if (req->offset >= capped_cast32(max_len)) {
+ req->input.len = 0;
+ req->output.len = 0;
+ req->offset = max_len;
+ return;
+ }
+
+ req->input.len = min(req->input.len, max_len - req->offset);
+ req->output.len = min(req->output.len, max_len - req->offset);
+
+ assert(req->input.len + req->offset <= max_len);
+ assert(req->input.len + req->offset <= max_len);
+}
+
+
+static int handle(struct vfs_request *req) {
+ assert(req->caller);
+ int id = (int)req->id;
+ switch (req->type) {
+ case VFSOP_OPEN:
+ if (exacteq(req, "/")) return HANDLE_ROOT;
+ if (exacteq(req, "/vga")) return HANDLE_VGA;
+
+ if (exacteq(req, "/ata/")) return HANDLE_ATA_ROOT;
+ if (exacteq(req, "/ata/0"))
+ return ata_available(0) ? HANDLE_ATA+0 : -1;
+ if (exacteq(req, "/ata/1"))
+ return ata_available(1) ? HANDLE_ATA+1 : -1;
+ if (exacteq(req, "/ata/2"))
+ return ata_available(2) ? HANDLE_ATA+2 : -1;
+ if (exacteq(req, "/ata/3"))
+ return ata_available(3) ? HANDLE_ATA+3 : -1;
+
+ return -1;
+
+ case VFSOP_READ:
+ switch (id) {
+ case HANDLE_ROOT: {
+ // TODO document directory read format
+ const char src[] =
+ "vga\0"
+ "com1\0"
+ "ps2\0"
+ "ata/";
+ req_preprocess(req, sizeof src);
+ virt_cpy_to(req->caller->pages, req->output.buf,
+ src + req->offset, req->output.len);
+ return req->output.len;
+ }
+ case HANDLE_VGA: {
+ char *vga = (void*)0xB8000;
+ req_preprocess(req, 80*25*2);
+ virt_cpy_to(req->caller->pages, req->output.buf,
+ vga + req->offset, req->output.len);
+ return req->output.len;
+ }
+ case HANDLE_ATA_ROOT: {
+ char list[8] = {};
+ size_t len = 0;
+ for (int i = 0; i < 4; i++) {
+ if (ata_available(i)) {
+ list[len] = '0' + i;
+ len += 2;
+ }
+ }
+ req_preprocess(req, len);
+ virt_cpy_to(req->caller->pages, req->output.buf,
+ list + req->offset, req->output.len);
+ return req->output.len;
+ }
+ case HANDLE_ATA: case HANDLE_ATA+1:
+ case HANDLE_ATA+2: case HANDLE_ATA+3: {
+ if (req->offset < 0) return 0;
+ char buf[512];
+ uint32_t sector = req->offset / 512;
+ size_t len = min(req->output.len, 512 - ((size_t)req->offset & 511));
+ ata_read(id - HANDLE_ATA, sector, buf);
+ virt_cpy_to(req->caller->pages, req->output.buf, buf, len);
+ return len;
+ }
+ default: panic_invalid_state();
+ }
+
+ case VFSOP_WRITE:
+ switch (id) {
+ case HANDLE_VGA: {
+ void *vga = (void*)0xB8000;
+ req_preprocess(req, 80*25*2);
+ virt_cpy_from(req->caller->pages, vga + req->offset,
+ req->input.buf, req->input.len);
+ return req->input.len;
+ }
+ default: return -1;
+ }
+
+ case VFSOP_CLOSE:
+ return 0;
+
+ default: panic_invalid_state();
+ }
+}
+
+static void accept(struct vfs_request *req) {
+ if (req->caller) {
+ vfsreq_finish_short(req, handle(req));
+ } else {
+ vfsreq_finish_short(req, -1);
+ }
+}
+
+static bool is_ready(struct vfs_backend __attribute__((unused)) *self) {
+ return true;
+}
+
+static struct vfs_backend backend = BACKEND_KERN(is_ready, accept);
+void vfs_root_init(void) { vfs_mount_root_register("", &backend); }
diff --git a/src/kernel/arch/amd64/driver/fsroot.h b/src/kernel/arch/amd64/driver/fsroot.h
new file mode 100644
index 0000000..dd72202
--- /dev/null
+++ b/src/kernel/arch/amd64/driver/fsroot.h
@@ -0,0 +1,2 @@
+#pragma once
+void vfs_root_init(void);
diff --git a/src/kernel/arch/amd64/driver/ps2.c b/src/kernel/arch/amd64/driver/ps2.c
new file mode 100644
index 0000000..8a5a078
--- /dev/null
+++ b/src/kernel/arch/amd64/driver/ps2.c
@@ -0,0 +1,61 @@
+#include <kernel/arch/amd64/driver/ps2.h>
+#include <kernel/arch/amd64/interrupts/irq.h>
+#include <kernel/mem/virt.h>
+#include <kernel/panic.h>
+#include <kernel/vfs/request.h>
+#include <shared/container/ring.h>
+#include <shared/mem.h>
+
+#define BACKLOG_CAPACITY 64
+static volatile uint8_t backlog_buf[BACKLOG_CAPACITY];
+static volatile ring_t backlog = {(void*)backlog_buf, BACKLOG_CAPACITY, 0, 0};
+
+static void accept(struct vfs_request *req);
+static bool is_ready(struct vfs_backend *self);
+
+static struct vfs_request *blocked_on = NULL;
+static struct vfs_backend backend = BACKEND_KERN(is_ready, accept);
+void ps2_init(void) { vfs_mount_root_register("/ps2", &backend); }
+
+
+void ps2_recv(uint8_t s) {
+ ring_put1b((void*)&backlog, s);
+ if (blocked_on) {
+ accept(blocked_on);
+ blocked_on = NULL;
+ vfs_backend_tryaccept(&backend);
+ }
+}
+
+static void accept(struct vfs_request *req) {
+ // when you fix something here go also fix it in the COM1 driver
+ static uint8_t buf[32]; // pretty damn stupid
+ int ret;
+ bool valid;
+ switch (req->type) {
+ case VFSOP_OPEN:
+ valid = req->input.len == 0;
+ vfsreq_finish_short(req, valid ? 0 : -1);
+ break;
+ case VFSOP_READ:
+ if (ring_size((void*)&backlog) == 0) {
+ // nothing to read
+ blocked_on = req;
+ } else if (req->caller) {
+ ret = clamp(0, req->output.len, sizeof buf);
+ ret = ring_get((void*)&backlog, buf, ret);
+ virt_cpy_to(req->caller->pages, req->output.buf, buf, ret);
+ vfsreq_finish_short(req, ret);
+ } else {
+ vfsreq_finish_short(req, -1);
+ }
+ break;
+ default:
+ vfsreq_finish_short(req, -1);
+ break;
+ }
+}
+
+static bool is_ready(struct vfs_backend __attribute__((unused)) *self) {
+ return blocked_on == NULL;
+}
diff --git a/src/kernel/arch/amd64/driver/ps2.h b/src/kernel/arch/amd64/driver/ps2.h
new file mode 100644
index 0000000..54e8fb2
--- /dev/null
+++ b/src/kernel/arch/amd64/driver/ps2.h
@@ -0,0 +1,5 @@
+#pragma once
+#include <stdint.h>
+
+void ps2_recv(uint8_t s);
+void ps2_init(void);
diff --git a/src/kernel/arch/amd64/driver/serial.c b/src/kernel/arch/amd64/driver/serial.c
new file mode 100644
index 0000000..6dda657
--- /dev/null
+++ b/src/kernel/arch/amd64/driver/serial.c
@@ -0,0 +1,114 @@
+#include <kernel/arch/amd64/driver/serial.h>
+#include <kernel/arch/amd64/interrupts/irq.h>
+#include <kernel/arch/amd64/port_io.h>
+#include <kernel/mem/virt.h>
+#include <kernel/panic.h>
+#include <shared/container/ring.h>
+#include <shared/mem.h>
+#include <stdint.h>
+
+#define BACKLOG_CAPACITY 64
+static volatile uint8_t backlog_buf[BACKLOG_CAPACITY];
+static volatile ring_t backlog = {(void*)backlog_buf, BACKLOG_CAPACITY, 0, 0};
+
+static const int COM1 = 0x3f8;
+
+static void accept(struct vfs_request *req);
+static bool is_ready(struct vfs_backend *self);
+
+static struct vfs_request *blocked_on = NULL;
+static struct vfs_backend backend = BACKEND_KERN(is_ready, accept);
+void serial_init(void) { vfs_mount_root_register("/com1", &backend); }
+
+
+static void serial_selftest(void) {
+ char b = 0x69;
+ port_out8(COM1 + 4, 0b00011110); // enable loopback mode
+ port_out8(COM1, b);
+ assert(port_in8(COM1) == b);
+}
+
+void serial_preinit(void) {
+ // see https://www.sci.muni.cz/docs/pc/serport.txt
+ // set baud rate divisor
+ port_out8(COM1 + 3, 0b10000000); // enable DLAB
+ port_out8(COM1 + 0, 0x01); // divisor = 1 (low byte)
+ port_out8(COM1 + 1, 0x00); // (high byte)
+
+ port_out8(COM1 + 3, 0b00000011); // 8 bits, no parity, one stop bit
+ port_out8(COM1 + 1, 0x01); // enable the Data Ready IRQ
+ port_out8(COM1 + 2, 0b11000111); // enable FIFO with 14-bit trigger level (???)
+
+ serial_selftest();
+
+ port_out8(COM1 + 4, 0b00001111); // enable everything in the MCR
+}
+
+
+void serial_irq(void) {
+ ring_put1b((void*)&backlog, port_in8(COM1));
+ if (blocked_on) {
+ accept(blocked_on);
+ blocked_on = NULL;
+ vfs_backend_tryaccept(&backend);
+ }
+}
+
+
+static void serial_putchar(char c) {
+ while ((port_in8(COM1 + 5) & 0x20) == 0); // wait for THRE
+ port_out8(COM1, c);
+}
+
+void serial_write(const char *buf, size_t len) {
+ for (size_t i = 0; i < len; i++)
+ serial_putchar(buf[i]);
+}
+
+
+static void accept(struct vfs_request *req) {
+ static char buf[32];
+ int ret;
+ bool valid;
+ switch (req->type) {
+ case VFSOP_OPEN:
+ valid = req->input.len == 0;
+ vfsreq_finish_short(req, valid ? 0 : -1);
+ break;
+ case VFSOP_READ:
+ if (ring_size((void*)&backlog) == 0) {
+ /* nothing to read, join queue */
+ assert(!req->postqueue_next);
+ struct vfs_request **slot = &blocked_on;
+ while (*slot)
+ slot = &(*slot)->postqueue_next;
+ *slot = req;
+ } else if (req->caller) {
+ ret = clamp(0, req->output.len, sizeof buf);
+ ret = ring_get((void*)&backlog, buf, ret);
+ virt_cpy_to(req->caller->pages, req->output.buf, buf, ret);
+ vfsreq_finish_short(req, ret);
+ } else {
+ vfsreq_finish_short(req, -1);
+ }
+ break;
+ case VFSOP_WRITE:
+ if (req->caller) {
+ struct virt_iter iter;
+ virt_iter_new(&iter, req->input.buf, req->input.len,
+ req->caller->pages, true, false);
+ while (virt_iter_next(&iter))
+ serial_write(iter.frag, iter.frag_len);
+ ret = iter.prior;
+ } else ret = -1;
+ vfsreq_finish_short(req, ret);
+ break;
+ default:
+ vfsreq_finish_short(req, -1);
+ break;
+ }
+}
+
+static bool is_ready(struct vfs_backend __attribute__((unused)) *self) {
+ return true;
+}
diff --git a/src/kernel/arch/amd64/driver/serial.h b/src/kernel/arch/amd64/driver/serial.h
new file mode 100644
index 0000000..6a4876e
--- /dev/null
+++ b/src/kernel/arch/amd64/driver/serial.h
@@ -0,0 +1,10 @@
+#pragma once
+#include <kernel/vfs/request.h>
+#include <stdbool.h>
+#include <stddef.h>
+
+void serial_preinit(void);
+void serial_irq(void);
+void serial_write(const char *buf, size_t len);
+
+void serial_init(void);
diff --git a/src/kernel/arch/amd64/interrupts/idt.c b/src/kernel/arch/amd64/interrupts/idt.c
new file mode 100644
index 0000000..9c71000
--- /dev/null
+++ b/src/kernel/arch/amd64/interrupts/idt.c
@@ -0,0 +1,71 @@
+#include <kernel/arch/amd64/32/gdt.h>
+#include <kernel/arch/amd64/interrupts/idt.h>
+#include <kernel/arch/amd64/interrupts/isr.h>
+#include <kernel/panic.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+struct idt_entry {
+ uint16_t offset_low;
+ uint16_t seg;
+ uint8_t ist;
+ uint8_t type : 4; // 0xE - interrupt, 0xF - trap
+ uint8_t zero1 : 1;
+ uint8_t ring : 2;
+ uint8_t present : 1;
+ uint16_t offset_mid;
+ uint32_t offset_high;
+ uint32_t zero2;
+} __attribute__((packed));
+
+// is exactly the same as lgdt_arg, i should combine them into a single struct
+// later
+struct lidt_arg {
+ uint16_t limit;
+ uintptr_t base;
+} __attribute__((packed));
+
+__attribute__((section(".shared")))
+static struct idt_entry IDT[256];
+static struct lidt_arg lidt_arg;
+
+static void idt_prepare(void);
+static void idt_load(void);
+static void idt_test(void);
+
+
+static void idt_prepare(void) {
+ for (int i = 0; i < 256; i++) {
+ uintptr_t offset = (uintptr_t) &_isr_stubs + i * 8;
+
+ IDT[i] = (struct idt_entry) {
+ .offset_low = offset,
+ .offset_mid = offset >> 16,
+ .offset_high = offset >> 32,
+ .seg = SEG_r0code << 3,
+ .present = 1,
+ .type = 0xE,
+ .ist = 1,
+ };
+ }
+}
+
+static void idt_load(void) {
+ lidt_arg.limit = sizeof(IDT) - 1;
+ lidt_arg.base = (uintptr_t) &IDT;
+ asm("lidt (%0)" : : "r" (&lidt_arg) : "memory");
+}
+
+static void idt_test(void) {
+ kprintf("idt test?\n");
+ asm("xchgw %%bx, %%bx" ::: "memory");
+ asm("int $0x34" : : : "memory");
+ assert(isr_test_interrupt_called);
+ kprintf("done.\n");
+}
+
+void idt_init(void) {
+ idt_prepare();
+ idt_load();
+ idt_test();
+}
diff --git a/src/kernel/arch/amd64/interrupts/idt.h b/src/kernel/arch/amd64/interrupts/idt.h
new file mode 100644
index 0000000..6576cf9
--- /dev/null
+++ b/src/kernel/arch/amd64/interrupts/idt.h
@@ -0,0 +1,3 @@
+#pragma once
+
+void idt_init(void);
diff --git a/src/kernel/arch/amd64/interrupts/irq.c b/src/kernel/arch/amd64/interrupts/irq.c
new file mode 100644
index 0000000..74bc48c
--- /dev/null
+++ b/src/kernel/arch/amd64/interrupts/irq.c
@@ -0,0 +1,33 @@
+#include <kernel/arch/amd64/interrupts/irq.h>
+#include <kernel/arch/amd64/port_io.h>
+#include <stdint.h>
+
+static const int PIC1 = 0x20;
+static const int PIC2 = 0xA0;
+
+void irq_init(void) {
+ port_out8(PIC1, 0x11); /* start init sequence */
+ port_out8(PIC2, 0x11);
+
+ port_out8(PIC1+1, 0x20); /* interrupt offsets */
+ port_out8(PIC2+1, 0x30);
+
+ port_out8(PIC1+1, 0x4); /* just look at the osdev wiki lol */
+ port_out8(PIC2+1, 0x2);
+
+ port_out8(PIC1+1, 0x1); /* 8086 mode */
+ port_out8(PIC2+1, 0x1);
+
+ uint8_t mask = 0xff;
+ mask &= ~(1 << 1); // keyboard
+ mask &= ~(1 << 4); // COM1
+
+ port_out8(PIC1+1, mask);
+ port_out8(PIC2+1, 0xff);
+}
+
+void irq_eoi(uint8_t line) {
+ port_out8(PIC1, 0x20);
+ if (line >= 8)
+ port_out8(PIC2, 0x20);
+}
diff --git a/src/kernel/arch/amd64/interrupts/irq.h b/src/kernel/arch/amd64/interrupts/irq.h
new file mode 100644
index 0000000..f523154
--- /dev/null
+++ b/src/kernel/arch/amd64/interrupts/irq.h
@@ -0,0 +1,6 @@
+#pragma once
+#include <stdbool.h>
+#include <stdint.h>
+
+void irq_init(void);
+void irq_eoi(uint8_t line);
diff --git a/src/kernel/arch/amd64/interrupts/isr.c b/src/kernel/arch/amd64/interrupts/isr.c
new file mode 100644
index 0000000..b55bc8b
--- /dev/null
+++ b/src/kernel/arch/amd64/interrupts/isr.c
@@ -0,0 +1,48 @@
+#include <kernel/arch/amd64/driver/ps2.h>
+#include <kernel/arch/amd64/driver/serial.h>
+#include <kernel/arch/amd64/interrupts/irq.h>
+#include <kernel/arch/amd64/interrupts/isr.h>
+#include <kernel/arch/amd64/port_io.h>
+#include <kernel/arch/generic.h>
+#include <kernel/panic.h>
+#include <kernel/proc.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+bool isr_test_interrupt_called = false;
+
+void isr_stage3(int interrupt, uint64_t *stackframe) {
+ if (interrupt == 0xe) stackframe++;
+ kprintf("interrupt %x, rip = k/%08x, cs 0x%x\n", interrupt, stackframe[0], stackframe[1]);
+ switch (interrupt) {
+ case 0x08: // double fault
+ kprintf("#DF");
+ panic_invalid_state();
+
+ case 0xe:
+ uint64_t addr = 0x69;
+ asm("mov %%cr2, %0" : "=r"(addr));
+ kprintf("error code 0x%x, addr 0x%x\n", stackframe[-1], addr);
+ panic_unimplemented();
+
+ case 0x34:
+ asm("nop" ::: "memory");
+ isr_test_interrupt_called = true;
+ return;
+
+ case 0x21: // keyboard irq
+ ps2_recv(port_in8(0x60));
+ irq_eoi(1);
+ return;
+
+ case 0x24: // COM1 irq
+ serial_irq();
+ irq_eoi(1);
+ return;
+
+ default:
+ // TODO check if the exception was in the kernel
+ process_kill(process_current, interrupt);
+ process_switch_any();
+ }
+}
diff --git a/src/kernel/arch/amd64/interrupts/isr.h b/src/kernel/arch/amd64/interrupts/isr.h
new file mode 100644
index 0000000..01f5236
--- /dev/null
+++ b/src/kernel/arch/amd64/interrupts/isr.h
@@ -0,0 +1,8 @@
+#pragma once
+#include <stdbool.h>
+#include <stdint.h>
+
+extern bool isr_test_interrupt_called; // used in the self-test in idt.c
+extern const char _isr_stubs;
+
+void isr_stage3(int interrupt, uint64_t *stackframe);
diff --git a/src/kernel/arch/amd64/interrupts/isr_stub.s b/src/kernel/arch/amd64/interrupts/isr_stub.s
new file mode 100644
index 0000000..e45c1c1
--- /dev/null
+++ b/src/kernel/arch/amd64/interrupts/isr_stub.s
@@ -0,0 +1,81 @@
+.section .shared
+
+.global _isr_stubs
+_isr_stubs:
+.rept 256
+ .set _stub_start, .
+
+ cli
+ call _isr_stage2
+
+ .if . - _stub_start > 8
+ .error "isr stubs over maximum size"
+ .abort
+ .endif
+ .align 8
+.endr
+
+_isr_stage2:
+ // pushal order, without %esp
+ push %rax
+ push %rcx
+ push %rdx
+ push %rbx
+ push %rbp
+ push %rsi
+ push %rdi
+ push %r8
+ push %r9
+ push %r10
+ push %r11
+ push %r12
+ push %r13
+ push %r14
+ push %r15
+
+ // convert the return address into the vector nr
+ mov 120(%rsp), %rdi
+ sub $_isr_stubs, %rdi
+ shr $3, %rdi
+
+ lea 128(%rsp), %rsi // second argument - IRET stack frame
+
+ // load kernel paging
+ mov %cr3, %rbx
+ push %rbx
+ mov $pml4_identity, %rbx
+ mov %rbx, %cr3
+
+ mov %rsp, %rbp
+ mov $_isr_big_stack, %rsp
+ call isr_stage3
+
+ mov %rbp, %rsp
+ pop %rax // restore old cr3
+ mov %rax, %cr3
+
+ // restore registers
+ pop %r15
+ pop %r14
+ pop %r13
+ pop %r12
+ pop %r11
+ pop %r10
+ pop %r9
+ pop %r8
+ pop %rdi
+ pop %rsi
+ pop %rbp
+ pop %rbx
+ pop %rdx
+ pop %rcx
+ pop %rax
+
+ add $8, %rsp // skip call's return address
+ iretq
+
+.align 8
+// TODO overflow check
+.skip 256 // seems to be enough
+.global _isr_mini_stack
+_isr_mini_stack:
diff --git a/src/kernel/arch/amd64/multiboot.h b/src/kernel/arch/amd64/multiboot.h
new file mode 100644
index 0000000..c6a2650
--- /dev/null
+++ b/src/kernel/arch/amd64/multiboot.h
@@ -0,0 +1,29 @@
+#pragma once
+#include <stdint.h>
+
+struct multiboot_mod {
+ uint32_t start;
+ uint32_t end;
+ uint32_t 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;
+
+ uint32_t cmdline;
+
+ uint32_t mods_count;
+ uint32_t mods;
+
+ // [...]
+} __attribute__((packed));
diff --git a/src/kernel/arch/amd64/multiboot.s b/src/kernel/arch/amd64/multiboot.s
new file mode 100644
index 0000000..dc19b36
--- /dev/null
+++ b/src/kernel/arch/amd64/multiboot.s
@@ -0,0 +1,12 @@
+.set MAGIC, 0x1BADB002
+
+/* 1<<0 - align modules on page boundaries. */
+.set FLAGS, 1<<0
+.set CHECKSUM, -(MAGIC + FLAGS)
+
+.section .multiboot
+.align 4
+multiboot_header:
+ .long MAGIC
+ .long FLAGS
+ .long CHECKSUM
diff --git a/src/kernel/arch/amd64/pagedir.c b/src/kernel/arch/amd64/pagedir.c
new file mode 100644
index 0000000..334cfc2
--- /dev/null
+++ b/src/kernel/arch/amd64/pagedir.c
@@ -0,0 +1,213 @@
+#include <kernel/arch/generic.h>
+#include <kernel/mem/alloc.h>
+#include <shared/mem.h>
+#include <stdint.h>
+
+/* <heat> nitpick: I highly recommend you dont use bitfields for paging
+ * structures
+ * <heat> you can't change them atomically and the way they're layed out
+ * in memory is implementation defined iirc
+ */
+struct pagetable_entry {
+ uint32_t present : 1;
+ uint32_t writeable : 1;
+ uint32_t user : 1;
+ uint32_t writethru : 1;
+ uint32_t uncached : 1;
+ uint32_t dirty : 1;
+ uint32_t always0 : 1; // memory type thing?
+ uint32_t global : 1;
+ uint32_t _unused : 3;
+ uint32_t address : 21;
+};
+
+struct pagedir_entry {
+ uint32_t present : 1;
+ uint32_t _writeable: 1; // don't use! not checked by multiple functions here
+ uint32_t _user : 1; // ^
+ uint32_t writethru : 1;
+ uint32_t uncached : 1;
+ uint32_t accessed : 1;
+ uint32_t always0 : 1;
+ uint32_t large : 1; // 4 MiB instead of 4 KiB
+ uint32_t _unused : 3;
+ uint32_t address : 21;
+} __attribute__((packed));
+
+struct pagedir {
+ struct pagedir_entry e[1024];
+} __attribute__((packed));
+
+
+struct pagedir *pagedir_new(void) {
+ struct pagedir *dir = page_alloc(1);
+ for (int i = 0; i < 1024; i++)
+ dir->e[i].present = 0;
+ return dir;
+}
+
+void pagedir_free(struct pagedir *dir) {
+ // assumes all user pages are unique and can be freed
+ struct pagetable_entry *pt;
+ void *page;
+
+ for (int i = 0; i < 1024; i++) {
+ if (!dir->e[i].present) continue;
+
+ pt = (void*)(dir->e[i].address << 11);
+
+ for (int j = 0; j < 1024; j++) {
+ if (!pt[j].present) continue;
+ if (!pt[j].user) continue;
+
+ page = (void*)(pt[j].address << 11);
+ page_free(page, 1);
+ }
+ page_free(pt, 1);
+ }
+ page_free(dir, 1);
+}
+
+static struct pagetable_entry*
+get_entry(struct pagedir *dir, const void __user *virt) {
+ uint32_t pd_idx = ((uintptr_t)virt) >> 22;
+ uint32_t pt_idx = ((uintptr_t)virt) >> 12 & 0x03FF;
+ struct pagetable_entry *pagetable;
+
+ if (!dir->e[pd_idx].present) return NULL;
+
+ pagetable = (void*)(dir->e[pd_idx].address << 11);
+ return &pagetable[pt_idx];
+}
+
+void *pagedir_unmap(struct pagedir *dir, void __user *virt) {
+ void *phys = pagedir_virt2phys(dir, virt, false, false);
+ struct pagetable_entry *page = get_entry(dir, virt);
+ page->present = false;
+ return phys;
+}
+
+void pagedir_map(struct pagedir *dir, void __user *virt, void *phys,
+ bool user, bool writeable)
+{
+ kprintf("in pagedir_map, dir 0x%x\n", dir);
+ uintptr_t virt_cast = (uintptr_t) virt;
+ uint32_t pd_idx = virt_cast >> 22;
+ uint32_t pt_idx = virt_cast >> 12 & 0x03FF;
+ struct pagetable_entry *pagetable;
+
+ kprintf("pre-if, accessing 0x%x because 0x%x\n", &dir->e[pd_idx], pd_idx);
+ if (dir->e[pd_idx].present) {
+ kprintf("present already\n");
+ pagetable = (void*) (dir->e[pd_idx].address << 11);
+ } else {
+ kprintf("allocing\n");
+ pagetable = page_alloc(1);
+ kprintf("alloc successful\n");
+ for (int i = 0; i < 1024; i++)
+ pagetable[i].present = 0;
+
+ dir->e[pd_idx] = (struct pagedir_entry) {
+ .present = 1,
+ ._writeable= 1,
+ ._user = 1,
+ .writethru = 1,
+ .uncached = 0,
+ .accessed = 0,
+ .always0 = 0,
+ .large = 0,
+ ._unused = 0,
+ .address = (uintptr_t) pagetable >> 11
+ };
+ }
+
+ pagetable[pt_idx] = (struct pagetable_entry) {
+ .present = 1,
+ .writeable = writeable,
+ .user = user,
+ .writethru = 1,
+ .uncached = 0,
+ .dirty = 0,
+ .always0 = 0,
+ .global = 0,
+ ._unused = 0,
+ .address = (uintptr_t) phys >> 11
+ };
+ kprintf("out pagedir_map\n");
+}
+
+extern void *pagedir_current;
+void pagedir_switch(struct pagedir *dir) {
+ pagedir_current = dir;
+}
+
+// creates a new pagedir with exact copies of the user pages
+struct pagedir *pagedir_copy(const struct pagedir *orig) {
+ struct pagedir *clone = page_alloc(1);
+ struct pagetable_entry *orig_pt, *clone_pt;
+ void *orig_page, *clone_page;
+
+ for (int i = 0; i < 1024; i++) {
+ clone->e[i] = orig->e[i];
+ if (!orig->e[i].present) continue;
+
+ orig_pt = (void*)(orig->e[i].address << 11);
+ clone_pt = page_alloc(1);
+ clone->e[i].address = (uintptr_t) clone_pt >> 11;
+
+ for (int j = 0; j < 1024; j++) {
+ clone_pt[j] = orig_pt[j];
+ if (!orig_pt[j].present) continue;
+ if (!orig_pt[j].user) continue;
+ // i could use .global?
+
+ orig_page = (void*)(orig_pt[j].address << 11);
+ clone_page = page_alloc(1);
+ clone_pt[j].address = (uintptr_t) clone_page >> 11;
+
+ memcpy(clone_page, orig_page, PAGE_SIZE);
+ }
+ }
+
+ return clone;
+}
+
+bool pagedir_iskern(struct pagedir *dir, const void __user *virt) {
+ struct pagetable_entry *page = get_entry(dir, virt);
+ return page && page->present && !page->user;
+}
+
+void *pagedir_virt2phys(struct pagedir *dir, const void __user *virt,
+ bool user, bool writeable)
+{
+ struct pagetable_entry *page;
+ uintptr_t phys;
+ page = get_entry(dir, virt);
+ if (!page || !page->present) return NULL;
+ if (user && !page->user) return NULL;
+ if (writeable && !page->writeable) return NULL;
+
+ phys = page->address << 11;
+ phys |= ((uintptr_t)virt) & 0xFFF;
+ return (void*)phys;
+}
+
+void __user *pagedir_findfree(struct pagedir *dir, char __user *start, size_t len) {
+ struct pagetable_entry *page;
+ char __user *iter;
+ start = (userptr_t)(((uintptr_t __force)start + PAGE_MASK) & ~PAGE_MASK); // round up to next page
+ iter = start;
+
+ while (iter < (char __user *)0xFFF00000) { // TODO better boundary
+ page = get_entry(dir, iter);
+ if (page && page->present) {
+ start = iter + PAGE_SIZE;
+ } else {
+ if ((size_t)(iter + PAGE_SIZE - start) >= len)
+ return start;
+ }
+ iter += PAGE_SIZE;
+ }
+
+ return NULL;
+}
diff --git a/src/kernel/arch/amd64/port_io.h b/src/kernel/arch/amd64/port_io.h
new file mode 100644
index 0000000..eac9331
--- /dev/null
+++ b/src/kernel/arch/amd64/port_io.h
@@ -0,0 +1,22 @@
+#include <stdint.h>
+
+static inline void port_out8(uint16_t port, uint8_t val) {
+ asm volatile("outb %0, %1" : : "a" (val), "Nd" (port));
+}
+
+static inline void port_out16(uint16_t port, uint16_t val) {
+ asm volatile("outw %0, %1" : : "a" (val), "Nd" (port));
+}
+
+static inline uint8_t port_in8(uint16_t port) {
+ uint8_t val;
+ asm volatile("inb %1, %0" : "=a" (val) : "Nd" (port));
+ return val;
+}
+
+static inline uint16_t port_in16(uint16_t port) {
+ uint16_t val;
+ asm volatile("inw %1, %0" : "=a" (val) : "Nd" (port));
+ return val;
+}
+
diff --git a/src/kernel/arch/amd64/registers.h b/src/kernel/arch/amd64/registers.h
new file mode 100644
index 0000000..e365e2c
--- /dev/null
+++ b/src/kernel/arch/amd64/registers.h
@@ -0,0 +1,18 @@
+#pragma once
+#include <shared/types.h>
+#include <stdint.h>
+
+struct registers {
+ uint64_t edi, esi;
+ userptr_t ebp, esp;
+ uint64_t ebx, edx, ecx, eax;
+
+ userptr_t eip;
+} __attribute__((__packed__));
+
+// saves a return value according to the SysV ABI
+static inline uint64_t regs_savereturn(struct registers *regs, uint64_t value) {
+ regs->eax = value;
+ regs->edx = value >> 32; // TODO check ABI
+ return value;
+}
diff --git a/src/kernel/arch/amd64/sysenter.c b/src/kernel/arch/amd64/sysenter.c
new file mode 100644
index 0000000..e42ec7d
--- /dev/null
+++ b/src/kernel/arch/amd64/sysenter.c
@@ -0,0 +1,27 @@
+#include <kernel/arch/generic.h>
+#include <kernel/arch/amd64/sysenter.h>
+#include <kernel/proc.h>
+#include <shared/syscalls.h>
+
+struct registers _sysexit_regs;
+
+void sysexit(struct registers regs) {
+ _sysexit_regs = regs;
+ _sysexit_regs.ecx = (uintptr_t) regs.esp;
+ _sysexit_regs.edx = (uintptr_t) regs.eip;
+ _sysexit_real();
+ __builtin_unreachable();
+}
+
+_Noreturn void sysenter_stage2(void) {
+ kprintf("ring0 again!\n");
+ struct registers *regs = &process_current->regs;
+
+ *regs = _sysexit_regs; // save the registers
+ regs->esp = (userptr_t) regs->ecx; // fix them up
+ regs->eip = (userptr_t) regs->edx;
+
+ _syscall(regs->eax, regs->ebx,
+ regs->esi, regs->edi, (uintptr_t)regs->ebp);
+ process_switch_any();
+}
diff --git a/src/kernel/arch/amd64/sysenter.h b/src/kernel/arch/amd64/sysenter.h
new file mode 100644
index 0000000..b88c186
--- /dev/null
+++ b/src/kernel/arch/amd64/sysenter.h
@@ -0,0 +1,8 @@
+#pragma once
+
+// sysenter.c
+extern struct registers _sysexit_regs;
+_Noreturn void sysenter_stage2(void);
+
+// sysenter.s
+void _sysexit_real(void);
diff --git a/src/kernel/arch/amd64/sysenter.s b/src/kernel/arch/amd64/sysenter.s
new file mode 100644
index 0000000..4dfe19d
--- /dev/null
+++ b/src/kernel/arch/amd64/sysenter.s
@@ -0,0 +1,93 @@
+/* TODO include gdt.h */
+.set SEG_r0code, 1
+.set SEG_r3code, 3
+.set SEG_r3data, 4
+
+.set IA32_SYSENTER_CS, 0x174
+.set IA32_SYSENTER_ESP, 0x175
+.set IA32_SYSENTER_EIP, 0x176
+
+.section .text
+.global sysenter_setup
+.type sysenter_setup, @function
+sysenter_setup:
+ xor %rdx, %rdx
+
+ mov $(SEG_r0code << 3), %rax
+ mov $IA32_SYSENTER_CS, %rcx
+ wrmsr
+
+ mov $IA32_SYSENTER_ESP, %rcx
+ mov $0, %rax // unused
+ wrmsr
+
+ mov $IA32_SYSENTER_EIP, %rcx
+ mov $sysenter_stage1, %rax
+ wrmsr
+
+ ret
+
+
+.section .shared
+
+.global stored_eax
+stored_eax:
+.long 0
+
+.global pagedir_current
+// a hack to maintain compat with the old arch api, TODO
+pagedir_current:
+.long 0
+
+.global _sysexit_real
+.type _sysexit_real, @function
+_sysexit_real:
+ xchgw %bx, %bx
+ mov $(SEG_r3data << 3 | 3), %ax
+ mov %ax, %ds
+ mov %ax, %es
+ mov %ax, %fs
+ mov %ax, %gs
+
+ // restore the registers
+ mov $_sysexit_regs, %rsp
+ pop %rdi
+ pop %rsi
+ pop %rbp
+ add $8, %rsp
+ pop %rbx
+ pop %rdx
+ pop %rcx
+ pop %rax
+
+ // enable paging
+ // %rsp used as a scratch register
+ mov (pagedir_current), %rsp
+ mov %rsp, %cr3
+ sysexit
+
+sysenter_stage1:
+ cli /* prevent random IRQs in the middle of kernel code */
+ xchgw %bx, %bx
+
+ // disable paging
+ // I don't want to damage any of the registers passed in by the user,
+ // so i'm using ESP as a temporary register. At this point there's nothing
+ // useful in it, it's == _bss_end.
+ mov %cr0, %rsp
+ and $0x7FFFFFFF, %rsp // disable paging
+ mov %rsp, %cr0
+
+ // save the registers
+ mov $(_sysexit_regs + 64), %rsp
+ push %rax
+ push %rcx
+ push %rdx
+ push %rbx
+ push $0x0 // pushal pushes %rsp here, but that's worthless
+ push %rbp
+ push %rsi
+ push %rdi
+
+ mov $_bss_end, %rsp
+ jmp sysenter_stage2
diff --git a/src/kernel/arch/amd64/tty/tty.c b/src/kernel/arch/amd64/tty/tty.c
new file mode 100644
index 0000000..6593ef6
--- /dev/null
+++ b/src/kernel/arch/amd64/tty/tty.c
@@ -0,0 +1,26 @@
+#include <kernel/arch/generic.h>
+#include <kernel/arch/amd64/driver/serial.h>
+#include <kernel/arch/amd64/tty/tty.h>
+#include <shared/printf.h>
+
+void tty_init(void) {
+ vga_clear();
+ serial_preinit();
+
+ vga_write("\x03 ", 2); // cp437 heart
+ serial_write("<3 ", 3);
+}
+
+static void backend(void __attribute__((unused)) *arg, const char *buf, size_t len) {
+ vga_write(buf, len);
+ serial_write(buf, len);
+}
+
+int kprintf(const char *fmt, ...) {
+ int ret;
+ va_list argp;
+ va_start(argp, fmt);
+ ret = __printf_internal(fmt, argp, backend, NULL);
+ va_end(argp);
+ return ret;
+}
diff --git a/src/kernel/arch/amd64/tty/tty.h b/src/kernel/arch/amd64/tty/tty.h
new file mode 100644
index 0000000..b96003d
--- /dev/null
+++ b/src/kernel/arch/amd64/tty/tty.h
@@ -0,0 +1,7 @@
+#pragma once
+#include <stddef.h>
+
+void vga_write(const char *buf, size_t len);
+void vga_clear(void);
+
+void tty_init(void);
diff --git a/src/kernel/arch/amd64/tty/vga.c b/src/kernel/arch/amd64/tty/vga.c
new file mode 100644
index 0000000..e5f2274
--- /dev/null
+++ b/src/kernel/arch/amd64/tty/vga.c
@@ -0,0 +1,33 @@
+#include <kernel/arch/amd64/tty/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 vga_scroll(void) {
+ for (size_t i = 0; i < vga_len - 80; i++)
+ vga[i] = vga[i + 80];
+ vga_pos -= 80;
+}
+
+static void vga_putchar(char c) {
+ if (vga_pos >= vga_len - 80)
+ vga_scroll();
+ vga[vga_pos++].c = c;
+}
+
+void vga_write(const char *buf, size_t len) {
+ for (size_t i = 0; i < len; i++)
+ vga_putchar(buf[i]);
+}
+
+void vga_clear(void) {
+ for (size_t i = 0; i < vga_len; i++)
+ vga[i].c = ' ';
+ vga_pos = 0;
+}