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](®s);
+ 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](®s);
+ 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(¤t_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(¤t_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"
+}