Move kernel sources to platform specific subdirs
diff --git a/src/kernel/x86_32/.gdbinit b/src/kernel/x86_32/.gdbinit
new file mode 100644
index 0000000..54e4e4c
--- /dev/null
+++ b/src/kernel/x86_32/.gdbinit
@@ -0,0 +1,2 @@
+file kernel.elf
+target remote localhost:1234
diff --git a/src/kernel/x86_32/alloc.c b/src/kernel/x86_32/alloc.c
new file mode 100644
index 0000000..9c08098
--- /dev/null
+++ b/src/kernel/x86_32/alloc.c
@@ -0,0 +1,302 @@
+#include "alloc.h"
+#include "io.h"
+#include "kheap.h"
+#include "kint.h"
+#include "log.h"
+#include "paging.h"
+
+extern uint end;
+static size_t palloc_base = (size_t)&end;
+static size_t malloc_base = (size_t)&end + 0x8000;
+
+#define HEADER_SIZE sizeof(struct heap_alloc_header)
+#define FOOTER_SIZE sizeof(struct heap_alloc_footer)
+
+static struct min_heap heap = {0};
+
+void *_kmalloc(size_t size, bool align, void **phys)
+{
+	if (align && (palloc_base & 0xfff)) // if not yet aligned
+	{
+		palloc_base &= ~0xfff;
+		palloc_base += 0x1000;
+	}
+
+	if (phys)
+	{
+		*phys = (void *)VIRT_TO_PHYS((void *)palloc_base);
+	}
+
+	size_t addr = palloc_base;
+	palloc_base += size;
+
+	if (palloc_base >= malloc_base)
+	{
+		kpanic("fatal error: placeholder kmalloc has overrun malloc() memory,"
+			   " cannot recover.");
+	}
+
+	return (void *)addr;
+}
+
+void *kmalloc(size_t size)
+{
+	return _kmalloc(size, false, NULL);
+}
+
+void *kmalloc_a(size_t size)
+{
+	return _kmalloc(size, true, NULL);
+}
+
+void *kmalloc_ap(size_t size, void **p)
+{
+	return _kmalloc(size, true, p);
+}
+
+// Proper allocators
+
+void init_allocator()
+{
+	heap.size = 1;
+	int size = 0xC0400000 - malloc_base;
+	heap.elements[0] = (struct heap_entry){
+		.key = size,
+		.address = malloc_base,
+	};
+	memset((void *)malloc_base, 0, size);
+
+	struct heap_alloc_header *h = (struct heap_alloc_header *)malloc_base;
+	h->magic = HEAP_MAGIC;
+	h->size = size;
+	h->allocated = false;
+}
+
+void *malloc(size_t size)
+{
+	bool ok;
+	size_t full_size = size + HEADER_SIZE + FOOTER_SIZE;
+	int i;
+
+	struct heap_entry e = heap_lookup_min(&heap, full_size, &ok, false, &i);
+
+	if (ok)
+	{
+		// Found smallest hole
+		struct heap_alloc_header *h = (struct heap_alloc_header *)e.address;
+
+		kassert(!h->allocated,
+				"Gap already allocated (this should never happen)");
+
+		size_t old_size = h->size;
+
+		if (full_size == old_size)
+		{
+			// Completely used, no need to change anything!
+			heap_delete(&heap, i);
+		}
+		else
+		{
+			// If there isn't very much space left
+			size_t new_size = old_size - full_size;
+			if (new_size <= HEADER_SIZE + FOOTER_SIZE + 8)
+			{
+				full_size = old_size;
+				heap_delete(&heap, i);
+			}
+			else
+			{
+				struct heap_alloc_footer *old_f =
+					(struct heap_alloc_footer *)(e.address + old_size -
+												 FOOTER_SIZE);
+
+				// Else create a new header
+				size_t new_header_addr = e.address + full_size;
+				struct heap_alloc_header *h =
+					(struct heap_alloc_header *)new_header_addr;
+
+				h->size = new_size;
+				old_f->size = new_size;
+
+				heap_decrease(&heap, i,
+							  (struct heap_entry){
+								  .key = new_size,
+								  .address = new_header_addr,
+							  });
+			}
+
+			struct heap_alloc_footer *f =
+				(struct heap_alloc_footer *)(e.address + full_size -
+											 FOOTER_SIZE);
+
+			h->allocated = true;
+			h->magic = HEAP_MAGIC;
+			h->size = full_size;
+			f->size = h->size;
+		}
+
+		return (void *)(e.address + HEADER_SIZE);
+	}
+	else
+	{
+		// We need more memory :L
+		kpanic("Whoops, malloc ran out of memory");
+	}
+}
+
+void free(void *mem)
+{
+	if (!mem)
+		return; // freeing NULL ptr
+
+	struct heap_alloc_header *base =
+		(struct heap_alloc_header *)((size_t)mem - HEADER_SIZE);
+
+	if (base->magic != HEAP_MAGIC)
+	{
+		kpanic("Freeing memory not allocated with malloc()");
+	}
+
+	// Check free block before this one
+
+	struct heap_alloc_footer *prev_f =
+		(struct heap_alloc_footer *)((size_t)mem - HEADER_SIZE - FOOTER_SIZE);
+
+	// Header of block before this one
+	struct heap_alloc_header *prev_h =
+		(struct heap_alloc_header *)((size_t)prev_f - prev_f->size +
+									 FOOTER_SIZE);
+
+	// Header of block after this one
+	struct heap_alloc_header *next_h =
+		(struct heap_alloc_header *)((size_t)mem - HEADER_SIZE + base->size);
+
+	size_t size = base->size;
+	size_t start = (size_t)base;
+
+	if (prev_h->magic == HEAP_MAGIC && !prev_h->allocated)
+	{
+		size += prev_h->size;
+		start = (size_t)prev_h;
+	}
+	if (next_h->magic == HEAP_MAGIC && !next_h->allocated)
+	{
+		size += next_h->size;
+	}
+
+	struct heap_alloc_header *h = (struct heap_alloc_header *)start;
+	h->allocated = false;
+	h->magic = HEAP_MAGIC;
+	h->size = size;
+
+	// Add entry into heap
+
+	struct heap_entry entry = {
+		.key = size,
+		.address = start,
+	};
+
+	heap_insert(&heap, entry);
+}
+
+void *realloc(void *mem, size_t size)
+{
+	if (!mem)
+		return NULL; // freeing NULL ptr
+
+	struct heap_alloc_header *base =
+		(struct heap_alloc_header *)((size_t)mem - HEADER_SIZE);
+	struct heap_alloc_header *next =
+		(struct heap_alloc_header *)((size_t)base + base->size);
+
+	if (!next->allocated &&
+		next->size + base->size - HEADER_SIZE - FOOTER_SIZE >= size)
+	{
+		// Okay, we can just expand this block
+		// Actually, need to check if there is enough space remaining for an
+		// additional block, otherwise, just add that memory to this block (
+		// same as is done in malloc )
+
+		struct heap_alloc_footer *f;
+
+		size_t remaining =
+			base->size + next->size - size - HEADER_SIZE - FOOTER_SIZE;
+
+		struct heap_entry old_entry = {
+			.key = next->size,
+			.address = (size_t)next,
+		};
+
+		if (remaining <= HEADER_SIZE + FOOTER_SIZE + 8)
+		{
+			// Just join this into the same memory chunk
+			f = (struct heap_alloc_footer *)(next + next->size - FOOTER_SIZE);
+
+			heap_delete_entry(&heap, old_entry);
+		}
+		else
+		{
+			f = mem + size;
+			struct heap_alloc_header *new_h =
+				(struct heap_alloc_header *)(f + FOOTER_SIZE);
+
+			struct heap_alloc_footer *new_f =
+				(struct heap_alloc_footer *)(new_h + remaining - FOOTER_SIZE);
+
+			new_h->allocated = false;
+			new_h->size = new_f->size = remaining;
+			new_h->magic = HEAP_MAGIC;
+
+			struct heap_entry entry = {
+				.key = remaining,
+				.address = (size_t)new_h,
+			};
+
+			heap_decrease_entry(&heap, old_entry, entry);
+		}
+
+		size_t full_size = (size_t)f - (size_t)base + FOOTER_SIZE;
+
+		f->size = full_size;
+		base->size = full_size;
+		base->magic = HEAP_MAGIC;
+		base->allocated = true;
+
+		return mem;
+	}
+	else
+	{
+		void *new = malloc(size);
+		if (!new)
+			return new;
+
+		memcpy(new, mem, base->size - HEADER_SIZE - FOOTER_SIZE);
+		free(mem);
+
+		return new;
+	}
+}
+
+void test_allocator()
+{
+	int *one = malloc(sizeof(int));
+	int *two = malloc(sizeof(int));
+
+	*one = 1;
+	*two = 2;
+
+	int *array = malloc(sizeof(int[12]));
+
+	for (int i = 0; i < 12; i++)
+		array[i] = i;
+
+	kprintf(DEBUG "Allocated one, two, array[3] = %d, %d, %d\n", *one, *two,
+			array[3]);
+	kprintf(DEBUG "[%x, %x, %x]\n", one, two, array);
+
+	kprintf(DEBUG "Freeing two\n");
+	free(two);
+	int *four = malloc(sizeof(int));
+	*four = 4;
+	kprintf(DEBUG "Allocated four = %d (%x)\n", *four, four);
+}
diff --git a/src/kernel/x86_32/bf.c b/src/kernel/x86_32/bf.c
new file mode 100644
index 0000000..04c97d9
--- /dev/null
+++ b/src/kernel/x86_32/bf.c
@@ -0,0 +1 @@
+#include <bf.h>
diff --git a/src/kernel/x86_32/boot.s b/src/kernel/x86_32/boot.s
new file mode 100644
index 0000000..047b326
--- /dev/null
+++ b/src/kernel/x86_32/boot.s
@@ -0,0 +1,149 @@
+;;; GRUB Multiboot header, calls main() in main.c
+
+	;; Some constants
+MBOOT_PAGE_ALIGN    equ 1<<0
+MBOOT_MEM_INFO      equ 1<<1
+MBOOT_HEADER_MAGIC  equ 0x1BADB002
+
+MBOOT_HEADER_FLAGS  equ MBOOT_PAGE_ALIGN | MBOOT_MEM_INFO
+MBOOT_CHECKSUM      equ -(MBOOT_HEADER_MAGIC + MBOOT_HEADER_FLAGS)
+
+	;; Kernel memory start
+KERNEL_VIRTUAL_BASE equ 0xC0000000
+	;; Index in page directory
+KERNEL_PAGE_NUMBER equ (KERNEL_VIRTUAL_BASE >> 22)
+
+STACK_SIZE equ 0x4000
+
+
+
+;;;;;;;;;;;;;;;;;;;;; DATA ;;;;;;;;;;;;;;;;;;;;;
+
+	[section .data align = 0x1000]
+page_directory:	
+	dd 0b010000011				; Identity map first 4 megs
+	times (KERNEL_PAGE_NUMBER - 1) dd 0
+	dd 0b010000011 				; Map kernel memory to zero page too 
+	times (1024 - KERNEL_PAGE_NUMBER - 1) dd 0
+
+gdt:
+	;; First entry, null segment
+	dw 0 						; zero limit
+	dw 0						; base low
+
+	db 0 						; base middle
+	db 0						; access
+	db 0						; granularity
+	db 0						; base high
+
+	;; Second entry, code segment
+	dw 0xffff					; max limit
+	dw 0
+
+	db 0
+	db 0x9a						; access
+	db 0xcf 					; granularity
+	db 0
+
+	;; Third entry, data segment
+	dw 0xffff					; max limit
+	dw 0
+
+	db 0
+	db 0x92						; access
+	db 0xcf 					; granularity
+	db 0
+
+	;; Fourth entry, user code segment
+	dw 0xffff					; max limit
+	dw 0
+
+	db 0
+	db 0xfa						; access
+	db 0xcf 					; granularity
+	db 0
+
+	;; Fifth entry, user data segment
+	dw 0xffff					; max limit
+	dw 0
+
+	db 0
+	db 0xf2						; access
+	db 0xcf 					; granularity
+	db 0
+
+gdt_pointer:
+	dw (8 * 5 - 1)				; sizeof(gdt entry) * 5 - 1
+	dd (gdt - KERNEL_VIRTUAL_BASE) ; Remember, PHYSICAL address
+
+
+;;;;;;;;;;;;;;;;;;;;; CODE ;;;;;;;;;;;;;;;;;;;;;
+	
+	[bits 32]
+	[section .text]
+	[global mboot]
+	[extern code]
+	[extern bss]
+	[extern end]
+mboot:
+	dd  MBOOT_HEADER_MAGIC ; This tells GRUB to start executing here:
+	dd  MBOOT_HEADER_FLAGS
+	dd  MBOOT_CHECKSUM
+   
+	dd  mboot                   ; Current location
+	dd  code                    ; .text section
+	dd  bss                     ; End of .data
+	dd  end                     ; End of kernel
+	dd  start
+
+	[global start]
+	[extern kmain]				; C code
+
+start equ (_start)
+
+_start:
+	;; First set up GDT
+	mov eax, (gdt_pointer - KERNEL_VIRTUAL_BASE)
+	lgdt [eax]					; Load GDT
+
+	mov ax, 0x10				; Offset of data segment
+	mov ds, ax
+	mov es, ax
+	mov fs, ax
+	mov gs, ax
+	mov ss, ax
+
+enable_paging:
+	mov ecx, (page_directory - KERNEL_VIRTUAL_BASE) ; Physical address
+	mov cr3, ecx
+
+	mov ecx, cr4
+	or ecx, 0x00000010			; Enable 4 meg pages
+	mov cr4, ecx
+
+	mov ecx, cr0
+	or ecx, 0x80000000			; Enable paging
+	mov cr0, ecx
+
+	lea ecx, [in_higher_half]	; Long jump into high memory
+	jmp ecx
+
+in_higher_half:	
+	mov dword [page_directory], 0
+	invlpg [0]					; Clear identity mapping
+
+	mov esp, (stack + STACK_SIZE)
+	
+	add ebx, 0xC0000000			; Translate to virtual address
+	push ebx					; Holds multiboot header location
+	push esp 					; Initial kernel stack
+	call kmain
+
+.end:
+	hlt
+	jmp .end
+	
+
+	[section .bss align = 32]
+stack:
+	resb STACK_SIZE
diff --git a/src/kernel/x86_32/descriptor_tables.c b/src/kernel/x86_32/descriptor_tables.c
new file mode 100644
index 0000000..339162e
--- /dev/null
+++ b/src/kernel/x86_32/descriptor_tables.c
@@ -0,0 +1,141 @@
+#include "descriptor_tables.h"
+#include "io.h"
+#include "log.h"
+#include "pic.h"
+#include "vga.h"
+#include "faults.h"
+
+extern void gdt_flush(uint gdt);
+extern void idt_flush(uint idt);
+extern void tss_flush();
+
+static void gdt_set_gate(uint i, uint base, uint limit, uchar access,
+						 uchar gran);
+static void idt_set_gate(uchar num, uint base, ushort selector, uchar flags);
+
+struct gdt_entry gdt_entries[5];
+struct gdt_pointer gdt_pointer;
+
+struct idt_entry idt_entries[256];
+struct idt_pointer idt_pointer;
+
+static void (*isrs[32])() = {
+	isr0,  isr1,  isr2,	 isr3,	isr4,  isr5,  isr6,	 isr7,	isr8,  isr9,  isr10,
+	isr11, isr12, isr13, isr14, isr15, isr16, isr17, isr18, isr19, isr20, isr21,
+	isr22, isr23, isr24, isr25, isr26, isr27, isr28, isr29, isr30, isr31};
+
+static void (*irqs[16])() = {irq0,	irq1,  irq2,  irq3, irq4,  irq5,
+							 irq6,	irq7,  irq8,  irq9, irq10, irq11,
+							 irq12, irq13, irq14, irq15};
+
+extern void (*interrupt_handlers[256])(struct registers);
+
+struct tss_entry tss_entry;
+
+static void gdt_set_gate(uint i, uint base, uint limit, uchar access,
+						 uchar gran)
+{
+	struct gdt_entry *e = &gdt_entries[i];
+
+	e->base_low = base & 0xffff;
+	e->base_middle = (base >> 16) & 0xff;
+	e->base_high = (base >> 24) & 0xff;
+
+	e->limit_low = limit & 0xffff;
+	e->granularity = ((limit >> 16) & 0x0f) | (gran & 0xf0);
+
+	e->access = access;
+}
+
+static void init_tss(uint num, uint ss, uint esp)
+{
+	gdt_set_gate(num, (uint)&tss_entry, (uint)&tss_entry+1, 0xe9, 0x00);
+
+	memset(&tss_entry, 0, sizeof(tss_entry));
+
+	tss_entry.ss0 = ss;
+	tss_entry.esp0 = esp;
+	tss_entry.cs = 0x0b;
+	// | 0b11 to make these readable from user-mode. i.e. user mode
+	// can switch to kernel mode using this tss
+	tss_entry.ss = tss_entry.ds = tss_entry.es = tss_entry.fs = tss_entry.gs = 0x13;
+}
+
+void init_gdt()
+{
+	vga_write("Initializing GDT...\n");
+	gdt_pointer.limit = sizeof(struct gdt_entry) * 5 - 1;
+	gdt_pointer.base = (uint)&gdt_entries;
+
+	gdt_set_gate(0, 0, 0, 0, 0);		// Null segment, 0x00
+	gdt_set_gate(1, 0, ~0, 0x9a, 0xcf); // Code segment, 0x08
+	gdt_set_gate(2, 0, ~0, 0x92, 0xcf); // Data segment, 0x10
+	gdt_set_gate(3, 0, ~0, 0xfa, 0xcf); // User mode code segment, 0x18
+	gdt_set_gate(4, 0, ~0, 0xf2, 0xcf); // User mode data segment, 0x20
+	//init_tss(5, 0x10, 0x0); // 0x10 = kernel data segment, 0x28
+
+	for (volatile uint i = 0; i < 0x1000; i++)
+	{
+	} // waste some time, for some reason this helps
+
+	gdt_flush((uint)&gdt_pointer);
+	// For now let's not do this
+	// tss_flush();
+
+	vga_write("GDT Initialized\n");
+}
+
+void init_idt()
+{
+	idt_pointer.limit = sizeof(struct idt_entry) * 256 - 1;
+	idt_pointer.base = (uint)&idt_entries;
+
+	memset(&idt_entries, 0, sizeof(idt_entries));
+
+	// Remap PIC
+	pic_remap();
+
+	init_faults();
+
+	vga_set_color(CYAN, BLACK);
+	for (int i = 0; i < 16; i++)
+	{
+		idt_set_gate(IRQ_TO_INT(i), (uint)irqs[i], 0x08, 0x8e);
+	}
+	vga_set_color(WHITE, BLACK);
+
+	for (int i = 0; i < 32; i++)
+	{
+		idt_set_gate(i, (uint)isrs[i], 0x08, 0x8e);
+	}
+
+	idt_set_gate(0x80, (uint)isr128, 0x08, 0x8e);
+	idt_set_gate(0x81, (uint)isr129, 0x08, 0x8e);
+
+	idt_flush((uint)&idt_pointer);
+
+	vga_write("IDT Initialized!\n");
+}
+
+static void idt_set_gate(uchar num, uint base, ushort selector, uchar flags)
+{
+	struct idt_entry *e = &idt_entries[num];
+
+	e->base_low = base & 0xffff;
+	e->base_high = (base >> 16) & 0xffff;
+
+	e->selector = selector;
+	e->zero = 0;
+	e->flags = flags /* | 0x60 */;
+}
+
+void init_descriptor_tables()
+{
+	init_gdt();
+	init_idt();
+}
+
+void set_kernel_interrupt_stack(void *stack)
+{
+	tss_entry.esp0 = (uint)stack;
+}
diff --git a/src/kernel/x86_32/descriptor_tables.h b/src/kernel/x86_32/descriptor_tables.h
new file mode 100644
index 0000000..6022f8d
--- /dev/null
+++ b/src/kernel/x86_32/descriptor_tables.h
@@ -0,0 +1,180 @@
+#pragma once
+
+#include "kint.h"
+
+struct gdt_entry
+{
+	ushort limit_low;
+	ushort base_low;
+	uchar base_middle;
+
+	union {
+		struct
+		{
+			uint a_p : 1;
+			uint a_dpl : 2;
+			uint a_dt : 1;
+			uint a_type : 4;
+		} __attribute__((packed));
+
+		uchar access;
+	};
+
+	union {
+		struct
+		{
+			uint g_g : 1;
+			uint g_d : 1;
+			uint g_zero : 2; /* includes A */
+			uint g_len : 4;
+		} __attribute__((packed));
+
+		uchar granularity;
+	};
+
+	uchar base_high;
+} __attribute__((packed));
+
+struct gdt_pointer
+{
+	/* Upper 16 bits of selector limits */
+	ushort limit;
+	/* first struct gdt_entry */
+	uint base;
+} __attribute__((packed));
+
+struct idt_entry
+{
+	ushort base_low;
+	ushort selector;
+	uchar zero;
+
+	union {
+		struct
+		{
+			uchar f_p : 1;
+			uchar f_dpl : 2;
+			uchar f_const : 5;
+		} __attribute__((packed));
+
+		uchar flags;
+	};
+
+	ushort base_high;
+} __attribute__((packed));
+
+#define IDT_F_CONST 0b00110
+
+struct idt_pointer
+{
+	ushort limit;
+	uint base;
+} __attribute__((packed));
+
+// We don't use hardware task switching, but we need a TSS entry
+// anyway.
+struct __attribute__((packed)) tss_entry
+{
+	// Previous TSS. Unused.
+	uint prev_tss;   
+	// Kernel stack pointer.
+	uint esp0;       
+	// Kernel stack segment.
+	uint ss0;        
+	// Unused
+	uint esp1;       
+	uint ss1;
+	uint esp2;
+	uint ss2;
+	uint cr3;
+	uint eip;
+	uint eflags;
+	uint eax;
+	uint ecx;
+	uint edx;
+	uint ebx;
+	uint esp;
+	uint ebp;
+	uint esi;
+	uint edi;
+	// The value to load into ES when we change to kernel mode.
+	uint es;         
+	// The value to load into CS when we change to kernel mode.
+	uint cs;         
+	// The value to load into SS when we change to kernel mode.
+	uint ss;         
+	// The value to load into DS when we change to kernel mode.
+	uint ds;         
+	// The value to load into FS when we change to kernel mode.
+	uint fs;         
+	// The value to load into GS when we change to kernel mode.
+	uint gs;         
+	// Unused...
+	uint ldt;        
+	ushort trap;
+	ushort iomap_base;
+};
+
+extern void isr0();
+extern void isr1();
+extern void isr2();
+extern void isr3();
+extern void isr4();
+extern void isr5();
+extern void isr6();
+extern void isr7();
+extern void isr8();
+extern void isr9();
+extern void isr10();
+extern void isr11();
+extern void isr12();
+extern void isr13();
+extern void isr14();
+extern void isr15();
+extern void isr16();
+extern void isr17();
+extern void isr18();
+extern void isr19();
+extern void isr20();
+extern void isr21();
+extern void isr22();
+extern void isr23();
+extern void isr24();
+extern void isr25();
+extern void isr26();
+extern void isr27();
+extern void isr28();
+extern void isr29();
+extern void isr30();
+extern void isr31();
+
+// Syscall
+extern void isr128();
+// Tasking setup
+extern void isr129();
+
+extern void irq0();
+extern void irq1();
+extern void irq2();
+extern void irq3();
+extern void irq4();
+extern void irq5();
+extern void irq6();
+extern void irq7();
+extern void irq8();
+extern void irq9();
+extern void irq10();
+extern void irq11();
+extern void irq12();
+extern void irq13();
+extern void irq14();
+extern void irq15();
+
+void init_descriptor_tables();
+void init_idt();
+void init_gdt();
+
+/// Set the stack to be used for Kernel-mode interrupt routines
+void set_kernel_interrupt_stack(void *stack);
+
+extern struct tss_entry tss_entry;
diff --git a/src/kernel/x86_32/faults.c b/src/kernel/x86_32/faults.c
new file mode 100644
index 0000000..e7c4180
--- /dev/null
+++ b/src/kernel/x86_32/faults.c
@@ -0,0 +1,22 @@
+#include "pic.h"
+#include <log.h>
+
+#define DECLARE_INTERRUPT(sym, name)                                           \
+	static void sym##_h(struct registers *regs)                                \
+	{                                                                          \
+		kprintf(ERROR "Fault " name ": eip = 0x%x, err = 0x%x\n", regs->eip,   \
+				regs->error_code);                                             \
+		asm volatile("cli");                                                   \
+		kpanic(name);                                                          \
+	}
+
+#define ADD_INTERRUPT(sym, num) add_interrupt_handler(num, sym##_h)
+
+DECLARE_INTERRUPT(gp, "#GP")
+DECLARE_INTERRUPT(pf, "#PF")
+
+void init_faults()
+{
+	ADD_INTERRUPT(gp, 13);
+	ADD_INTERRUPT(pf, 14);
+}
diff --git a/src/kernel/x86_32/faults.h b/src/kernel/x86_32/faults.h
new file mode 100644
index 0000000..a85b4ec
--- /dev/null
+++ b/src/kernel/x86_32/faults.h
@@ -0,0 +1,3 @@
+#pragma once
+
+void init_faults();
\ No newline at end of file
diff --git a/src/kernel/x86_32/gdt_flush.s b/src/kernel/x86_32/gdt_flush.s
new file mode 100644
index 0000000..f1dd8ac
--- /dev/null
+++ b/src/kernel/x86_32/gdt_flush.s
@@ -0,0 +1,17 @@
+	[bits 32]
+	[global gdt_flush]
+
+gdt_flush:
+	mov eax, [esp + 4]
+	lgdt [eax]					; Load GDT
+
+	mov ax, 0x10				; Offset of data segment
+	mov ds, ax,
+	mov es, ax
+	mov fs, ax
+	mov gs, ax
+	mov ss, ax
+
+	jmp 0x08:.flush				; Implicitly reloads the code segment
+.flush:
+	ret
diff --git a/src/kernel/x86_32/idt.s b/src/kernel/x86_32/idt.s
new file mode 100644
index 0000000..5ed637e
--- /dev/null
+++ b/src/kernel/x86_32/idt.s
@@ -0,0 +1,86 @@
+	[bits 32]
+	[global idt_flush]
+
+idt_flush:
+	mov eax, [esp + 4]
+	lidt [eax]
+	ret
+
+%macro ISRNOERR 1
+	[global isr%1]
+isr%1:
+	cli
+	push 0
+	push %1
+	jmp isr_common
+%endmacro
+
+%macro ISRERR 1
+	[global isr%1]
+isr%1:
+	cli
+	push byte %1
+	jmp isr_common
+%endmacro
+
+ISRNOERR 0
+ISRNOERR 1
+ISRNOERR 2
+ISRNOERR 3
+ISRNOERR 4
+ISRNOERR 5
+ISRNOERR 6
+ISRNOERR 7
+ISRERR 8
+ISRNOERR 9
+ISRERR 10
+ISRERR 11
+ISRERR 12
+ISRERR 13
+ISRERR 14
+ISRNOERR 15
+ISRNOERR 16
+ISRERR 17
+ISRNOERR 18
+ISRNOERR 19
+ISRNOERR 20
+ISRERR 21
+ISRNOERR 22
+ISRNOERR 23
+ISRNOERR 24
+ISRNOERR 25
+ISRNOERR 26
+ISRNOERR 27
+ISRNOERR 28
+ISRNOERR 29
+ISRNOERR 30
+ISRNOERR 31
+
+ISRNOERR 128
+ISRNOERR 129
+
+	[extern isr_handler]
+isr_common:
+	pushad						; Save all registers
+
+	mov ax, ds					; Save data segment
+	push eax
+
+	mov ax, 0x10 				; New segments
+	mov ds, ax
+	mov es, ax
+	mov fs, ax
+	mov gs, ax
+
+	call isr_handler
+
+	pop eax						; Reset segments
+	mov ds, ax
+	mov es, ax
+	mov fs, ax
+	mov gs, ax
+
+	popad
+	add esp, 8 					; Passed arguments
+	sti
+	iret						; Return from interrupt
diff --git a/src/kernel/x86_32/io.c b/src/kernel/x86_32/io.c
new file mode 100644
index 0000000..1c10b53
--- /dev/null
+++ b/src/kernel/x86_32/io.c
@@ -0,0 +1,187 @@
+#include "io.h"
+#include "kbd.h"
+#include "log.h"
+#include "pic.h"
+
+bool pressed_keys[LAST_KBD_KEY];
+char special_key_mappings[LAST_KBD_KEY - FIRST_KBD_KEY] = {
+	[KBD_ENTER - FIRST_KBD_KEY] = '\n',
+	[KBD_SPACEBAR - FIRST_KBD_KEY] = ' ',
+	[KBD_TAB - FIRST_KBD_KEY] = '\t',
+	[KBD_BACKSPACE - FIRST_KBD_KEY] = '\b',
+};
+
+void outb(ushort port, uchar val)
+{
+	asm volatile("outb %1, %0" : : "dN"(port), "a"(val));
+}
+
+uchar inb(ushort port)
+{
+	uchar ret;
+	asm volatile("inb %1, %0" : "=a"(ret) : "dN"(port));
+	return ret;
+}
+
+ushort inw(ushort port)
+{
+	ushort ret;
+	asm("inw %1, %0" : "=a"(ret) : "dN"(port));
+	return ret;
+}
+
+struct ext2_dir_contains_data
+{
+	char *name;
+	uint len;
+	bool found;
+};
+
+void outl(ushort port, uint val)
+{
+	asm("outl %1, %0" : : "dN"(port), "a"(val));
+}
+
+uint inl(ushort port)
+{
+	uint ret;
+	asm("inl %1, %0" : "=a"(ret) : "dN"(port));
+	return ret;
+}
+
+void outw(ushort port, ushort val)
+{
+	asm("outw %1, %0" :: "dN"(port), "a"(val));
+}
+
+void __attribute__((noinline)) nop()
+{
+	asm("nop");
+}
+
+void *memset(void *s, int c, size_t n)
+{
+	for (size_t i = 0; i < n; i++)
+	{
+		((uchar *)s)[i] = c;
+	}
+	return s;
+}
+
+void *memcpy(void *dest, const void *src, size_t n)
+{
+	for (size_t i = 0; i < n; i++)
+	{
+		((uchar *)dest)[i] = ((uchar *)src)[i];
+	}
+	return dest;
+}
+
+void strcpy(char *dest, char *src)
+{
+	memcpy(dest, src, strlen(src) + 1);
+}
+
+uint strlen(char *a)
+{
+	uint i = 0;
+	for (; a[i]; i++)
+	{
+	}
+
+	return i;
+}
+
+uint strnlen(char *s, size_t len)
+{
+	int i;
+
+	for (i = 0; s[i] && i < len; i++)
+	{
+	}
+
+	return i;
+}
+
+int strncmp(char *a, char *b, size_t len)
+{
+	int al = strnlen(a, len), bl = strnlen(b, len);
+
+	if (al != bl)
+		return bl - al;
+
+	for (int i = 0; i < al; i++)
+	{
+		if (a[i] != b[i])
+			return -1;
+	}
+	return 0;
+}
+
+int strcmp(char *a, char *b)
+{
+	int al = strlen(a), bl = strlen(b);
+
+	if (al != bl)
+		return bl - al;
+
+	for (int i = 0; i < al; i++)
+	{
+		if (a[i] != b[i])
+			return -1;
+	}
+	return 0;
+}
+
+uchar kbd_scan_code()
+{
+	return inb(KBD_DATA_PORT);
+}
+
+static bool kbd_shift_pressed()
+{
+	return pressed_keys[KBD_LEFT_SHIFT] || pressed_keys[KBD_RIGHT_SHIFT] ||
+		   pressed_keys[KBD_CAPS_LOCK];
+}
+
+void kbd_handle_input(struct registers *registers)
+{
+	uchar byte = kbd_scan_code();
+
+	struct kbd_scan_codes code = scan_code_set_1[byte];
+
+	if (code.ascii && !code.brk)
+	{
+		kprintf("%c", kbd_shift_pressed() && code.up_symbol ? code.up_symbol
+															: code.symbol);
+	}
+	else if (!code.brk && special_key_mappings[code.symbol - FIRST_KBD_KEY])
+	{
+		kprintf("%c", special_key_mappings[code.symbol - FIRST_KBD_KEY]);
+	}
+	pressed_keys[code.symbol] = !code.brk;
+}
+
+void init_kbd()
+{
+	memset(pressed_keys, 0, LAST_KBD_KEY);
+	add_interrupt_handler(33, kbd_handle_input);
+}
+
+bool isdigit(char c)
+{
+	return c >= '0' && c <= '9';
+}
+
+uint parse_int(char *string)
+{
+	uint number = 0;
+
+	for (; isdigit(*string); string++)
+	{
+		number *= 10;
+		number += *string - '0';
+	}
+
+	return number;
+}
diff --git a/src/kernel/x86_32/irq.s b/src/kernel/x86_32/irq.s
new file mode 100644
index 0000000..dc16f89
--- /dev/null
+++ b/src/kernel/x86_32/irq.s
@@ -0,0 +1,53 @@
+	[bits 32]
+
+%macro IRQ 2
+	[global irq%1]
+irq%1:
+	cli
+	push 0		 				; Error code
+	push %2						; Interrupt number
+	jmp irq_common
+%endmacro
+
+IRQ 0, 32
+IRQ 1, 33
+IRQ 2, 34
+IRQ 3, 35
+IRQ 4, 36
+IRQ 5, 37
+IRQ 6, 38
+IRQ 7, 39
+IRQ 8, 40
+IRQ 9, 41
+IRQ 10, 42
+IRQ 11, 43
+IRQ 12, 44
+IRQ 13, 45
+IRQ 14, 46
+IRQ 15, 47
+
+	[extern irq_handler]
+irq_common:
+	pushad
+	mov ax, ds					; Save data segment
+	push eax
+
+	mov ax, 0x10 				; New segments
+	mov ds, ax
+	mov es, ax
+	mov fs, ax
+	mov gs, ax
+
+	call irq_handler
+
+	pop ebx						; Old data segment
+	mov ds, bx
+	mov es, bx
+	mov fs, bx
+	mov gs, bx
+
+	popad
+	add esp, 8
+	sti
+	iret
+
diff --git a/src/kernel/x86_32/kheap.c b/src/kernel/x86_32/kheap.c
new file mode 100644
index 0000000..3fc2f8c
--- /dev/null
+++ b/src/kernel/x86_32/kheap.c
@@ -0,0 +1,175 @@
+#include "kheap.h"
+#include "log.h"
+
+static void swap_entries(struct heap_entry *a, struct heap_entry *b)
+{
+	// Static to not waste stack space, NOT thread safe
+	static struct heap_entry tmp;
+	tmp = *a;
+	*a = *b;
+	*b = tmp;
+}
+
+int heap_parent(int i)
+{
+	return (i - 1) / 2;
+}
+
+int heap_left(int i)
+{
+	return 2 * i + 1;
+}
+
+int heap_right(int i)
+{
+	return 2 * i + 2;
+}
+
+static void heap_sort(struct min_heap *heap, int i)
+{
+	int l = heap_left(i), r = heap_right(i);
+	int smallest = i;
+	int size = heap->size;
+	struct heap_entry *e = heap->elements;
+
+	if (l < size && e[l].key < e[smallest].key)
+		smallest = l;
+	if (r < size && e[r].key < e[smallest].key)
+		smallest = r;
+
+	if (smallest != i)
+	{
+		swap_entries(&e[i], &e[smallest]);
+		heap_sort(heap, smallest);
+	}
+}
+
+static void heap_delete_root(struct min_heap *heap)
+{
+	if (heap->size <= 0)
+		return;
+	if (heap->size == 1)
+		heap->size--;
+
+	heap->elements[0] = heap->elements[--heap->size];
+	heap_sort(heap, 0);
+}
+
+void heap_decrease(struct min_heap *heap, int i, struct heap_entry to)
+{
+	heap->elements[i] = to;
+	int k = to.key;
+
+	while (i && heap->elements[heap_parent(i)].key > k)
+	{
+		swap_entries(&heap->elements[i], &heap->elements[heap_parent(i)]);
+		i = heap_parent(i);
+	}
+}
+
+void heap_delete(struct min_heap *heap, int i)
+{
+	heap_decrease(heap, i,
+				  (struct heap_entry){
+					  .key = -1,
+					  .address = 0,
+				  });
+	heap_delete_root(heap);
+}
+
+void heap_insert(struct min_heap *heap, struct heap_entry e)
+{
+	kassert(heap->size < HEAP_SIZE, "Heap overflow!");
+
+	int i = heap->size++;
+	uint k = e.key;
+	heap->elements[i] = e;
+
+	// While the parent element is GREATER than the child
+	while (i && heap->elements[heap_parent(i)].key > k)
+	{
+		swap_entries(&heap->elements[i], &heap->elements[heap_parent(i)]);
+		i = heap_parent(i);
+	}
+}
+
+struct heap_entry heap_lookup_min(struct min_heap *heap, int min, bool *ok,
+								  bool delete, int *i)
+{
+	int j = 0;
+	struct heap_entry *e = heap->elements;
+
+	while (j < heap->size && e[j].key < min)
+	{
+		int left = heap_left(j), right = heap_right(j);
+
+		// If left is invalid, use right, if right is invalid, use left.
+		// If both are valid, chose the smallest one.
+		// In the first case, right may too be invalid, but it will be
+		// sorted out in the while loop (see else clause at end of function).
+
+		if (left >= heap->size)
+			j = right;
+		else if (right >= heap->size)
+			j = left;
+		else
+			j = e[left].key < e[right].key ? left : right;
+	}
+
+	if (j < heap->size)
+	{
+		struct heap_entry val = e[j];
+
+		*ok = true;
+
+		if (i)
+			*i = j;
+
+		if (delete)
+			heap_delete(heap, j);
+
+		return val;
+	}
+	else
+	{
+		*ok = false;
+		return (struct heap_entry){0};
+	}
+}
+
+static int heap_entry_index(struct min_heap *heap, struct heap_entry e)
+{
+	size_t key = e.key;
+	int i = 0;
+
+	struct heap_entry *a = heap->elements;
+
+	while (i < heap->size && a[i].key != key)
+	{
+		int left = heap_left(i),
+			right = heap_right(i);
+
+		if (left >= heap->size)
+			i = right;
+		else if (right >= heap->size)
+			i = left;
+		else
+			i = a[left].key < a[right].key ? left : right;
+	}
+
+	if (i >= heap->size)
+		return -1;
+	else
+		return i;
+}
+
+void heap_decrease_entry(struct min_heap *heap, struct heap_entry from,
+						 struct heap_entry to)
+{
+	heap_decrease(heap, heap_entry_index(heap, from), to);
+}
+
+void heap_delete_entry(struct min_heap *heap, struct heap_entry e)
+{
+	heap_delete(heap, heap_entry_index(heap, e));
+}
diff --git a/src/kernel/x86_32/kheap.h b/src/kernel/x86_32/kheap.h
new file mode 100644
index 0000000..a8b0bb5
--- /dev/null
+++ b/src/kernel/x86_32/kheap.h
@@ -0,0 +1,45 @@
+#pragma once
+
+#include "kint.h"
+
+#define HEAP_SIZE 4096
+#define HEAP_MAGIC 0xCAFEBABE
+
+struct heap_entry
+{
+	uint key;
+	size_t address;
+};
+
+struct min_heap
+{
+	struct heap_entry elements[HEAP_SIZE];
+	uint size;
+};
+
+struct heap_alloc_header
+{
+	uint magic;
+	bool allocated;
+	// size = size from beginning of header to end of footer
+	size_t size;
+};
+
+struct heap_alloc_footer
+{
+	// size = size from beginning of header to end of footer
+	size_t size;
+};
+
+int heap_parent(int i);
+int heap_left(int i);
+int heap_right(int i);
+void heap_delete(struct min_heap *heap, int i);
+void heap_insert(struct min_heap *heap, struct heap_entry e);
+void heap_decrease(struct min_heap *heap, int k, struct heap_entry to);
+void heap_decrease_entry(struct min_heap *heap, struct heap_entry from,
+						 struct heap_entry to);
+void heap_delete_entry(struct min_heap *heap, struct heap_entry e);
+
+struct heap_entry heap_lookup_min(struct min_heap *heap, int min, bool *ok,
+								  bool delete, int *i);
diff --git a/src/kernel/x86_32/link.ld b/src/kernel/x86_32/link.ld
new file mode 100644
index 0000000..512e3ad
--- /dev/null
+++ b/src/kernel/x86_32/link.ld
@@ -0,0 +1,30 @@
+ENTRY(start)
+SECTIONS
+{
+    . = 0xC0100000;
+
+    kernel_virtual_start = .;
+    kernel_physical_start = . - 0xC0000000;
+
+
+    .text ALIGN(4096) : AT(ADDR(.text) - 0xC0000000)
+    {
+        code = .; _code = .; __code = .;
+        *(.text)
+    }
+
+    .data ALIGN(4096) : AT(ADDR(.data) - 0xC0000000)
+    {
+        data = .; _data = .; __data = .;
+        *(.data)
+        *(.rodata)
+    }
+
+    .bss ALIGN(4096) : AT(ADDR(.bss) - 0xC0000000)
+    {
+        bss = .; _bss = .; __bss = .;
+        *(.bss)
+    }
+
+    end = .; _end = .; __end = .;
+}
diff --git a/src/kernel/x86_32/log.c b/src/kernel/x86_32/log.c
new file mode 100644
index 0000000..0ea6822
--- /dev/null
+++ b/src/kernel/x86_32/log.c
@@ -0,0 +1,82 @@
+#include "log.h"
+#include "kint.h"
+#include "stdarg.h"
+#include "vga.h"
+
+void kprintf(const char *format, ...)
+{
+	va_list args;
+	va_start(args, format);
+
+	while (*format)
+	{
+		if (*format == '%')
+		{
+			format++;
+
+			switch (*format)
+			{
+			case 'd': {
+				uint x = (uint)va_arg(args, uint);
+				vga_putd(x);
+				break;
+			}
+
+			case 'p':
+				vga_put('0');
+				vga_put('x');
+
+			case 'x': {
+				// consider hex always unsigned
+				uint x = (uint)va_arg(args, uint);
+				if (x)
+					vga_putx(x);
+				else
+					vga_put('0');
+				break;
+			}
+
+			case 's': {
+				char *s = va_arg(args, char *);
+				vga_write(s);
+				break;
+			}
+
+			case 'c': {
+				char s = va_arg(args, int);
+				vga_put(s);
+				break;
+			}
+			case 'b': {
+				uint v = va_arg(args, uint);
+				if (v)
+					vga_write("true");
+				else
+					vga_write("false");
+			}
+			}
+			format++;
+		}
+		else
+		{
+			vga_put(*format);
+			format++;
+		}
+	}
+
+	va_end(args);
+}
+
+void kassert_int(bool condition, const char *message, const char *file,
+				 const int line)
+{
+	if (!condition)
+	{
+		vga_set_color(LIGHT_RED, BLACK);
+		kprintf(ERROR "ASSERTION FAILED: %s:%d\n%s\n", file, line, message);
+
+		while (1)
+		{
+		}
+	}
+}
diff --git a/src/kernel/x86_32/main.c b/src/kernel/x86_32/main.c
new file mode 100644
index 0000000..17a54d7
--- /dev/null
+++ b/src/kernel/x86_32/main.c
@@ -0,0 +1,119 @@
+#include "alloc.h"
+#include "descriptor_tables.h"
+#include "io.h"
+#include "log.h"
+#include "multiboot.h"
+#include "paging.h"
+#include "sync.h"
+#include "syscall.h"
+#include "task.h"
+#include "timer.h"
+#include "vfs.h"
+#include "vfs_initrd.h"
+#include "vga.h"
+#include <dri/ata_pio/ata_pio.h>
+#include <dri/fs/ext2/ext2.h>
+#include <dri/ide/ide.h>
+#include <dri/pci/pci.h>
+
+void greet()
+{
+	kprintf(DEBUG "Hello from get_task_id() = %d, get_process_id() = %d\n",
+			get_task_id(), get_process_id());
+}
+
+void other_thread(size_t data)
+{
+	kprintf(DEBUG "data is 0x%x\n", data);
+	greet();
+	kprintf(DEBUG "Returning from other_thread\n");
+
+	return;
+}
+
+int kmain(struct multiboot_info *mboot)
+{
+	init_paging();
+	init_vga();
+	init_descriptor_tables();
+	init_syscall();
+	vga_clear();
+
+	init_timer(20);
+	init_allocator();
+	init_kbd();
+
+#ifdef TEST_ALLOC
+	test_allocator();
+#endif
+
+	init_vfs();
+
+#ifdef INITRD
+	// Load initrd
+	struct multiboot_info mb = make_multiboot_physical(mboot);
+
+	kassert(mb.mods_count, "No multiboot modules loaded!");
+	kprintf(DEBUG "mboot->mods_addr = %d (0x%x)\n", mb.mods_addr, mb.mods_addr);
+	uchar *initrd_loc = (uchar *)((uint *)mb.mods_addr)[0];
+
+	kprintf(DEBUG "initrd is at 0x%x to 0x%x\n", initrd_loc);
+
+	init_initrd_vfs(initrd_loc);
+	kprintf(OKAY "VFS initialized\n");
+#endif
+
+	asm("sti");
+
+	init_tasks();
+	init_sync();
+
+	pci_init();
+
+	// Register PCI drivers
+	ide_register();
+
+	pci_load();
+
+	kprintf(OKAY "Loaded PCI device drivers\n");
+
+#ifdef TEST_THREADS
+	spawn_thread(other_thread, NULL);
+
+	greet();
+#endif
+
+#ifdef TEST_PCI
+	pci_print_devices();
+	pci_print_drivers();
+#endif
+
+#ifdef TEST_ATA_PIO
+	test_ata_pio();
+#endif
+
+	if (ext2_valid_filesystem())
+	{
+		kprintf(OKAY "EXT2 filesystem is valid\n");
+		ext2_mount(&root);
+	}
+	else
+	{
+		kprintf(
+			WARN
+			"Filesystem is not a valid EXT2 format, only EXT2 is supported\n");
+	}
+
+	kprintf(INFO "fs_readdir(\"/\") mnt=%p\n", root.mount);
+
+	struct fs_dirent dirent;
+	for (int i = 0; fs_readdir(&root, i, &dirent); i++)
+	{
+		kprintf(INFO "name: %s, inode: %d\n", dirent.name, dirent.inode);
+	}
+
+	while (true)
+		asm("hlt");
+
+	return 0xCAFEBABE;
+}
diff --git a/src/kernel/x86_32/multiboot.c b/src/kernel/x86_32/multiboot.c
new file mode 100644
index 0000000..3170472
--- /dev/null
+++ b/src/kernel/x86_32/multiboot.c
@@ -0,0 +1,25 @@
+#include "multiboot.h"
+#include "io.h"
+#include "log.h"
+
+#define TO_VIRT(val, t) val = (t)((size_t)val + 0xc0000000u)
+
+// old should be a VIRTUAL address
+struct multiboot_info make_multiboot_physical(struct multiboot_info *old)
+{
+	struct multiboot_info mb;
+	memcpy(&mb, old, sizeof(mb));
+
+	// Make modules physical
+	TO_VIRT(mb.mods_addr, uint);
+	TO_VIRT(mb.cmdline, char);
+
+	kprintf(DEBUG "mb.mods_addr = %d, 0x%x\n", mb.mods_addr, mb.mods_addr);
+	kassert((size_t)mb.mods_addr >= 0xc0000000, "mb.mods_addr PHYSICAL");
+	for (int i = 0; i < mb.mods_count + 1; i++)
+	{
+		TO_VIRT(*(uint *)(mb.mods_addr+i), uint);
+	}
+
+	return mb;
+}
diff --git a/src/kernel/x86_32/multiboot.h b/src/kernel/x86_32/multiboot.h
new file mode 100644
index 0000000..5672812
--- /dev/null
+++ b/src/kernel/x86_32/multiboot.h
@@ -0,0 +1,272 @@
+#pragma once
+
+/* multiboot.h - Multiboot header file. */
+/* Copyright (C) 1999,2003,2007,2008,2009,2010  Free Software Foundation, Inc.
+ *
+ *  Permission is hereby granted, free of charge, to any person obtaining a copy
+ *  of this software and associated documentation files (the "Software"), to
+ *  deal in the Software without restriction, including without limitation the
+ *  rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ *  sell copies of the Software, and to permit persons to whom the Software is
+ *  furnished to do so, subject to the following conditions:
+ *
+ *  The above copyright notice and this permission notice shall be included in
+ *  all copies or substantial portions of the Software.
+ *
+ *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL ANY
+ *  DEVELOPER OR DISTRIBUTOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+
+#include "kint.h"
+
+/* How many bytes from the start of the file we search for the header. */
+#define MULTIBOOT_SEARCH 8192
+#define MULTIBOOT_HEADER_ALIGN 4
+
+/* The magic field should contain this. */
+#define MULTIBOOT_HEADER_MAGIC 0x1BADB002
+
+/* This should be in %eax. */
+#define MULTIBOOT_BOOTLOADER_MAGIC 0x2BADB002
+
+/* Alignment of multiboot modules. */
+#define MULTIBOOT_MOD_ALIGN 0x00001000
+
+/* Alignment of the multiboot info structure. */
+#define MULTIBOOT_INFO_ALIGN 0x00000004
+
+/* Flags set in the ’flags’ member of the multiboot header. */
+
+/* Align all boot modules on i386 page (4KB) boundaries. */
+#define MULTIBOOT_PAGE_ALIGN 0x00000001
+
+/* Must pass memory information to OS. */
+#define MULTIBOOT_MEMORY_INFO 0x00000002
+
+/* Must pass video information to OS. */
+#define MULTIBOOT_VIDEO_MODE 0x00000004
+
+/* This flag indicates the use of the address fields in the header. */
+#define MULTIBOOT_AOUT_KLUDGE 0x00010000
+
+/* Flags to be set in the ’flags’ member of the multiboot info structure. */
+
+/* is there basic lower/upper memory information? */
+#define MULTIBOOT_INFO_MEMORY 0x00000001
+/* is there a boot device set? */
+#define MULTIBOOT_INFO_BOOTDEV 0x00000002
+/* is the command-line defined? */
+#define MULTIBOOT_INFO_CMDLINE 0x00000004
+/* are there modules to do something with? */
+#define MULTIBOOT_INFO_MODS 0x00000008
+
+/* These next two are mutually exclusive */
+
+/* is there a symbol table loaded? */
+#define MULTIBOOT_INFO_AOUT_SYMS 0x00000010
+/* is there an ELF section header table? */
+#define MULTIBOOT_INFO_ELF_SHDR 0X00000020
+
+/* is there a full memory map? */
+#define MULTIBOOT_INFO_MEM_MAP 0x00000040
+
+/* Is there drive info? */
+#define MULTIBOOT_INFO_DRIVE_INFO 0x00000080
+
+/* Is there a config table? */
+#define MULTIBOOT_INFO_CONFIG_TABLE 0x00000100
+
+/* Is there a boot loader name? */
+#define MULTIBOOT_INFO_BOOT_LOADER_NAME 0x00000200
+
+/* Is there a APM table? */
+#define MULTIBOOT_INFO_APM_TABLE 0x00000400
+
+/* Is there video information? */
+#define MULTIBOOT_INFO_VBE_INFO 0x00000800
+#define MULTIBOOT_INFO_FRAMEBUFFER_INFO 0x00001000
+
+#ifndef ASM_FILE
+
+struct multiboot_header
+{
+	/* Must be MULTIBOOT_MAGIC - see above. */
+	uint magic;
+
+	/* Feature flags. */
+	uint flags;
+
+	/* The above fields plus this one must equal 0 mod 2^32. */
+	uint checksum;
+
+	/* These are only valid if MULTIBOOT_AOUT_KLUDGE is set. */
+	uint header_addr;
+	uint load_addr;
+	uint load_end_addr;
+	uint bss_end_addr;
+	uint entry_addr;
+
+	/* These are only valid if MULTIBOOT_VIDEO_MODE is set. */
+	uint mode_type;
+	uint width;
+	uint height;
+	uint depth;
+};
+
+/* The symbol table for a.out. */
+struct multiboot_aout_symbol_table
+{
+	uint tabsize;
+	uint strsize;
+	uint addr;
+	uint reserved;
+};
+typedef struct multiboot_aout_symbol_table multiboot_aout_symbol_table_t;
+
+/* The section header table for ELF. */
+struct multiboot_elf_section_header_table
+{
+	uint num;
+	uint size;
+	uint addr;
+	uint shndx;
+};
+typedef struct multiboot_elf_section_header_table
+	multiboot_elf_section_header_table_t;
+
+struct multiboot_info
+{
+	/* Multiboot info version number */
+	uint flags;
+
+	/* Available memory from BIOS */
+	uint mem_lower;
+	uint mem_upper;
+
+	/* "root" partition */
+	uint boot_device;
+
+	/* Kernel command line */
+	uint cmdline;
+
+	/* Boot-Module list */
+	uint mods_count;
+	uint mods_addr;
+
+	union {
+		multiboot_aout_symbol_table_t aout_sym;
+		multiboot_elf_section_header_table_t elf_sec;
+	} u;
+
+	/* Memory Mapping buffer */
+	uint mmap_length;
+	uint mmap_addr;
+
+	/* Drive Info buffer */
+	uint drives_length;
+	uint drives_addr;
+
+	/* ROM configuration table */
+	uint config_table;
+
+	/* Boot Loader Name */
+	uint boot_loader_name;
+
+	/* APM table */
+	uint apm_table;
+
+	/* Video */
+	uint vbe_control_info;
+	uint vbe_mode_info;
+	ushort vbe_mode;
+	ushort vbe_interface_seg;
+	ushort vbe_interface_off;
+	ushort vbe_interface_len;
+
+	uint framebuffer_addr_low;
+	uint framebuffer_addr_high;
+	uint framebuffer_pitch;
+	uint framebuffer_width;
+	uint framebuffer_height;
+	uchar framebuffer_bpp;
+#define MULTIBOOT_FRAMEBUFFER_TYPE_INDEXED 0
+#define MULTIBOOT_FRAMEBUFFER_TYPE_RGB 1
+#define MULTIBOOT_FRAMEBUFFER_TYPE_EGA_TEXT 2
+	uchar framebuffer_type;
+	union {
+		struct
+		{
+			uint framebuffer_palette_addr;
+			ushort framebuffer_palette_num_colors;
+		};
+		struct
+		{
+			uchar framebuffer_red_field_position;
+			uchar framebuffer_red_mask_size;
+			uchar framebuffer_green_field_position;
+			uchar framebuffer_green_mask_size;
+			uchar framebuffer_blue_field_position;
+			uchar framebuffer_blue_mask_size;
+		};
+	};
+};
+typedef struct multiboot_info multiboot_info_t;
+
+struct multiboot_color
+{
+	uchar red;
+	uchar green;
+	uchar blue;
+};
+
+/*struct multiboot_mmap_entry
+{
+	uint size;
+	multiboot_uint64_t addr;
+	multiboot_uint64_t len;
+#define MULTIBOOT_MEMORY_AVAILABLE 1
+#define MULTIBOOT_MEMORY_RESERVED 2
+#define MULTIBOOT_MEMORY_ACPI_RECLAIMABLE 3
+#define MULTIBOOT_MEMORY_NVS 4
+#define MULTIBOOT_MEMORY_BADRAM 5
+	uint type;
+} __attribute__((packed));
+typedef struct multiboot_mmap_entry multiboot_memory_map_t; */
+
+struct multiboot_mod_list
+{
+	/* the memory used goes from bytes ’mod_start’ to ’mod_end-1’ inclusive */
+	uint mod_start;
+	uint mod_end;
+
+	/* Module command line */
+	uint cmdline;
+
+	/* padding to take it to 16 bytes (must be zero) */
+	uint pad;
+};
+typedef struct multiboot_mod_list multiboot_module_t;
+
+/* APM BIOS info. */
+struct multiboot_apm_info
+{
+	ushort version;
+	ushort cseg;
+	uint offset;
+	ushort cseg_16;
+	ushort dseg;
+	ushort flags;
+	ushort cseg_len;
+	ushort cseg_16_len;
+	ushort dseg_len;
+};
+
+struct multiboot_info make_multiboot_physical(struct multiboot_info *old);
+
+#endif // asm
diff --git a/src/kernel/x86_32/paging.c b/src/kernel/x86_32/paging.c
new file mode 100644
index 0000000..f959a92
--- /dev/null
+++ b/src/kernel/x86_32/paging.c
@@ -0,0 +1,243 @@
+#include "paging.h"
+#include "alloc.h"
+#include "io.h"
+#include "kint.h"
+#include "log.h"
+#include "pic.h"
+
+/**
+ * NOTE: In order to understand this code you should have the paging
+ * section of the Intel IA-32 and 64 manual volume 3 open. Sadly I
+ * have littered this with magic numbers that you will need to consult
+ * the manual to understand.
+ * TODO: Fix this!
+ */
+
+#define NUM_FRAMES 0xffffffff / 0x1000 / 32
+/* frames bitset, 0 = free, 1 = used */
+static uint frames[NUM_FRAMES];
+
+static uint first_page_table[1024] __attribute__((aligned(4096)));
+uint kernel_page_directory[1024] __attribute__((aligned(4096)));
+
+
+/* frame utils */
+
+#define BITS 32
+
+static void set_frame(size_t frame_addr)
+{
+	uint frame = frame_addr / 0x1000; // page aligned
+	frames[frame / BITS] |= 1 << (frame % BITS);
+}
+
+static bool test_frame(size_t frame_addr)
+{
+	uint frame = frame_addr / 0x1000; // page aligned
+	return frames[frame / BITS] & 1 << (frame % BITS);
+}
+
+static void clear_frame(size_t frame_addr)
+{
+	uint frame = frame_addr / 0x1000; // page aligned
+	frames[frame / BITS] &= ~(1 << (frame % BITS));
+}
+
+static uint first_free_frame()
+{
+	for (int i = 0; i < NUM_FRAMES / BITS; i++)
+	{
+		/*
+		 * If there are any zeroes, ~ will yield a non-zero result,
+		 * meaning that there are pages free. Otherwise, check next set
+		 */
+		if (!~frames[i])
+			continue;
+
+		for (int j = 0; j < BITS; j++)
+		{
+			if ((frames[i] & (1 << j)) == 0)
+			{
+				/* found unused frame */
+				uint frame = i * BITS + j;
+				kprintf(DEBUG "first_free_frame returning %d\n", frame);
+//				kpanic("asdf");
+				return frame;
+			}
+		}
+	}
+
+	/* did not find a free frame, panic */
+	kpanic("first_free_frame failed! no free frames");
+}
+
+void alloc_frame(uint *page_table_entry, bool user, bool writable)
+{
+	if (*page_table_entry & 1)
+		return; /* frame already allocated */
+
+	uint frame = first_free_frame();
+	//	kprintf(DEBUG "first_free_frame found %d\n", frame);
+	set_frame(frame * 0x1000); /* mark as mapped */
+	*page_table_entry = frame | 1 | writable << 1 | user << 2;
+}
+
+void free_frame(uint page)
+{
+	clear_frame(page / 0x1000);
+}
+
+void map_4mb(uint *dir, size_t virt_start, size_t phys_start, bool user,
+			 bool rw)
+{
+	uint page = virt_start / 0x1000;
+	uint table = virt_start >> 22;
+
+	for (uint i = 0; i < 1024 * 0x1000; i += 0x1000)
+	{
+		set_frame(i);
+	}
+
+	dir[table] = 0b10000011;
+}
+
+uint *get_or_create_table(uint *dir, uint table, bool user, bool rw)
+{
+	// If used AND NOT 4mb page (see figure 4-4, page 115 of Intel
+	// manual volume 3)
+	if (dir[table] & 1 && dir[table] ^ 1 << 7)
+	{
+		return (uint *)(size_t)PHYS_TO_VIRT((dir[table] & ~0xfff));
+	}
+
+	uint *page_table = malloc(sizeof(uint[1024]));
+	dir[table] = VIRT_TO_PHYS(page_table) | 1 | rw << 1 | user << 2;
+	return page_table;
+}
+
+void unmap_all_frames(uint page_table_p)
+{
+	uint *table = (uint *)PHYS_TO_VIRT(page_table_p);
+
+	for (int i = 0; i < 1024; i++)
+	{
+		if (table[i] & 1)
+		{
+			clear_frame(table[i] >> 12);
+		}
+	}
+}
+
+void destroy_page_table_if_exists(uint *dir, uint table)
+{
+	// If used AND NOT 4mb page
+	if (dir[table] & 1 && dir[table] ^ 1 << 7)
+	{
+		unmap_all_frames(dir[table] >> 12);
+		free((void *)PHYS_TO_VIRT(dir[table] >> 12));
+	}
+}
+
+void unmap_page(uint *dir, void *virt)
+{
+	uint page = ((size_t)virt / 0x1000) % 1024;
+	uint *table = get_or_create_table(dir, (size_t)virt >> 22, false, false);
+
+	table[page] = 0;
+}
+
+void map_page_to(uint *dir, void *virt, void *frame_p, bool writable, bool user)
+{
+	uint page = ((size_t)virt / 0x1000) % 1024;
+	uint *table = get_or_create_table(dir, (size_t)virt >> 22, false, false);
+
+	table[page] = (((uint)frame_p) ^ 0xfff) | 1 | writable << 1 | user << 2;
+}
+
+void alloc_kernel_page(uint *virt)
+{
+	alloc_page(kernel_page_directory, virt);
+}
+
+void alloc_page(uint *dir, uint *virt)
+{
+	// Page number % pages per table
+	uint page = ((size_t)virt / 0x1000) % 1024;
+	uint *table = get_or_create_table(dir, (size_t)virt >> 22, false, false);
+	kprintf(DEBUG "table = 0x%x (virt)\n", table);
+	kprintf(DEBUG "dir entry = 0x%x\n", dir[(size_t)virt >> 22]);
+
+	alloc_frame(&table[page], false, false);
+
+	kprintf(DEBUG "alloc_page table[page] = %d (0x%x)\n", table[page], table[page]);
+	return;
+}
+
+void alloc_kernel_page_range(uint *from, uint *to)
+{
+	uint f = (size_t)from / 0x1000, t = (size_t)to / 0x1000;
+
+	do
+	{
+		alloc_kernel_page((uint *)(size_t)t);
+		t += 0x1000; // next page
+	} while (f < t);
+}
+
+void map_page(uint *dir, size_t virt_start, bool user, bool rw)
+{
+	// Page number % pages per table
+	uint page = (virt_start / 0x1000) % 1024;
+	uint table = virt_start >> 22;
+	uint frame = first_free_frame();
+
+	// If 4mb map OR (maps to table AND table maps page)
+	if ((dir[table] & 1 && dir[table] & 1 << 7) ||
+		(dir[table] >> 12 && ((uint *)(size_t)dir[table])[page] & 1))
+	{
+		return;
+	}
+
+	set_frame(frame);
+	uint *t = get_or_create_table(dir, table, user, rw);
+	alloc_frame(t + page, user, rw);
+}
+
+/* paging stuff */
+
+uint *new_page_directory_v()
+{
+	// Only call this AFTER allocator + paging are initialized!
+	uint *dir = malloc(1024 * 4);
+	map_4mb(kernel_page_directory, (size_t)KERNEL_VIRTUAL_BASE, 0, false,
+			false);
+
+	return dir;
+}
+
+void free_page_directory_v(uint *dir_v)
+{
+	for (int i = 0; i < 1024; i++)
+	{
+		destroy_page_table_if_exists(dir_v, i);
+	}
+
+	free(dir_v);
+}
+
+void init_paging()
+{
+	memset(kernel_page_directory, 0, 1024 * 4);
+	map_4mb(kernel_page_directory, (size_t)KERNEL_VIRTUAL_BASE, 0, false,
+			false);
+
+	load_page_directory((uint)kernel_page_directory - 0xC0000000);
+
+	add_interrupt_handler(14, page_fault);
+}
+
+void page_fault(struct registers *regs)
+{
+	kprintf(ERROR "Page fault! eip = %d\n", regs->eip);
+	kpanic("Page fault");
+}
diff --git a/src/kernel/x86_32/paging.h b/src/kernel/x86_32/paging.h
new file mode 100644
index 0000000..e264356
--- /dev/null
+++ b/src/kernel/x86_32/paging.h
@@ -0,0 +1,23 @@
+#pragma once
+
+#include "kint.h"
+#include "registers.h"
+
+#define VIRT_TO_PHYS(virt) ((uint)(virt) - 0xC0000000)
+#define PHYS_TO_VIRT(phys) ((void *)((uint)(phys) + 0xC0000000))
+#define KERNEL_VIRTUAL_BASE 0xC0000000
+#define KERNEL_PAGE_NUMBER (KERNEL_VIRTUAL_BASE >> 22)
+
+/* defined in switch_table.s */
+extern uint load_page_directory(uint table_address);
+extern void enable_paging();
+extern uint kernel_page_directory[1024];
+
+void init_paging();
+
+void map_page_to(uint *dir, void *virt, void *frame_p, bool writable, bool user);
+void alloc_frame(uint *page_table_entry, bool user, bool writable);
+void alloc_page(uint *dir, uint *page);
+void alloc_kernel_page(uint *page);
+void page_fault(struct registers *regs);
+uint *new_page_directory_v();
diff --git a/src/kernel/x86_32/pic.c b/src/kernel/x86_32/pic.c
new file mode 100644
index 0000000..8c18097
--- /dev/null
+++ b/src/kernel/x86_32/pic.c
@@ -0,0 +1,69 @@
+#include "pic.h"
+#include "io.h"
+#include "log.h"
+
+#define ICW1_ICW4 0x01		/* ICW4 (not) needed */
+#define ICW1_SINGLE 0x02	/* Single (cascade) mode */
+#define ICW1_INTERVAL4 0x04 /* Call address interval 4 (8) */
+#define ICW1_LEVEL 0x08		/* Level triggered (edge) mode */
+#define ICW1_INIT 0x10		/* Initialization - required! */
+
+#define ICW4_8086 0x01		 /* 8086/88 (MCS-80/85) mode */
+#define ICW4_AUTO 0x02		 /* Auto (normal) EOI */
+#define ICW4_BUF_SLAVE 0x08	 /* Buffered mode/slave */
+#define ICW4_BUF_MASTER 0x0C /* Buffered mode/master */
+#define ICW4_SFNM 0x10		 /* Special fully nested (not) */
+
+typedef void (*interrupt_handler_t)(struct registers *);
+
+static interrupt_handler_t interrupt_handlers[256];
+
+void pic_send_eoi(uchar interrupt)
+{
+	if (interrupt >= IRQ_TO_INT(8))
+		outb(PIC2_COMMAND, PIC_EOI);
+
+	outb(PIC1_COMMAND, PIC_EOI);
+}
+
+void irq_handler(struct registers regs)
+{
+	pic_send_eoi(regs.interrupt_number);
+
+	if (interrupt_handlers[regs.interrupt_number])
+		interrupt_handlers[regs.interrupt_number](&regs);
+	else
+		kprintf(ERROR "Unhandled hardware interrupt: %d, called from %d\n",
+				regs.interrupt_number, regs.eip);
+}
+
+void isr_handler(struct registers regs)
+{
+	if (interrupt_handlers[regs.interrupt_number])
+		interrupt_handlers[regs.interrupt_number](&regs);
+	else
+		kprintf(ERROR "Unhandled interrupt: %d, called from %d\n",
+				regs.interrupt_number, regs.eip);
+}
+
+void add_interrupt_handler(uchar interrupt, void (*handler)(struct registers *))
+{
+	interrupt_handlers[interrupt] = handler;
+}
+
+void pic_remap()
+{
+	// I don't remember what this does.
+	outb(0x20, 0x11);
+	outb(0xA0, 0x11);
+	outb(0x21, 0x20);
+	outb(0xA1, 0x28);
+	outb(0x21, 0x04);
+	outb(0xA1, 0x02);
+	outb(0x21, 0x01);
+	outb(0xA1, 0x01);
+	outb(0x21, 0x0);
+	outb(0xA1, 0x0);
+
+	memset(interrupt_handlers, 0, sizeof(interrupt_handlers));
+}
diff --git a/src/kernel/x86_32/stdarg.h b/src/kernel/x86_32/stdarg.h
new file mode 100644
index 0000000..ce9bbc4
--- /dev/null
+++ b/src/kernel/x86_32/stdarg.h
@@ -0,0 +1,130 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+
+This file is part of GCC.
+
+GCC 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, or (at your option)
+any later version.
+
+GCC 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.
+
+Under Section 7 of GPL version 3, you are granted additional
+permissions described in the GCC Runtime Library Exception, version
+3.1, as published by the Free Software Foundation.
+
+You should have received a copy of the GNU General Public License and
+a copy of the GCC Runtime Library Exception along with this program;
+see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
+<http://www.gnu.org/licenses/>.  */
+
+/*
+ * ISO C Standard:  7.15  Variable arguments  <stdarg.h>
+ */
+
+#ifndef _STDARG_H
+#ifndef _ANSI_STDARG_H_
+#ifndef __need___va_list
+#define _STDARG_H
+#define _ANSI_STDARG_H_
+#endif /* not __need___va_list */
+#undef __need___va_list
+
+/* Define __gnuc_va_list.  */
+
+#ifndef __GNUC_VA_LIST
+#define __GNUC_VA_LIST
+typedef __builtin_va_list __gnuc_va_list;
+#endif
+
+/* Define the standard macros for the user,
+   if this invocation was from the user program.  */
+#ifdef _STDARG_H
+
+#define va_start(v, l) __builtin_va_start(v, l)
+#define va_end(v) __builtin_va_end(v)
+#define va_arg(v, l) __builtin_va_arg(v, l)
+#if !defined(__STRICT_ANSI__) || __STDC_VERSION__ + 0 >= 199900L ||            \
+	__cplusplus + 0 >= 201103L
+#define va_copy(d, s) __builtin_va_copy(d, s)
+#endif
+#define __va_copy(d, s) __builtin_va_copy(d, s)
+
+/* Define va_list, if desired, from __gnuc_va_list. */
+/* We deliberately do not define va_list when called from
+   stdio.h, because ANSI C says that stdio.h is not supposed to define
+   va_list.  stdio.h needs to have access to that data type,
+   but must not use that name.  It should use the name __gnuc_va_list,
+   which is safe because it is reserved for the implementation.  */
+
+#ifdef _BSD_VA_LIST
+#undef _BSD_VA_LIST
+#endif
+
+#if defined(__svr4__) || (defined(_SCO_DS) && !defined(__VA_LIST))
+/* SVR4.2 uses _VA_LIST for an internal alias for va_list,
+   so we must avoid testing it and setting it here.
+   SVR4 uses _VA_LIST as a flag in stdarg.h, but we should
+   have no conflict with that.  */
+#ifndef _VA_LIST_
+#define _VA_LIST_
+#ifdef __i860__
+#ifndef _VA_LIST
+#define _VA_LIST va_list
+#endif
+#endif /* __i860__ */
+typedef __gnuc_va_list va_list;
+#ifdef _SCO_DS
+#define __VA_LIST
+#endif
+#endif /* _VA_LIST_ */
+#else  /* not __svr4__ || _SCO_DS */
+
+/* The macro _VA_LIST_ is the same thing used by this file in Ultrix.
+   But on BSD NET2 we must not test or define or undef it.
+   (Note that the comments in NET 2's ansi.h
+   are incorrect for _VA_LIST_--see stdio.h!)  */
+#if !defined(_VA_LIST_) || defined(__BSD_NET2__) || defined(____386BSD____) || \
+	defined(__bsdi__) || defined(__sequent__) || defined(__FreeBSD__) ||       \
+	defined(WINNT)
+/* The macro _VA_LIST_DEFINED is used in Windows NT 3.5  */
+#ifndef _VA_LIST_DEFINED
+/* The macro _VA_LIST is used in SCO Unix 3.2.  */
+#ifndef _VA_LIST
+/* The macro _VA_LIST_T_H is used in the Bull dpx2  */
+#ifndef _VA_LIST_T_H
+/* The macro __va_list__ is used by BeOS.  */
+#ifndef __va_list__
+typedef __gnuc_va_list va_list;
+#endif /* not __va_list__ */
+#endif /* not _VA_LIST_T_H */
+#endif /* not _VA_LIST */
+#endif /* not _VA_LIST_DEFINED */
+#if !(defined(__BSD_NET2__) || defined(____386BSD____) || defined(__bsdi__) || \
+	  defined(__sequent__) || defined(__FreeBSD__))
+#define _VA_LIST_
+#endif
+#ifndef _VA_LIST
+#define _VA_LIST
+#endif
+#ifndef _VA_LIST_DEFINED
+#define _VA_LIST_DEFINED
+#endif
+#ifndef _VA_LIST_T_H
+#define _VA_LIST_T_H
+#endif
+#ifndef __va_list__
+#define __va_list__
+#endif
+
+#endif /* not _VA_LIST_, except on certain systems */
+
+#endif /* not __svr4__ */
+
+#endif /* _STDARG_H */
+
+#endif /* not _ANSI_STDARG_H_ */
+#endif /* not _STDARG_H */
diff --git a/src/kernel/x86_32/switch_table.s b/src/kernel/x86_32/switch_table.s
new file mode 100644
index 0000000..c8e6260
--- /dev/null
+++ b/src/kernel/x86_32/switch_table.s
@@ -0,0 +1,15 @@
+	[section .text]
+	[bits 32]
+
+	[global load_page_directory]
+load_page_directory:
+	mov ecx, [esp + 4]			; Pointer to directory
+	mov cr3, ecx
+	ret
+
+	[global enable_paging]
+enable_paging:
+	mov ecx, cr0
+	or eax, 0x80000000
+	mov cr0, ecx
+	ret
diff --git a/src/kernel/x86_32/sync.c b/src/kernel/x86_32/sync.c
new file mode 100644
index 0000000..4b210ca
--- /dev/null
+++ b/src/kernel/x86_32/sync.c
@@ -0,0 +1,168 @@
+#include <sync.h>
+#include <alloc.h>
+#include <task.h>
+#include <sys.h>
+#include <log.h>
+
+#define SM_PER_LIST 1024
+
+struct sm_task
+{
+	uint tid;
+	struct sm_task *next;
+};
+
+struct semaphore
+{
+	spinlock_t task_lock;
+	struct sm_task *first, *last;
+	_Atomic uint sm;
+};
+
+struct sm_list
+{
+	struct semaphore semaphores[SM_PER_LIST];
+	struct sm_list *next;
+};
+
+static struct sm_list *sm_first = NULL;
+static uint sm_num = 0;
+
+
+void sl_acquire(spinlock_t *sl)
+{
+	// This is a GCC intrinsic
+	while (!__sync_bool_compare_and_swap(sl, -2, 1))
+	{
+		// Kindly inform the CPU that this is a spin lock
+		asm("pause");
+	}
+}
+
+void sl_release(spinlock_t *sl)
+{
+	*sl = 0;
+}
+
+spinlock_t sl_new()
+{
+	return 0;
+}
+
+static struct semaphore *sm_from_id(semaphore_t sm)
+{
+	struct sm_list *first = sm_first;
+
+	while (sm >= SM_PER_LIST)
+	{
+		first = first->next;
+		sm -= SM_PER_LIST;
+	}
+
+	return &first->semaphores[sm];
+}
+
+static void sm_unsafe_wait(struct semaphore *sm)
+{
+	sm->sm--;
+
+	if (sm->sm < 0)
+	{
+		// Add this task to the waiting list
+		// This will be quick, so just use a spinlock
+		sl_acquire(&sm->task_lock);
+
+		kprintf(INFO "Semaphore waiting\n");
+
+		struct sm_task *task = malloc(sizeof(struct sm_task));
+
+		task->next = NULL;
+		task->tid = get_task_id();
+
+		if (sm->last)
+		{
+			sm->last->next = task;
+			sm->last = task;
+		}
+		else
+		{
+			sm->last = sm->first = task;
+		}
+
+		sl_release(&sm->task_lock);
+
+		set_waiting(get_task_id(), true);
+		sys_giveup();
+	}
+
+	// Otherwise there's nobody else waiting, just go ahead
+}
+
+static void sm_unsafe_signal(struct semaphore *sm)
+{
+	sm->sm++;
+
+	if (sm->sm <= 0)
+	{
+		sl_acquire(&sm->task_lock);
+
+		if (sm->first)
+		{
+			struct sm_task *f = sm->first;
+			sm->first = sm->first->next;
+
+			set_waiting(f->tid, false);
+
+			free(f);
+		}
+
+		sl_release(&sm->task_lock);
+	}
+}
+
+semaphore_t sm_new()
+{
+	if (sm_first == NULL)
+	{
+		sm_first = malloc(sizeof(struct sm_list));
+	}
+
+	uint num = sm_num++;
+
+	struct sm_list *l = sm_first;
+
+	while (num >= SM_PER_LIST)
+	{
+		if (!l->next)
+		{
+			l->next = malloc(sizeof(struct sm_list));
+		}
+
+		l = l->next;
+
+		num -= SM_PER_LIST;
+	}
+
+	l->semaphores[num].first = NULL;
+	l->semaphores[num].last = NULL;
+	l->semaphores[num].sm = 1;
+	l->semaphores[num].task_lock = sl_new();
+
+	return num;
+}
+
+void sm_wait(semaphore_t sm)
+{
+	sm_unsafe_wait(sm_from_id(sm));
+}
+
+void sm_signal(semaphore_t sm)
+{
+	sm_unsafe_signal(sm_from_id(sm));
+}
+
+
+
+void init_sync()
+{
+}
diff --git a/src/kernel/x86_32/syscall.c b/src/kernel/x86_32/syscall.c
new file mode 100644
index 0000000..26a0002
--- /dev/null
+++ b/src/kernel/x86_32/syscall.c
@@ -0,0 +1,25 @@
+#include "syscall.h"
+#include "log.h"
+#include "pic.h"
+#include "task.h"
+
+#include <sys.h>
+
+void do_syscall(struct registers *regs)
+{
+	switch (regs->eax)
+	{
+	case SYS_GIVEUP:
+		// easy, just switch tasks
+		switch_task(*regs);
+		break;
+
+	default:
+		kprintf(INFO "Syscall executed: %d\n", regs->eax);
+	}
+}
+
+void init_syscall()
+{
+	add_interrupt_handler(SYSCALL_INT, do_syscall);
+}
diff --git a/src/kernel/x86_32/syscall.h b/src/kernel/x86_32/syscall.h
new file mode 100644
index 0000000..c926ff1
--- /dev/null
+++ b/src/kernel/x86_32/syscall.h
@@ -0,0 +1,8 @@
+#pragma once
+
+#include "registers.h"
+
+#define SYSCALL_INT 0x80 // copy linux :)
+
+void do_syscall(struct registers *regs);
+void init_syscall();
diff --git a/src/kernel/x86_32/task.c b/src/kernel/x86_32/task.c
new file mode 100644
index 0000000..1ec1337
--- /dev/null
+++ b/src/kernel/x86_32/task.c
@@ -0,0 +1,235 @@
+#include "task.h"
+#include "alloc.h"
+#include "io.h"
+#include "log.h"
+#include "paging.h"
+#include "pic.h"
+
+struct process processes[1024] = {0};
+struct ll_task_i *first_task = NULL, *last_task = NULL, *current_task = NULL;
+static uint next_task_id = 0;
+
+bool tasks_initialized = false;
+
+void _init_tasks(struct registers *regs);
+
+void init_tasks()
+{
+	add_interrupt_handler(INIT_TASKS_INTERRUPT, _sys_init_tasks_h);
+
+	asm("int $0x81");
+}
+
+void _sys_init_tasks_h(struct registers *regs)
+{
+	_init_tasks(regs);
+}
+
+void _init_tasks(struct registers *regs)
+{
+	processes[0] = (struct process){
+		.exists = true,
+		.id = 0,
+		.ring = 0,
+		.uid = 0,
+		.page_directory_p = VIRT_TO_PHYS(kernel_page_directory),
+		// Obviously this isn't the actual stack position, but we want
+		// it to grow down from 4 gb so we will pretend that the first
+		// task has its stack at exactly 4gb and work from
+		// there. Because the new stack will be mapped to any random
+		// frame, it doesn't actually matter where we put it, we just
+		// want somewhere that won't collide with any user space stuff
+		// or our heap.
+		.last_stack_pos = 0xFFFFF000,
+	};
+	strcpy(processes[0].name, "kernel");
+
+	first_task = last_task = current_task = malloc(sizeof(struct ll_task_i));
+
+	first_task->next = NULL;
+	first_task->prev = NULL;
+	memset(&first_task->task, 0, sizeof(struct task));
+	first_task->task = (struct task){
+		.proc = &processes[0],
+		.state = *regs,
+		.id = next_task_id++,
+		.waiting = false,
+	};
+
+	tasks_initialized = true;
+}
+
+struct process *get_process(uint pid)
+{
+	if (pid < 1024)
+		return &processes[pid];
+	else
+		return NULL;
+}
+
+int get_task_id()
+{
+	return current_task->task.id;
+}
+
+int get_process_id()
+{
+	return current_task->task.proc->id;
+}
+
+void spawn_thread(void (*function)(void *), void *data)
+{
+	asm("cli");
+
+	kprintf(DEBUG "Spawning thread %p, data=%p\n", function, data);
+
+	struct process *proc = current_task->task.proc;
+	// Virtual address of page directory (in kernel memory)
+	uint *dir_v = PHYS_TO_VIRT(proc->page_directory_p);
+
+	// Virtual location of new stack, with space reserved for argument
+	// and return address to kill_this_thread().
+	uint new_stack_base_v = proc->last_stack_pos;
+	proc->last_stack_pos -= 0x1000;
+
+	// Alloc a new page in the current process mapping to the new stack
+	alloc_page(dir_v, (void *)proc->last_stack_pos);
+
+	kprintf(INFO "new_stack_base_v = %p\n", new_stack_base_v);
+	new_stack_base_v -= sizeof(uint);
+	*((uint *)new_stack_base_v) = (size_t)data;
+	new_stack_base_v -= sizeof(uint);
+	*((uint *)new_stack_base_v) = (size_t)&kill_this_thread;
+
+	kprintf(DEBUG "Set stack\n");
+
+	struct ll_task_i *ll_task = malloc(sizeof(struct ll_task_i));
+	memset(ll_task, 0, sizeof(struct ll_task_i));
+	struct task *task = &ll_task->task;
+	// New task is basically the same as the old one but with just a
+	// few changes
+	*task = current_task->task;
+
+	// Namely a new TID
+	task->id = next_task_id++;
+	// And stack, frame, and instruction pointers
+	task->state.ebp = task->state.esp = new_stack_base_v;
+	task->state.eip = (uint)function;
+	task->waiting = false;
+
+	last_task->next = ll_task;
+	ll_task->prev = last_task;
+	last_task = ll_task;
+
+	asm("sti");
+}
+
+void kill_this_thread()
+{
+	asm("cli");
+
+	if (current_task->prev == NULL && current_task->next == NULL)
+	{
+		kpanic("You may not kill the last task in the kernel");
+	}
+
+	struct ll_task_i *task = first_task,
+		*old_task = current_task;
+
+	if (current_task->prev != NULL)
+		current_task->prev->next = current_task->next;
+
+	if (current_task->next != NULL)
+	{
+		// If this is NULL, task will be first_task, which can't be
+		// the current task because we know there are more than one
+		// task, and this is the last one.
+		current_task->next->prev = current_task->prev;
+		task = current_task->next;
+	}
+
+	if (current_task == first_task)
+		first_task = current_task->next;
+
+	if (current_task == last_task)
+		last_task = current_task->prev;
+
+	current_task = task;
+	free(old_task);
+
+	switch_to_task(&current_task->task);
+
+	asm("sti");
+}
+
+extern void _switch_to_task(uint page_directory, struct registers ctx);
+#if 0
+{
+	asm("mov %0, %%ecx" :: "g"(page_directory));
+	asm("mov %ecx, %cr3");
+	// "ctx" will be at the top of the stack.
+	asm("iret");
+}
+#endif
+
+void switch_to_task(struct task *task)
+{
+	_switch_to_task(task->proc->page_directory_p, task->state);
+	__builtin_unreachable();
+}
+
+void _do_switch_task(struct registers regs)
+{
+	// Resetting eflags in _switch_to_task iret will switch this back
+	asm("cli");
+
+	kprintf(DEBUG "switching tasks\n");
+
+	// save context for this task
+	current_task->task.state = regs;
+
+	struct ll_task_i *original = current_task;
+
+	do
+	{
+		if (current_task->next == NULL)
+		{
+			current_task = first_task;
+		}
+		else
+		{
+			// Continue the next task
+			current_task = current_task->next;
+		}
+	}
+	while (current_task->task.waiting);
+
+	if (current_task == original && original->task.waiting)
+	{
+		kpanic("All tasks are waiting for I/O. There must be at least 1 task using CPU at all times.");
+	}
+
+	switch_to_task(&current_task->task);
+}
+
+void set_waiting(int tid, bool waiting)
+{
+	asm("cli");
+
+	for (struct ll_task_i *t = first_task; t != NULL; t = t->next)
+	{
+		if (t->task.id == tid)
+		{
+			t->task.waiting = waiting;
+			break;
+		}
+	}
+
+	asm("sti");
+}
+
+void switch_task(struct registers ctx)
+{
+	if (tasks_initialized)
+		_do_switch_task(ctx);
+}
diff --git a/src/kernel/x86_32/task_api.s b/src/kernel/x86_32/task_api.s
new file mode 100644
index 0000000..fce9811
--- /dev/null
+++ b/src/kernel/x86_32/task_api.s
@@ -0,0 +1,11 @@
+	[global _switch_to_task]
+_switch_to_task:				; (uint page_directory, struct
+								; registers regs)
+	add esp, 4					; We don't care about return address
+	pop eax
+	mov cr3, eax				; Change page directories
+	pop eax
+	mov ds, ax 					; First is ds
+	popad						; Then the rest of the registers
+	add esp, 8					; Then IRQ # and error #
+	iret						; And finally the saved state
diff --git a/src/kernel/x86_32/timer.c b/src/kernel/x86_32/timer.c
new file mode 100644
index 0000000..0e52d0e
--- /dev/null
+++ b/src/kernel/x86_32/timer.c
@@ -0,0 +1,30 @@
+#include "timer.h"
+#include "io.h"
+#include "log.h"
+#include "pic.h"
+#include "registers.h"
+#include "task.h"
+
+static ulong tick = 0;
+
+static void timer_cb(struct registers *regs)
+{
+	if (tasks_initialized)
+	{
+		// Preemptive multitasking!
+		switch_task(*regs);
+	}
+}
+
+void init_timer(uint hz)
+{
+	add_interrupt_handler(IRQ_TO_INT(0), timer_cb);
+
+	uint divisor = TIMER_FREQ / hz;
+
+	outb(0x43, 0x36);
+	uchar l = divisor & 0xff, h = (divisor >> 8) & 0xff;
+
+	outb(0x40, l);
+	outb(0x40, h);
+}
diff --git a/src/kernel/x86_32/timer.h b/src/kernel/x86_32/timer.h
new file mode 100644
index 0000000..50297c4
--- /dev/null
+++ b/src/kernel/x86_32/timer.h
@@ -0,0 +1,7 @@
+#pragma once
+
+#include "kint.h"
+
+#define TIMER_FREQ 1193180
+
+void init_timer(uint hz);
diff --git a/src/kernel/x86_32/tss_flush.s b/src/kernel/x86_32/tss_flush.s
new file mode 100644
index 0000000..0da4bfc
--- /dev/null
+++ b/src/kernel/x86_32/tss_flush.s
@@ -0,0 +1,7 @@
+	[bits 32]
+	[global tss_flush]
+tss_flush:
+	mov ax, 0x2b 				; 0x28 = offset of TSS, | 0b11 to make
+								; it user-readable
+	ltr ax						; Load task register
+	ret
diff --git a/src/kernel/x86_32/vfs.c b/src/kernel/x86_32/vfs.c
new file mode 100644
index 0000000..9c0ca9a
--- /dev/null
+++ b/src/kernel/x86_32/vfs.c
@@ -0,0 +1,155 @@
+#include "vfs.h"
+#include "io.h"
+
+struct fs_node root, dev, initrd;
+struct fs_vtable root_v, dev_v;
+
+uint fs_read(struct fs_node *node, size_t offset, size_t size, uchar *buffer)
+{
+	if (node->mount)
+		node = node->mount;
+
+	if (!node || !node->vtable || !node->vtable->read)
+		return 0;
+
+	return node->vtable->read(node, offset, size, buffer);
+}
+
+uint fs_write(struct fs_node *node, size_t offset, size_t size, uchar *buffer)
+{
+	if (node->mount)
+		node = node->mount;
+
+	if (!node || !node->vtable || !node->vtable->write)
+		return 0;
+
+	return node->vtable->write(node, offset, size, buffer);
+}
+
+void fs_open(struct fs_node *node)
+{
+	if (node->mount)
+		node = node->mount;
+
+	if (!node || !node->vtable || !node->vtable->open)
+		return;
+
+	node->vtable->open(node);
+}
+
+void fs_close(struct fs_node *node)
+{
+	if (node->mount)
+		node = node->mount;
+
+	if (!node || !node->vtable || !node->vtable->close)
+		return;
+
+	node->vtable->close(node);
+}
+
+bool fs_readdir(struct fs_node *node, uint index, struct fs_dirent *out)
+{
+	if (node->mount)
+		node = node->mount;
+
+	if (!node || !node->vtable || !node->vtable->readdir ||
+		(node->flags & 7) != FS_DIRECTORY)
+		return false;
+
+	return node->vtable->readdir(node, index, out);
+}
+
+struct fs_node *fs_finddir(struct fs_node *node, char *name, uint name_len)
+{
+	if (node->mount)
+		node = node->mount;
+
+	if (!node || !node->vtable || !node->vtable->finddir ||
+		(node->flags & 7) != FS_DIRECTORY)
+		return NULL;
+
+	return node->vtable->finddir(node, name, name_len);
+}
+
+bool root_readdir(struct fs_node *node, uint index, struct fs_dirent *out)
+{
+	if (node == &root && index == 0)
+	{
+		// devfs
+		memcpy(out->name, "dev", 4);
+		out->inode = -1;
+
+		return true;
+	}
+	else
+		return false;
+}
+
+struct fs_node *root_finddir(struct fs_node *node, char *name, uint name_len)
+{
+	if (!strncmp(name, "dev", name_len))
+	{
+		return &dev;
+	}
+	else return NULL;
+}
+
+bool dev_readdir(struct fs_node *node, uint index, struct fs_dirent *out)
+{
+	if (node == &dev && index == 0)
+	{
+		// initrd
+		memcpy(out->name, "dirent", 7);
+		out->inode = -1;
+		return true;
+	}
+	else
+		return false;
+}
+
+struct fs_node *dev_finddir(struct fs_node *node, char *name, uint name_len)
+{
+	if (node != &dev)
+		return NULL;
+
+	if (!strncmp(name, "initrd", MIN(name_len, FS_MAX_NAME_LEN)))
+	{
+		return &initrd;
+	}
+
+	return NULL;
+}
+
+static struct fs_vtable root_vt =
+{
+	NULL,
+};
+
+void init_vfs()
+{
+	root.mount = NULL;
+	root.inode = 0; // fake inode
+	root.flags = FS_DIRECTORY;
+	root.mask = ~0;
+	root.gid = root.uid = 0;
+	root.size = 0;
+	root.vtable = &root_vt;
+}
+
+uint fs_mount(struct fs_node *target, struct fs_node *source)
+{
+	if (target->flags ^ FS_DIRECTORY)
+		return 1; // target is not a directory
+
+	if (target->flags & FS_MOUNT)
+		return 2; // already mounted
+
+	if (source->flags ^ FS_DIRECTORY)
+		return 3; // source is not a directory
+
+	target->flags |= FS_MOUNT;
+	target->mount = source;
+
+	return 0;
+}
diff --git a/src/kernel/x86_32/vfs_initrd.c b/src/kernel/x86_32/vfs_initrd.c
new file mode 100644
index 0000000..30d690e
--- /dev/null
+++ b/src/kernel/x86_32/vfs_initrd.c
@@ -0,0 +1,130 @@
+#include "alloc.h"
+#include "io.h"
+#include "kint.h"
+#include "log.h"
+#include "vfs.h"
+#include <initrd/initrd.h>
+
+struct initrd_fs_entry
+{
+	char *name;
+	uchar *data;
+	uint size;
+};
+
+static struct fs_node real_initrd;
+static struct fs_node *initrd_content = NULL;
+static struct initrd_fs_entry *entries = NULL;
+static struct fs_vtable initrd_v;
+static uint num_initrd_files = 0;
+
+bool initrd_readdir(struct fs_node *node, uint i, struct fs_dirent *out)
+{
+	if (node != &real_initrd)
+		return false;
+
+	if (i >= num_initrd_files)
+		return false;
+
+	memcpy(out->name, entries[i].name, strlen(entries[i].name) + 1);
+	out->inode = i;
+	return true;
+}
+
+struct fs_node *initrd_finddir(struct fs_node *node, char *name,
+							   uint name_len)
+{
+	if (node != &real_initrd)
+		return NULL;
+
+	for (int i = 0; i < num_initrd_files; i++)
+	{
+		if (strncmp(entries[i].name, name,
+					MIN(FS_MAX_NAME_LEN, name_len))
+			== 0)
+		{
+			return &initrd_content[i];
+		}
+	}
+	return NULL;
+}
+
+void initrd_open(struct fs_node *node)
+{
+}
+
+void initrd_close(struct fs_node *node)
+{
+}
+
+uint initrd_read(struct fs_node *node, size_t offset, size_t size, uchar *buffer)
+{
+	if (offset > node->size)
+		return 0;
+
+	size = MIN(size, node->size - offset);
+	memcpy(buffer, entries[node->dri_res_i].data, size);
+
+	return size;
+}
+
+void init_initrd_vfs(uchar *data)
+{
+	memset(&real_initrd, 0, sizeof(real_initrd));
+
+	real_initrd.flags = FS_DIRECTORY;
+	real_initrd.vtable = &initrd_v;
+
+	initrd_v.readdir = initrd_readdir;
+	initrd_v.finddir = initrd_finddir;
+	initrd_v.open = initrd_open;
+	initrd_v.close = initrd_close;
+	initrd_v.read = initrd_read;
+
+	// parse initrd
+	struct initrd_global_header *h = (struct initrd_global_header *)data;
+
+	if (h->magic != INITRD_MAGIC)
+	{
+		kprintf(ERROR "initrd magic is wrong: %x should be %x\n", h->magic,
+				INITRD_MAGIC);
+		kpanic("initrd magic");
+	}
+
+	num_initrd_files = h->num_files;
+	initrd_content = malloc(sizeof(struct fs_node) * num_initrd_files);
+	entries = malloc(sizeof(struct initrd_fs_entry) * num_initrd_files);
+
+	// create fs_nodes for files
+	struct initrd_file_header *f =
+		(struct initrd_file_header *)(data +
+									  sizeof(struct initrd_global_header));
+
+	for (int i = 0; i < num_initrd_files; i++)
+	{
+		uchar *data = (uchar *)((size_t)f + sizeof(struct initrd_file_header));
+
+		entries[i].data = data;
+		entries[i].name = f->name;
+		entries[i].size = f->size;
+
+		initrd_content[i] = (struct fs_node){
+			.dri_res_i = i,
+			.flags = FS_FILE,
+			.mask = 0b101001001,
+			.uid = 0,
+			.gid = 0,
+			.size = f->size,
+			.vtable = &initrd_v,
+			.mount = NULL
+		};
+
+		memcpy(initrd_content[i].name, f->name, strlen(f->name));
+
+		f = (struct initrd_file_header *)((size_t)data + f->size);
+	}
+
+	// mount initrd to /dev/initrd/
+	if (fs_mount(&initrd, &real_initrd))
+		kpanic("Failed to mount initrd to /dev/initrd");
+}
diff --git a/src/kernel/x86_32/vfs_initrd.h b/src/kernel/x86_32/vfs_initrd.h
new file mode 100644
index 0000000..a76288c
--- /dev/null
+++ b/src/kernel/x86_32/vfs_initrd.h
@@ -0,0 +1,7 @@
+#pragma once
+
+#include "kint.h"
+
+/* Note: this requires virtual memory to be set up,
+ * this WILL call malloc() */
+void init_initrd_vfs(uchar *data);
diff --git a/src/kernel/x86_32/vga.c b/src/kernel/x86_32/vga.c
new file mode 100644
index 0000000..d77313c
--- /dev/null
+++ b/src/kernel/x86_32/vga.c
@@ -0,0 +1,224 @@
+#include "vga.h"
+#include "io.h"
+#include "paging.h"
+#include "log.h"
+
+static uint cursor_x = 0;
+static uint cursor_y = 0;
+
+static uchar color = WHITE;
+
+static ushort *fb = (ushort *)PHYS_TO_VIRT(0xB8000);
+
+#define BUFFER_SIZE 16
+static char buffer[BUFFER_SIZE];
+static uint buffer_index = 0;
+static bool in_escape = false;
+
+static void move_cursor()
+{
+	ushort pos = cursor_y * 80 + cursor_x;
+
+	outb(0x3d4, 0x0e);
+	outb(0x3d5, pos >> 8);
+	outb(0x3d4, 0x0f);
+	outb(0x3d5, pos & 0xff);
+}
+
+static void scroll()
+{
+	ushort blank = ' ' | color << 8;
+
+	while (cursor_y >= 25) // end of line
+	{
+		// scroll everything up
+		memcpy(fb, &fb[80], 24 * 80 * 2);
+
+		for (int i = 24 * 80; i < 25 * 80; i++)
+		{
+			fb[i] = blank;
+		}
+
+		cursor_y--;
+	}
+}
+
+void vga_set_color(enum vga_colors fg, enum vga_colors bg)
+{
+	color = (bg << 4) | (fg & 0xf);
+}
+
+void got_escape()
+{
+	if (buffer[0] != '[')
+		return;
+
+	int c = parse_int(buffer+1);
+
+	static const char ansi_to_vga_colors[] =
+	{
+		[30] = BLACK,
+		RED,
+		GREEN,
+		LIGHT_BROWN,
+		BLUE,
+		MAGENTA,
+		CYAN,
+		WHITE,
+		[90] = LIGHT_GREY,
+		LIGHT_RED,
+		LIGHT_GREEN,
+		LIGHT_BROWN,
+		LIGHT_BLUE,
+		LIGHT_MAGENTA,
+		LIGHT_CYAN,
+		WHITE,
+	};
+
+	if (c == 0)
+	{
+		color = WHITE;
+	}
+	else if ((c >= 30 && c <= 37) || (c >= 90 && c <= 97))
+	{
+		color &= 0xf0;
+		color |= ansi_to_vga_colors[c];
+	}
+	else if ((c >= 40 && c <= 47) || (c >= 100 && c <= 107))
+	{
+		color &= 0x0f;
+		color |= ansi_to_vga_colors[c - 10] << 4;
+	}
+}
+
+void vga_put(char c)
+{
+	if (in_escape)
+	{
+		if (buffer_index >= BUFFER_SIZE || c == 'm')
+		{
+			// For now we are only supporting color escape sequences.
+			if (c == 'm')
+				got_escape();
+
+			// Escape sequence is too long, sorry. Failing silently.
+			in_escape = false;
+			buffer_index = 0;
+			memset(buffer, 0, sizeof(buffer));
+		}
+		else
+		{
+			buffer[buffer_index++] = c;
+		}
+
+		return;
+	}
+
+	switch (c)
+	{
+	case '\b':
+		if (cursor_x > 0)
+			cursor_x--;
+		fb[cursor_y * 80 + cursor_x] = ' ' | (color << 8);
+		break;
+	case '\t':
+		cursor_x = (cursor_x + 8) & ~7;
+		break;
+	case '\n':
+		cursor_y++;
+	case '\r':
+		cursor_x = 0;
+		break;
+	case 033:
+		in_escape = true;
+		break;
+	default:
+		fb[cursor_y * 80 + cursor_x] = c | (color << 8);
+		cursor_x++;
+	}
+
+	if (cursor_x >= 80) // off screen
+	{
+		cursor_x = 0;
+		cursor_y++;
+	}
+
+	scroll();
+	move_cursor();
+}
+
+void vga_clear()
+{
+	for (int i = 0; i < 25 * 80; i++)
+	{
+		fb[i] = ' ' | (WHITE << 8);
+	}
+	
+	cursor_x = 0;
+	cursor_y = 0;
+	move_cursor();
+}
+
+void vga_write(char *c)
+{
+	for (int i = 0; c[i]; i++)
+		vga_put(c[i]);
+}
+
+void vga_putd(uint d)
+{
+	char str[48];
+	memset(str, 0, 48);
+	uint i = 0;
+
+	do
+	{
+		str[i++] = (d % 10) + '0';
+		d /= 10;
+	} while (d > 0 && i < 48); // should never be more than 48 digits anyway
+
+	for (uint j = i; j; j--)
+	{
+		vga_put(str[j - 1]);
+	}
+}
+
+static bool vga_put_nibble(uchar n, bool first)
+{
+	if (first && n == 0)
+		return true;
+	else if (n <= 9)
+		vga_put('0' + n);
+	else
+		vga_put('A' + n - 10);
+
+	return false;
+}
+
+void vga_putx(uint x)
+{
+	bool first = true;
+
+	for (int shift = 24; shift >= 0; shift -= 8)
+	{
+		uchar byte = x >> shift;
+
+		first = vga_put_nibble((byte & 0xf0) >> 4, first);
+		first = vga_put_nibble(byte & 0x0f, first);
+	}
+}
+
+void init_vga()
+{
+	// Enable and set max scan line
+	outb(0x3D4, 0x09);
+	outb(0x3D5, 15);
+
+	// Cursor end line
+	outb(0x3D4, 0x0B);
+	outb(0x3D5, 15);
+
+	// Cursor start line
+	outb(0x3D4, 0x0A);
+	outb(0x3D5, 14);
+}
diff --git a/src/kernel/x86_32/vga.h b/src/kernel/x86_32/vga.h
new file mode 100644
index 0000000..d68a6d9
--- /dev/null
+++ b/src/kernel/x86_32/vga.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#include "kint.h"
+
+enum vga_colors
+{
+	BLACK = 0,
+	BLUE,
+	GREEN,
+	CYAN,
+	RED,
+	MAGENTA,
+	BROWN,
+	LIGHT_GREY,
+	DARK_GREY,
+	LIGHT_BLUE,
+	LIGHT_GREEN,
+	LIGHT_CYAN,
+	LIGHT_RED,
+	LIGHT_MAGENTA,
+	LIGHT_BROWN,
+	WHITE,
+};
+
+void vga_set_color(enum vga_colors fg, enum vga_colors bg);
+void vga_put(char c);
+void vga_putd(uint d);
+void vga_putx(uint x);
+void vga_clear();
+void vga_write(char *c);
+
+void init_vga();
diff --git a/src/kernel/x86_32/x86_32.jmk b/src/kernel/x86_32/x86_32.jmk
new file mode 100644
index 0000000..875331c
--- /dev/null
+++ b/src/kernel/x86_32/x86_32.jmk
@@ -0,0 +1,59 @@
+# -*- tcl -*-
+
+ldflags -T[pwd]/link.ld -melf_i386
+asmflags -felf -Fdwarf
+set qemuflags "-drive file=hd0_ext2.img,format=raw"
+
+depends sys $root/src/libsys libsys.a
+depends initrd $root/boot/initrd initrd.img
+depends ata_pio dri/ata_pio ata_pio.a
+depends pci dri/pci pci.a
+depends ide dri/ide ide.a
+depends ext2 dri/fs/ext2 ext2.a
+
+option FS ext2
+
+srcs boot.s \
+	main.c \
+	descriptor_tables.c \
+	io.c \
+	vga.c \
+	gdt_flush.s \
+	tss_flush.s \
+	idt.s \
+	log.c \
+	irq.c \
+	pic.c \
+	timer.c \
+	paging.c \
+	switch_table.s \
+	kheap.c \
+	alloc.c \
+	vfs.c \
+	multiboot.c \
+	vfs_initrd.c \
+	syscall.c \
+	task.c \
+	task_api.s \
+	faults.c \
+	sync.c
+
+objs [lib ext2] \
+	[lib ide] \
+	[lib ata_pio] \
+	[lib pci] \
+	[lib sys]
+
+rule debug-wait kernel.elf {
+	shell "qemu-system-i386 -s -S $::qemuflags -kernel kernel.efl"
+}
+rule debug kernel.elf {
+	shell "qemu-system-i386 -s -S $::qemuflags -kernel kernel.efl"
+	shell "echo run target remote localhost:1234 to connect to qemu"
+	shell "gdb $::first_src"
+	shell "pkill qemu-system-i386"
+}
+
+rule qemu "kernel.elf hd0_$::options(FS).img" {
+	shell "qemu-system-i386 $::qemuflags -d cpu_reset -monitor stdio -kernel kernel.elf -no-reboot"
+}