BarryServer : Git

All the code for all my projects
// BarryServer : Git / Orion / commit / d41a53cbc7d055b1c00cf0a339dbed6925f4f02c

// Related

Orion

Barry Importing existing Orion kernel d41a53c (2 years, 4 months ago)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4cae3ed
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+build/
+*.o
+orion
diff --git a/000.S b/000.S
new file mode 100644
index 0000000..6e8a1c0
--- /dev/null
+++ b/000.S
@@ -0,0 +1,25 @@
+[section .multiboot]
+[global header]
+header:
+	dd 0x1BADB002
+	dd 1 | 2
+	dd -(0x1BADB002 + (1 | 2))
+
+	dd 0, 0, 0, 0, 0
+
+[section .bss]
+stack_bottom:
+	resb 16384
+stack_top:
+
+[section .text]
+[extern kmain]
+[global _start]
+_start:
+	mov ebp, stack_top
+	mov esp, ebp
+	push ebx
+	push esp
+	call kmain
+	cli
+	jmp $
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..d0ad907
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,45 @@
+PRODUCT=orion
+
+CC=i686-orion-gcc
+CFLAGS=-ffreestanding -fno-pie #-I${SYSROOT}/usr/include/
+
+AS=nasm
+AFLAGS=-f elf32
+
+LD=i686-orion-gcc
+LFLAGS=-T linker.ld -ffreestanding -nostdlib
+
+AS_SOURCES := $(shell find . -name '*.S')
+OBJS = $(sort $(subst ./,build/,$(subst .S,.o,$(AS_SOURCES))))
+
+C_SOURCES := $(shell find . -name '*.c')
+OBJS += $(sort $(subst ./,build/,$(subst .c,.o,$(C_SOURCES))))
+
+.PHONY: clean all install
+
+all: $(PRODUCT)
+
+clean:
+	@echo "REMOVING OBJECT FILES"
+	@mkdir -p build
+	@rm -rf build
+	@touch $(PRODUCT)
+	@rm $(PRODUCT)
+
+install: $(PRODUCT)
+	@echo "INSTALLING $^"
+	@install -Dm 755 $(PRODUCT) -t ${SYSROOT}/boot/
+
+$(PRODUCT): $(OBJS)
+	@echo "LINKING $@"
+	@$(LD) -o $@ $^ $(LFLAGS)
+
+build/%.o: %.c
+	@echo "COMPILING $<"
+	@mkdir -p $(@D)
+	@$(CC) -c $< -o $@ $(CFLAGS)
+
+build/%.o: %.S
+	@echo "ASSEMBLING $<"
+	@mkdir -p $(@D)
+	@$(AS) $< -o $@ $(AFLAGS)
diff --git a/drivers/drivers.c b/drivers/drivers.c
new file mode 100644
index 0000000..5ae8c4b
--- /dev/null
+++ b/drivers/drivers.c
@@ -0,0 +1,102 @@
+/*
+ * This is the main file of the Driver core, it finds the devices attached to
+ * the system and also triggers any initialisation required for them.
+ */
+
+#include <stdint.h>
+#include <stddef.h>
+#include "drivers.h"
+#include "pci.h"
+#include "../vfs/vfs.h"
+#include "../screen.h"
+
+/* Structure for a Device */
+typedef struct Device {
+	uint16_t vendor, device;
+	char *name;
+	void (*init)(uint8_t, uint8_t, uint8_t);
+} Device;
+
+Device devices[] = {
+	/* Intel system devices */
+	{0x8086, 0x1237, "Intel i440FX Chipset", NULL},
+	{0x8086, 0x7000, "Intel PIIX3 PCI-to-ISA Bridge (Triton II)", NULL},
+	{0x8086, 0x7010, "Intel PIIX3 IDE Interface (Triton II)", ide_init},
+	{0x8086, 0x7020, "Intel PIIX3 USB (Natoma/Triton II)", NULL},
+	{0x8086, 0x7113, "Intel PIIX4/4E/4M Power Management Controller", NULL},
+
+	/* Network devices */
+	{0x10ec, 0x8139, "Realtek RTL8139 10/100 NIC", rtl8139_init},
+	{0x8086, 0x100e, "Intel Pro 1000/MT NIC", NULL},
+
+	/* VGA devices */
+	{0x1234, 0x1111, "QEMU/Bochs VBE Framebuffer", bga_init},
+};
+
+Driver *drivers = NULL;
+
+/* Register a driver */
+void
+register_driver(Driver *driver)
+{
+	Driver *prev;
+search:
+	if (!driver->major) /* Zero not allowed */
+		driver->major = 1;
+
+	/* Maintain an ordered (by major) list of drivers */
+	if (!drivers) {
+		drivers = driver;
+		return;
+	}
+	if (drivers->major > driver->major) {
+		driver->next = drivers;
+		drivers = driver;
+		return;
+	}
+
+	for (prev = drivers; prev->next; prev = prev->next) {
+		/* If major is taken, find next available slot */
+		if (prev->major == driver->major) {
+			driver->major++;
+			if (!driver->major) /* Overflow */
+				goto search;
+		}
+		if (prev->major < driver->major
+		 && prev->next->major > driver->major)
+			break;
+	}
+	driver->next = prev->next;
+	prev->next = driver;
+}
+
+/* Initialise devices */
+void
+init_drivers(void)
+{
+	uint16_t bus, slot, func;
+	uint16_t vendor, device;
+	uint32_t dev;
+	kprintf("Enumerating PCI devices");
+	for (bus = 0; bus < 256; bus++)
+	for (slot = 0; slot < 32; slot++)
+	for (func = 0; func < 8; func++)
+	if ((vendor = pci_read_word(bus, slot, func, 0)) != 0xFFFF) {
+		device = pci_read_word(bus, slot, func, 2);
+		for (dev = 0; dev < sizeof(devices)/sizeof(devices[0]); dev++) {
+			if (devices[dev].vendor == vendor
+			 && devices[dev].device == device) {
+				kprintf("  PCI(%d,%d,%d) \"%s\"",
+				        bus, slot, func, devices[dev].name);
+				if (devices[dev].init)
+					devices[dev].init(bus, slot, func);
+				break;
+			}
+		}
+		if (devices[dev].vendor != vendor
+		 || devices[dev].device != device) {
+			kprintf("  PCI(%d,%d,%d) = %#x:%#x",
+			        bus, slot, func, vendor, device);
+		}
+	}
+}
diff --git a/drivers/drivers.h b/drivers/drivers.h
new file mode 100644
index 0000000..47cd243
--- /dev/null
+++ b/drivers/drivers.h
@@ -0,0 +1,30 @@
+#ifndef KERNEL_DRIVERS_H
+#define KERNEL_DRIVERS_H
+
+#include <sys/types.h>
+#include "../vfs/vfs.h"
+
+#define MKDEV(maj, min) ((dev_t) (((maj & 0xFFFF) << 16) | (min & 0xFFFF)))
+#define MAJOR(dev) ((dev >> 16) & 0xFFFF)
+#define MINOR(dev) (dev & 0xFFFF)
+
+typedef struct Driver Driver;
+
+/* Structure for a Driver */
+struct Driver {
+	unsigned short major;
+	FileOps *ops;
+	Driver *next;
+};
+
+extern Driver *drivers;
+
+void init_drivers(void);
+void register_driver(Driver *driver);
+
+/* Drivers */
+void ide_init(uint8_t bus, uint8_t slot, uint8_t func);
+void rtl8139_init(uint8_t bus, uint8_t dev, uint8_t func);
+void bga_init(uint8_t bus, uint8_t slot, uint8_t func);
+
+#endif
diff --git a/drivers/ide/ide.c b/drivers/ide/ide.c
new file mode 100644
index 0000000..9e3b73a
--- /dev/null
+++ b/drivers/ide/ide.c
@@ -0,0 +1,685 @@
+/*
+ * This file is the driver for the PCI IDE devices, which handles access to
+ * ATA/ATAPI devices.  It detects the drives connected to the system, and
+ * handles any access to them - with the ability to do DMA if possible.  This
+ * driver will access the drives in the best available way, presenting a uniform
+ * interface for any other code to use.  It also adds a device node to the DevFS
+ * mount for every device, to provide access to the rest of the OS easily.
+ */
+
+#include <stdint.h>
+#include <stddef.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+#include "../drivers.h"
+#include "../pci.h"
+#include "../../mem/frame.h"
+#include "../../proc/proc.h"
+#include "../../vfs/vfs.h"
+#include "../../io.h"
+#include "../../screen.h"
+
+struct partition {
+	uint8_t drive;
+	uint8_t shead, ssect, scyl;
+	uint8_t type;
+	uint8_t ehead, esect, ecyl;
+	uint32_t lba;
+	uint32_t size;
+};
+
+size_t ata_read(File *file, char *buf, size_t size, off_t offset);
+size_t ata_write(File *file, char *buf, size_t size, off_t offset);
+int ata_open(File *file);
+
+FileOps ideFileOps = {
+	.read = ata_read,
+	.write = ata_write,
+	.open = ata_open,
+};
+
+Driver ideDriver = {
+	.major = 2,
+	.ops = &ideFileOps,
+	.next = NULL,
+};
+
+page_t ataDmaArea;
+
+uint8_t ide_read(uint8_t chan, uint8_t reg);
+void ide_write(uint8_t chan, uint8_t reg, uint8_t data);
+
+/* Status */
+#define ATA_SR_BSY  0x80 /* Busy */
+#define ATA_SR_DRDY 0x40 /* Drive ready */
+#define ATA_SR_DF   0x20 /* Drive write fault */
+#define ATA_SR_DSC  0x10 /* Drive seek complete */
+#define ATA_SR_DRQ  0x08 /* Data request ready */
+#define ATA_SR_CORR 0x04 /* Corrected data */
+#define ATA_SR_IDX  0x02 /* Index */
+#define ATA_SR_ERR  0x01 /* Error */
+
+/* Error */
+#define ATA_ER_BBK  0x80 /* Bad block */
+#define ATA_ER_UNC  0x40 /* Uncorrectable data */
+#define ATA_ER_MC   0x20 /* Media changed */
+#define ATA_ER_IDNF 0x10 /* ID mark not found */
+#define ATA_ER_MCR  0x08 /* Media change request */
+#define ATA_ER_ABRT 0x04 /* Command aborted */
+#define ATA_ER_T0NF 0x02 /* Track 0 not found */
+#define ATA_ER_AMNF 0x01 /* No address mark */
+
+/* Command */
+#define ATA_CMD_READ_PIO        0x20
+#define ATA_CMD_READ_PIO_EXT    0x24
+#define ATA_CMD_READ_DMA        0xC8
+#define ATA_CMD_READ_DMA_EXT    0x25
+#define ATA_CMD_WRITE_PIO       0x30
+#define ATA_CMD_WRITE_PIO_EXT   0x34
+#define ATA_CMD_WRITE_DMA       0xCA
+#define ATA_CMD_WRITE_DMA_EXT   0x35
+#define ATA_CMD_CACHE_FLUSH     0xE7
+#define ATA_CMD_CACHE_FLUSH_EXT 0xEA
+#define ATA_CMD_PACKET          0xA0
+#define ATA_CMD_IDENTIFY_PACKET 0xA1
+#define ATA_CMD_IDENTIFY        0xEC
+
+#define ATAPI_CMD_READ  0xA8
+#define ATAPI_CMD_EJECT 0x1B
+
+#define ATA_IDENT_DEVICETYPE     0
+#define ATA_IDENT_CYLINDERS      2
+#define ATA_IDENT_HEADS          6
+#define ATA_IDENT_SECTORS       12
+#define ATA_IDENT_SERIAL        20
+#define ATA_IDENT_MODEL         54
+#define ATA_IDENT_CAPABILITIES  98
+#define ATA_IDENT_FIELDVALID   106
+#define ATA_IDENT_MAX_LBA      120
+#define ATA_IDENT_COMMANDSETS  164
+#define ATA_IDENT_MAX_LBA_EXT  200
+
+#define IDE_ATA       0
+#define IDE_ATAPI     1
+#define ATA_MASTER    0
+#define ATA_SLAVE     1
+#define ATA_PRIMARY   0
+#define ATA_SECONDARY 1
+#define ATA_READ      0
+#define ATA_WRITE     1
+
+/* Registers */
+#define ATA_REG_DATA       0x0
+#define ATA_REG_ERROR      0x1
+#define ATA_REG_FEATURES   0x1
+#define ATA_REG_SECCOUNT0  0x2
+#define ATA_REG_LBA0       0x3
+#define ATA_REG_LBA1       0x4
+#define ATA_REG_LBA2       0x5
+#define ATA_REG_HDDEVSEL   0x6
+#define ATA_REG_COMMAND    0x7
+#define ATA_REG_STATUS     0x7
+#define ATA_REG_SECCOUNT1  0x8
+#define ATA_REG_LBA3       0x9
+#define ATA_REG_LBA4       0xA
+#define ATA_REG_LBA5       0xB
+#define ATA_REG_CONTROL    0xC
+#define ATA_REG_ALTSTATUS  0xC
+#define ATA_REG_DEVADDRESS 0xD
+
+struct IDEChanRegs {
+	uint16_t base;  /* IO base */
+	uint16_t ctrl;  /* Control base */
+	uint16_t bmide; /* Bus Master IDE */
+	uint8_t noint;  /* No interrupt */
+} channels[2];
+
+uint8_t ideBuf[2048] = {0};
+volatile unsigned static char ideIrqInvoked = 0;
+unsigned static char atapiPacket[12] = {0xA8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+
+struct IDEDevice {
+	uint8_t reserved;
+	uint8_t channel;
+	uint8_t drive;
+	uint16_t type;
+	uint16_t signature;
+	uint16_t capabilities;
+	uint32_t commandSets;
+	uint32_t size;
+	uint8_t model[41];
+} ideDevices[4];
+
+/* Read channel */
+uint8_t
+ide_read(uint8_t chan, uint8_t reg)
+{
+	uint8_t result;
+	if (reg > 0x07 && reg < 0x0C)
+		ide_write(chan, ATA_REG_CONTROL, 0x80 | channels[chan].noint);
+	if (reg < 0x08)
+		result = inb(channels[chan].base + reg - 0x00);
+	else if (reg < 0x0C)
+		result = inb(channels[chan].base + reg - 0x06);
+	else if (reg < 0x0E)
+		result = inb(channels[chan].ctrl + reg - 0x0A);
+	else if (reg < 0x16)
+		result = inb(channels[chan].bmide + reg - 0x0E);
+	if (reg > 0x07 && reg < 0x0C)
+		ide_write(chan, ATA_REG_CONTROL, channels[chan].noint);
+	return result;
+}
+
+/* Write channel */
+void
+ide_write(uint8_t chan, uint8_t reg, uint8_t data)
+{
+	if (reg > 0x07 && reg < 0x0C)
+		ide_write(chan, ATA_REG_CONTROL, 0x80 | channels[chan].noint);
+	if (reg < 0x08)
+		outb(channels[chan].base + reg - 0x00, data);
+	else if (reg < 0x0C)
+		outb(channels[chan].base + reg - 0x06, data);
+	else if (reg < 0x0E)
+		outb(channels[chan].ctrl + reg - 0x0A, data);
+	else if (reg < 0x16)
+		outb(channels[chan].bmide + reg - 0x0E, data);
+	if (reg > 0x07 && reg < 0x0C)
+		ide_write(chan, ATA_REG_CONTROL, channels[chan].noint);
+}
+
+/* Read identification space */
+void
+ide_read_buffer(uint8_t chan, uint8_t reg, void *buffer, uint32_t quads)
+{
+	if (reg > 0x07 && reg < 0x0C)
+		ide_write(chan, ATA_REG_CONTROL, 0x80 | channels[chan].noint);
+	asm volatile("pushw %es; movw %ds, %ax; movw %ax, %es");
+	if (reg < 0x08)
+		insl(channels[chan].base + reg - 0x00, buffer, quads);
+	else if (reg < 0x0C)
+		insl(channels[chan].base + reg - 0x06, buffer, quads);
+	else if (reg < 0x0E)
+		insl(channels[chan].ctrl + reg - 0x0A, buffer, quads);
+	else if (reg < 0x16)
+		insl(channels[chan].bmide + reg - 0x0E, buffer, quads);
+	asm volatile("popw %es");
+	if (reg > 0x07 && reg < 0x0C)
+		ide_write(chan, ATA_REG_CONTROL, channels[chan].noint);
+}
+
+/* Poll for status after command */
+uint8_t
+ide_polling(uint8_t chan, uint32_t check)
+{
+	uint8_t i, state;
+	/* Wait for BSY to set */
+	for (i = 0; i < 4; i++)
+		ide_read(chan, ATA_REG_ALTSTATUS);
+	/* Wait for BSY to clear */
+	while (ide_read(chan, ATA_REG_STATUS) & ATA_SR_BSY);
+
+	if (check) {
+		state = ide_read(chan, ATA_REG_STATUS);
+		if (state & ATA_SR_ERR)
+			return 2;
+		if (state & ATA_SR_DF)
+			return 1;
+		if ((state & ATA_SR_DRQ) == 0)
+			return 3;
+	}
+
+	return 0;
+}
+
+/* Print an IDE error */
+uint8_t
+ide_print_error(uint32_t drive, uint8_t err)
+{
+	if (err == 0)
+		return err;
+
+	uint8_t st;
+	kprintf("IDE:");
+	if (err == 1) {
+		kprintf(" - Device fault");
+		err = 19;
+	} else if (err == 2) {
+		st = ide_read(ideDevices[drive].channel, ATA_REG_ERROR);
+		if (st & ATA_ER_AMNF) {
+			kprintf(" - No address mark found");
+			err = 7;
+		}
+		if (st & ATA_ER_T0NF) {
+			kprintf(" - No media or media error");
+			err = 3;
+		}
+		if (st & ATA_ER_ABRT) {
+			kprintf(" - Command aborted");
+			err = 20;
+		}
+		if (st & ATA_ER_MCR) {
+			kprintf(" - No media or media error");
+			err = 3;
+		}
+		if (st & ATA_ER_IDNF) {
+			kprintf(" - ID mark not found");
+			err = 21;
+		}
+		if (st & ATA_ER_MC) {
+			kprintf(" - No media or media error");
+			err = 3;
+		}
+		if (st & ATA_ER_UNC) {
+			kprintf(" - Uncorrectable data error");
+			err = 22;
+		}
+		if (st & ATA_ER_BBK) {
+			kprintf(" - Bad sectors");
+			err = 13;
+		}
+	} else if (err == 3) {
+		kprintf(" - Read nothing");
+		err = 23;
+	} else if (err == 4) {
+		kprintf(" - Write protected");
+		err = 8;
+	}
+	kprintf(" - [%s %s] %s",
+	        (char *[]){"Primary","Secondary"}[ideDevices[drive].channel],
+	        (char *[]){"Master","Slave"}[ideDevices[drive].drive],
+	        ideDevices[drive].model);
+	return err;
+}
+
+/* Initialise an IDE drive */
+void
+ide_drive_init(uint32_t bar0, uint32_t bar1, uint32_t bar2,
+               uint32_t bar3, uint32_t bar4)
+{
+	channels[ATA_PRIMARY].base = (bar0 & ~3) + 0x1F0 * (!bar0);
+	channels[ATA_PRIMARY].ctrl = (bar1 & ~3) + 0x3F6 * (!bar1);
+	channels[ATA_SECONDARY].base = (bar2 & ~3) + 0x170 * (!bar2);
+	channels[ATA_SECONDARY].ctrl = (bar3 & ~3) + 0x376 * (!bar3);
+	channels[ATA_PRIMARY].bmide = (bar4 & ~3) + 0;
+	channels[ATA_SECONDARY].bmide = (bar4 & ~3) + 8;
+	/* Disable IRQs */
+	ide_write(ATA_PRIMARY, ATA_REG_CONTROL, 2);
+	ide_write(ATA_SECONDARY, ATA_REG_CONTROL, 2);
+
+	uint32_t count = 0;
+	uint8_t i, j, k, err, type, status;
+	for (i = 0; i < 2; i++)
+	for (j = 0; j < 2; j++) {
+		err = 0;
+		type = IDE_ATA;
+		ideDevices[count].reserved = 0;
+		ide_write(i, ATA_REG_HDDEVSEL, 0xA0 | (j << 4));
+		sleep(1);
+		ide_write(i, ATA_REG_COMMAND, ATA_CMD_IDENTIFY);
+		sleep(1);
+		if (ide_read(i, ATA_REG_STATUS) == 0)
+			continue;
+
+		while (1) {
+			status = ide_read(i, ATA_REG_STATUS);
+			if ((status & ATA_SR_ERR)) {
+				err = 1;
+				break;
+			}
+			if (!(status & ATA_SR_BSY) && (status & ATA_SR_DRQ))
+				break;
+		}
+
+		if (err) {
+			uint8_t cl = ide_read(i, ATA_REG_LBA1);
+			uint8_t ch = ide_read(i, ATA_REG_LBA2);
+
+			if (cl == 0x14 && ch == 0xEB)
+				type = IDE_ATAPI;
+			else if (cl == 0x69 && ch == 0x96)
+				type = IDE_ATAPI;
+			else
+				continue;
+
+			ide_write(i, ATA_REG_COMMAND, ATA_CMD_IDENTIFY_PACKET);
+			sleep(1);
+		}
+
+		ide_read_buffer(i, ATA_REG_DATA, ideBuf, 128);
+
+		uint16_t sig, cap, set;
+		sig = *((uint16_t *) (ideBuf + ATA_IDENT_DEVICETYPE));
+		cap = *((uint16_t *) (ideBuf + ATA_IDENT_CAPABILITIES));
+		set = *((uint16_t *) (ideBuf + ATA_IDENT_COMMANDSETS));
+		ideDevices[count].reserved = 1;
+		ideDevices[count].type = type;
+		ideDevices[count].channel = i;
+		ideDevices[count].drive = j;
+		ideDevices[count].signature = sig;
+		ideDevices[count].capabilities = cap;
+		ideDevices[count].commandSets = set;
+
+		uint32_t size;
+		if (ideDevices[count].commandSets & (1 << 26))
+			size = *((uint32_t *) (ideBuf + ATA_IDENT_MAX_LBA_EXT));
+		else
+			size = *((uint32_t *) (ideBuf + ATA_IDENT_MAX_LBA));
+		ideDevices[count].size = size;
+		for (k = 0; k < 40; k += 2) {
+			ideDevices[count].model[k] =
+				ideBuf[ATA_IDENT_MODEL + k + 1];
+			ideDevices[count].model[k + 1] =
+				ideBuf[ATA_IDENT_MODEL + k];
+		}
+		ideDevices[count].model[40] = 0;
+		count++;
+	}
+}
+
+/* Wait for an IRQ to arrive */
+void
+ide_wait_irq(void)
+{
+	while (!ideIrqInvoked);
+	ideIrqInvoked = 0;
+}
+
+/* Access an ATA drive */
+uint8_t
+ide_ata_access(uint8_t write, uint8_t drive, uint32_t lba, uint8_t sectors,
+               uint16_t selector, uint32_t edi)
+{
+	uint8_t lbaMode, dma, cmd;
+	uint8_t lbaIO[6];
+	uint32_t chan = ideDevices[drive].channel;
+	uint32_t slave = ideDevices[drive].drive;
+	uint32_t bus = channels[chan].base;
+	uint32_t words = 256;
+	uint16_t cyl, i;
+	uint8_t head, sect, err;
+
+	ide_write(chan, ATA_REG_CONTROL,
+	          channels[chan].noint = (ideIrqInvoked = 0) + 2);
+
+	if (lba >= 0x10000000) {
+		/* LBA48 */
+		lbaMode = 2;
+		lbaIO[0] = (lba & 0x000000FF) >> 0;
+		lbaIO[1] = (lba & 0x0000FF00) >> 8;
+		lbaIO[2] = (lba & 0x00FF0000) >> 16;
+		lbaIO[3] = (lba & 0xFF000000) >> 24;
+		lbaIO[4] = 0;
+		lbaIO[5] = 0;
+		head = 0;
+	} else if (ideDevices[drive].capabilities & 0x200) {
+		/* LBA28 */
+		lbaMode = 1;
+		lbaIO[0] = (lba & 0x000000FF) >> 0;
+		lbaIO[1] = (lba & 0x0000FF00) >> 8;
+		lbaIO[2] = (lba & 0x00FF0000) >> 16;
+		lbaIO[3] = 0;
+		lbaIO[4] = 0;
+		lbaIO[5] = 0;
+		head = (lba & 0x0F000000) >> 24;
+	} else {
+		/* CHS */
+		lbaMode = 0;
+		sect = (lba % 63) + 1;
+		cyl = (lba + 1 - sect) / (16 * 63);
+		lbaIO[0] = sect;
+		lbaIO[1] = (cyl >> 0) & 0xFF;
+		lbaIO[2] = (cyl >> 8) & 0xFF;
+		lbaIO[3] = 0;
+		lbaIO[4] = 0;
+		lbaIO[5] = 0;
+		head = (lba + 1 - sect) % (16 * 63) / 63;
+	}
+
+	dma = 0; /* XXX */
+
+	while (ide_read(chan, ATA_REG_STATUS) & ATA_SR_BSY);
+
+	if (lbaMode == 0)
+		ide_write(chan, ATA_REG_HDDEVSEL, 0xA0 | (slave << 4) | head);
+	else
+		ide_write(chan, ATA_REG_HDDEVSEL, 0xE0 | (slave << 4) | head);
+
+	if (lbaMode == 2) {
+		ide_write(chan, ATA_REG_SECCOUNT1, 0);
+		ide_write(chan, ATA_REG_LBA3, lbaIO[3]);
+		ide_write(chan, ATA_REG_LBA4, lbaIO[4]);
+		ide_write(chan, ATA_REG_LBA5, lbaIO[5]);
+	}
+	ide_write(chan, ATA_REG_SECCOUNT0, sectors);
+	ide_write(chan, ATA_REG_LBA0, lbaIO[0]);
+	ide_write(chan, ATA_REG_LBA1, lbaIO[1]);
+	ide_write(chan, ATA_REG_LBA2, lbaIO[2]);
+
+	if (lbaMode == 0 && !dma && !write) cmd = ATA_CMD_READ_PIO;
+	if (lbaMode == 1 && !dma && !write) cmd = ATA_CMD_READ_PIO;
+	if (lbaMode == 2 && !dma && !write) cmd = ATA_CMD_READ_PIO_EXT;
+	if (lbaMode == 0 &&  dma && !write) cmd = ATA_CMD_READ_DMA;
+	if (lbaMode == 1 &&  dma && !write) cmd = ATA_CMD_READ_DMA;
+	if (lbaMode == 2 &&  dma && !write) cmd = ATA_CMD_READ_DMA_EXT;
+	if (lbaMode == 0 && !dma &&  write) cmd = ATA_CMD_WRITE_PIO;
+	if (lbaMode == 1 && !dma &&  write) cmd = ATA_CMD_WRITE_PIO;
+	if (lbaMode == 2 && !dma &&  write) cmd = ATA_CMD_WRITE_PIO_EXT;
+	if (lbaMode == 0 &&  dma &&  write) cmd = ATA_CMD_WRITE_DMA;
+	if (lbaMode == 1 &&  dma &&  write) cmd = ATA_CMD_WRITE_DMA;
+	if (lbaMode == 2 &&  dma &&  write) cmd = ATA_CMD_WRITE_DMA_EXT;
+	ide_write(chan, ATA_REG_COMMAND, cmd);
+
+	if (dma) {
+		if (write) {
+			/* TODO */
+		} else {
+			/* TODO */
+		}
+	} else {
+		if (write) {
+			for (i = 0; i < sectors; i++) {
+				ide_polling(chan, 0);
+				outsw(bus, (void *) edi, words);
+			}
+			ide_write(chan, ATA_REG_COMMAND, (uint8_t []) {
+				ATA_CMD_CACHE_FLUSH,
+				ATA_CMD_CACHE_FLUSH,
+				ATA_CMD_CACHE_FLUSH_EXT,
+			}[lbaMode]);
+			ide_polling(chan, 0);
+		} else {
+			for (i = 0; i < sectors; i++) {
+				if (err = ide_polling(chan, 1))
+					return err;
+				insw(bus, (void *) edi, words);
+				edi += words * 2;
+			}
+		}
+	}
+
+	return 0;
+}
+
+/* IDE IRQ handler */
+void
+ide_irq(InterruptFrame *frame)
+{
+	ideIrqInvoked = 1;
+}
+
+/* Initialise an IDE drive */
+void
+ide_init(uint8_t bus, uint8_t slot, uint8_t func)
+{
+	register_driver(&ideDriver);
+
+	uint8_t class, subclass, progif, header;
+	class = pci_read_byte(bus, slot, func, 11);
+	subclass = pci_read_byte(bus, slot, func, 10);
+	progif = pci_read_byte(bus, slot, func, 9);
+	header = pci_read_byte(bus, slot, func, 14);
+
+	uint8_t primaryNative = 0, primaryChange = 0,
+	        secondaryNative = 0, secondaryChange = 0,
+	        busMaster = 0;
+	if (progif & (1 << 0))
+		primaryNative = 1;
+	if (progif & (1 << 1))
+		primaryChange = 1;
+	if (progif & (1 << 2))
+		secondaryNative = 1;
+	if (progif & (1 << 3))
+		secondaryChange = 1;
+	if (progif & (1 << 7))
+		busMaster = 1;
+
+	uint32_t bar0 = 0x1F0, bar1 = 0x3F6,
+	         bar2 = 0x170, bar3 = 0x376,
+	         bar4 = 0x00;
+
+	if (primaryNative) {
+		bar0 = pci_read_dword(bus, slot, func, 0x10);
+		bar1 = pci_read_dword(bus, slot, func, 0x14);
+	}
+	if (secondaryNative) {
+		bar2 = pci_read_dword(bus, slot, func, 0x18);
+		bar3 = pci_read_dword(bus, slot, func, 0x1C);
+	}
+	if (busMaster)
+		bar4 = pci_read_dword(bus, slot, func, 0x20);
+
+	register_interrupt(14, ide_irq);
+	ataDmaArea = alloc_frames(1);
+
+	ide_drive_init(0x1F0, 0x3F6, 0x170, 0x376, 0x00);
+	uint8_t i, j;
+	char name[16];
+	for (i = 0; i < 4; i++) {
+		if (!ideDevices[i].reserved)
+			continue;
+		sprintf(name, "/dev/%cd%c",
+		        (char []){'h','c'}[ideDevices[i].type],
+		        (char []){'a','b'}[ideDevices[i].channel]);
+		mknod(name, S_IFBLK | 0600, MKDEV(ideDriver.major, i * 16));
+		kprintf("    %s drive [%s] %d MB - %s",
+		        (char *[]){"ATA", "ATAPI"}[ideDevices[i].type],
+		        name, ideDevices[i].size / 1024 / 2,
+		        ideDevices[i].model);
+		/* Attempt to read partition table */
+		if (ideDevices[i].type != 0)
+			continue;
+		ide_ata_access(0, i, 0, 0x02, 1, ataDmaArea);
+		struct partition *part;
+		for (j = 0; j < 4; j++) {
+			part = (void *) ataDmaArea + 446 + (j * 16);
+			if (!part->type)
+				continue;
+			sprintf(name, "/dev/%cd%c%d",
+			        (char []){'h','c'}[ideDevices[i].type],
+			        (char []){'a','b'}[ideDevices[i].channel],
+			        j + 1);
+			int asdf = mknod(name, S_IFBLK | 0600,
+			      MKDEV(ideDriver.major, (i * 16) + (j + 1)));
+			kprintf("      Partition %d: %#.8x - %#.8x",
+			        j+1, part->lba*512,
+			        (part->lba + part->size) * 512);
+		}
+	}
+}
+
+/* Read a drive */
+size_t
+ata_read(File *file, char *buf, size_t size, off_t offset)
+{
+	uint16_t min, bufOffset = offset % 512;
+	size_t count = 0;
+	uint16_t dev = MINOR(file->inode->dev) / 16,
+	         parti = MINOR(file->inode->dev) % 16;
+	uint32_t lba;
+	struct partition *part;
+	if (parti) {
+		ide_ata_access(0, dev, 0, 1, 0x02, ataDmaArea);
+		part = (void *) ataDmaArea + 446 + ((parti - 1) * 16);
+		if (offset > (part->lba + part->size) * 512)
+			return 0;
+		if (offset + size > (part->lba + part->size) * 512)
+			size = ((part->lba + part->size) * 512) - offset;
+	} else {
+		if (offset > ideDevices[dev].size * 512)
+			return 0;
+		if (offset + size > ideDevices[dev].size * 512)
+			size = (ideDevices[dev].size * 512) - offset;
+	}
+	while (size + bufOffset) {
+		min = (size > 0x1000) ? 0x1000 : size;
+		lba = (parti ? part->lba : 0) + ((offset + count) / 512);
+		ide_ata_access(0, dev, lba, 8, 0x02, ataDmaArea);
+		memcpy((void *) (buf + count),
+		       (void *) ataDmaArea + bufOffset,
+		       min);
+		size -= min;
+		count += min;
+		bufOffset = 0;
+	}
+	return count;
+}
+
+/* Write a drive */
+size_t
+ata_write(File *file, char *buf, size_t size, off_t offset)
+{
+	uint16_t min;
+	size_t count = 0;
+	uint16_t dev = MINOR(file->inode->dev) / 16,
+	         parti = MINOR(file->inode->dev) % 16;
+	uint32_t lba;
+	struct partition *part;
+	if (parti) {
+		ide_ata_access(0, dev, 0, 1, 0x02, ataDmaArea);
+		part = (void *) ataDmaArea + 446 + ((parti - 1) * 16);
+		if (offset > (part->lba + part->size) * 512)
+			return 0;
+		if (offset + size > (part->lba + part->size) * 512)
+			size = ((part->lba + part->size) * 512) - offset;
+	} else {
+		if (offset > ideDevices[dev].size * 512)
+			return 0;
+		if (offset + size > ideDevices[dev].size * 512)
+			size = (ideDevices[dev].size * 512) - offset;
+	}
+	while (size) {
+		min = (size > 0x1000) ? 0x1000 : size;
+		memcpy((void *) ataDmaArea,
+//		       (void *) (((uint32_t) (buf + count) / 512) * 512),
+		       (void *) (buf + count),
+		       min);
+		lba = (parti ? part->lba : 0) + ((offset + count) / 512);
+		ide_ata_access(1, dev, lba, 8, 0x02, ataDmaArea);
+		size -= min;
+		count += min;
+	}
+	return count;
+	/*
+	 * TODO: Currently, offset is sector-aligned, which we don't always
+	 *       want.  This should be fixed by basically checking the offset
+	 *       for this and reading into the dma buffer before we write to the
+	 *       adjusted offset in the buffer, this way the surrounding data
+	 *       isn't deleted by accident with bad alignment.
+	 */
+}
+
+/* Open a drive */
+int
+ata_open(File *file)
+{
+	if (ideDevices[MINOR(file->inode->dev)/16].reserved)
+		return 0;
+	if (ideDevices[MINOR(file->inode->dev)/16].type != 0)
+		return -EINVAL;
+	return -ENODEV;
+}
+
diff --git a/drivers/net/rtl8139.c b/drivers/net/rtl8139.c
new file mode 100644
index 0000000..e801845
--- /dev/null
+++ b/drivers/net/rtl8139.c
@@ -0,0 +1,148 @@
+/*
+ * The is the RTL8139 driver.  It is a simple driver for a simple network card.
+ * It will just abstract the hardware away, and allows the network system to
+ * utilise it
+ */
+
+#include <stdint.h>
+#include <stddef.h>
+#include <string.h>
+#include "../pci.h"
+#include "../../mem/heap.h"
+#include "../../proc/proc.h"
+#include "../../net/net.h"
+#include "../../io.h"
+#include "../../screen.h"
+
+#define RTL_PORT_MAC     0x00
+#define RTL_PORT_RX_PTR  0x38
+#define RTL_PORT_RBSTART 0x30
+#define RTL_PORT_IMR     0x3C
+#define RTL_PORT_ISR     0x3E
+#define RTL_PORT_CMD     0x37
+#define RTL_PORT_RXMISS  0x4C
+#define RTL_PORT_TCR     0x40
+#define RTL_PORT_RCR     0x44
+#define RTL_PORT_CONFIG  0x52
+#define RTL_RX_BUF_SIZE  (8192+16+1500)
+#define RTL_TX_BUF_SIZE  (1536)
+
+#define RTL_CFG_AAP (1 << 0) /* Accept all packets */
+#define RTL_CFG_APM (1 << 1) /* Accept packets to NIC's MAC address */
+#define RTL_CFG_AM  (1 << 2) /* Accept multicast packets */
+#define RTL_CFG_AB  (1 << 3) /* Accept broadcast packets */
+
+#define RTL_ISR_ROK (1 << 0) /* Receive Okay */
+#define RTL_ISR_TOK (1 << 2) /* Transmit Okay */
+
+static NetIF *netif;
+
+void *txBuf, *rxBuf;
+uint8_t txIndex = 0;
+uint32_t rxIndex = 0;
+uint16_t port;
+
+uint16_t txStartRegs[] = {0x20, 0x24, 0x28, 0x2C};
+uint16_t txControlRegs[] = {0x10, 0x14, 0x18, 0x1C};
+
+void rtl8139_transmit(void *data, size_t len);
+void rtl8139_receive(void);
+
+/* Handle the RTL8139 interrupt */
+void
+rtl8139_irq(InterruptFrame *frame)
+{
+	uint16_t status;
+	/* Loop until everything has been handled */
+	while (1) {
+		status = inw(port + RTL_PORT_ISR);
+		outw(port + RTL_PORT_ISR, status);
+		if (!status)
+			break;
+		if (status & RTL_ISR_ROK)
+			rtl8139_receive();
+	}
+}
+
+/* Transmit a frame */
+void
+rtl8139_transmit(void *data, size_t len)
+{
+	memcpy(txBuf, data, len);
+	outl(port + txStartRegs[txIndex], (uintptr_t) txBuf);
+	outl(port + txControlRegs[txIndex], len);
+	txIndex++;
+	if (txIndex > 3)
+		txIndex = 0;
+}
+
+/* Receive a frame */
+void
+rtl8139_receive(void)
+{
+	uint8_t *frame, *buf = rxBuf;
+	uint32_t index = rxIndex;
+	uint32_t offset, len;
+
+	/* While the buffer is not empty */
+	while ((inb(port + RTL_PORT_CMD) & 0x01) == 0) {
+		offset = index % RTL_RX_BUF_SIZE;
+		len = (buf[3 + index] << 8) + buf[2 + index];
+
+		frame = kmalloc(len);
+		memcpy(frame, &buf[offset + 4], len);
+		ethernet_receive_frame(netif, frame, len);
+		kfree(frame);
+
+		index = (index + len + 7) & ~3;
+		outw(port + RTL_PORT_RX_PTR, index - 16);
+	}
+
+	rxIndex = index;
+}
+
+/* Get the RTL8139 MAC Address */
+void
+rtl8139_get_mac(char *buf)
+{
+	uint8_t i;
+	for (i = 0; i < 6; i++)
+		buf[i] = inb(port + RTL_PORT_MAC + i);
+}
+
+/* Initialise the RTL8139 Network card */
+void
+rtl8139_init(uint8_t bus, uint8_t dev, uint8_t func)
+{
+	/* Enable bus mastering */
+	uint16_t command = pci_read_word(bus, dev, func, 4);
+	if (!(command & (1 << 2)))
+		pci_write_word(bus, dev, func, 4, command | (1 << 2));
+
+	/* Turn device on */
+	port = pci_read_word(bus, dev, func, 0x10) & ~3;
+	outb(port + RTL_PORT_CONFIG, 0x00);
+	/* Software reset */
+	outb(port + RTL_PORT_CMD, 0x10);
+	while ((inb(port + RTL_PORT_CMD) & 0x10) != 0);
+	/* Initialise buffers */
+	txBuf = kmalloc(RTL_RX_BUF_SIZE);
+	rxBuf = kmalloc(RTL_TX_BUF_SIZE);
+	/* TODO: use dedicated DMA area */
+	outl(port + RTL_PORT_RBSTART, (uintptr_t) rxBuf);
+	/* IMR & ISR */
+	outw(port + RTL_PORT_IMR, 0x05);
+
+	/* Configure receive buffer */
+	outl(port + RTL_PORT_RCR, (1 << 7) |
+	     RTL_CFG_AB | RTL_CFG_AM | RTL_CFG_APM | RTL_CFG_AAP);
+	/* Enable RX and TX */
+	outb(port + RTL_PORT_CMD, 0x0C);
+
+	/* Register interrupt handler */
+	uint8_t irq = pci_read_byte(bus, dev, func, 0x3C);
+	register_interrupt(irq, rtl8139_irq);
+
+	/* Setup Network Interface */
+	netif = net_create_interface(1, rtl8139_transmit, rtl8139_get_mac);
+}
diff --git a/drivers/pci.c b/drivers/pci.c
new file mode 100644
index 0000000..22db76e
--- /dev/null
+++ b/drivers/pci.c
@@ -0,0 +1,55 @@
+/*
+ * This file contains the routines for accessing the PCI devices.  It just wraps
+ * the IO functions necessary to read/write the PCI Configuration Space.  It
+ * uses PCI Configuration Space Access Mechanism #1, which is the most supported
+ * access mechanism.
+ */
+
+#include <stdint.h>
+#include "pci.h"
+#include "../io.h"
+
+/* Read PCI config */
+uint8_t
+pci_read_byte(int bus, int dev, int func, int off)
+{
+	outl(0xCF8, (1 << 31) | (bus << 16) | (dev << 11) |
+	            (func << 8) | (off & 0xFC));
+	return inb(0xCFC + (off & 3));
+}
+uint16_t
+pci_read_word(int bus, int dev, int func, int off)
+{
+	outl(0xCF8, (1 << 31) | (bus << 16) | (dev << 11) |
+	            (func << 8) | (off & 0xFC));
+	return inw(0xCFC + (off & 2));
+}
+uint32_t
+pci_read_dword(int bus, int dev, int func, int off)
+{
+	outl(0xCF8, (1 << 31) | (bus << 16) | (dev << 11) |
+	            (func << 8) | (off & 0xFC));
+	return inl(0xCFC);
+}
+/* Write PCI config */
+void
+pci_write_byte(int bus, int dev, int func, int off, uint8_t value)
+{
+	outl(0xCF8, (1 << 31) | (bus << 16) | (dev << 11) |
+	            (func << 8) | (off & 0xFC));
+	outb(0xCFC + (off & 3), value);
+}
+void
+pci_write_word(int bus, int dev, int func, int off, uint16_t value)
+{
+	outl(0xCF8, (1 << 31) | (bus << 16) | (dev << 11) |
+	            (func << 8) | (off & 0xFC));
+	outw(0xCFC + (off & 2), value);
+}
+void
+pci_write_dword(int bus, int dev, int func, int off, uint32_t value)
+{
+	outl(0xCF8, (1 << 31) | (bus << 16) | (dev << 11) |
+	            (func << 8) | (off & 0xFC));
+	outl(0xCFC, value);
+}
diff --git a/drivers/pci.h b/drivers/pci.h
new file mode 100644
index 0000000..44b65ae
--- /dev/null
+++ b/drivers/pci.h
@@ -0,0 +1,11 @@
+#ifndef KERNEL_PCI_H
+#define KERNEL_PCI_H
+
+uint8_t pci_read_byte(int bus, int dev, int func, int off);
+uint16_t pci_read_word(int bus, int dev, int func, int off);
+uint32_t pci_read_dword(int bus, int dev, int func, int off);
+void pci_write_byte(int bus, int dev, int func, int off, uint8_t value);
+void pci_write_word(int bus, int dev, int func, int off, uint16_t value);
+void pci_write_dword(int bus, int dev, int func, int off, uint32_t value);
+
+#endif
diff --git a/drivers/tty/font.c b/drivers/tty/font.c
new file mode 100644
index 0000000..bb35673
--- /dev/null
+++ b/drivers/tty/font.c
@@ -0,0 +1,260 @@
+#include <stdint.h>
+
+uint8_t font[4096] = {
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x7e, 0x81, 0xa5, 0x81, 0x81, 0xbd, 0x99, 0x81, 0x81, 0x7e, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x7e, 0xff, 0xdb, 0xff, 0xff, 0xc3, 0xe7, 0xff, 0xff, 0x7e, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x6c, 0xfe, 0xfe, 0xfe, 0xfe, 0x7c, 0x38, 0x10, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x10, 0x38, 0x7c, 0xfe, 0x7c, 0x38, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x18, 0x3c, 0x3c, 0xe7, 0xe7, 0xe7, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x18, 0x3c, 0x7e, 0xff, 0xff, 0x7e, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x3c, 0x3c, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe7, 0xc3, 0xc3, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x42, 0x42, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xc3, 0x99, 0xbd, 0xbd, 0x99, 0xc3, 0xff, 0xff, 0xff, 0xff, 0xff,
+	0x00, 0x00, 0x1e, 0x0e, 0x1a, 0x32, 0x78, 0xcc, 0xcc, 0xcc, 0xcc, 0x78, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x18, 0x7e, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x3f, 0x33, 0x3f, 0x30, 0x30, 0x30, 0x30, 0x70, 0xf0, 0xe0, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x7f, 0x63, 0x7f, 0x63, 0x63, 0x63, 0x63, 0x67, 0xe7, 0xe6, 0xc0, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x18, 0x18, 0xdb, 0x3c, 0xe7, 0x3c, 0xdb, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfe, 0xf8, 0xf0, 0xe0, 0xc0, 0x80, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x02, 0x06, 0x0e, 0x1e, 0x3e, 0xfe, 0x3e, 0x1e, 0x0e, 0x06, 0x02, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x18, 0x3c, 0x7e, 0x18, 0x18, 0x18, 0x7e, 0x3c, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x7f, 0xdb, 0xdb, 0xdb, 0x7b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x7c, 0xc6, 0x60, 0x38, 0x6c, 0xc6, 0xc6, 0x6c, 0x38, 0x0c, 0xc6, 0x7c, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xfe, 0xfe, 0xfe, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x18, 0x3c, 0x7e, 0x18, 0x18, 0x18, 0x7e, 0x3c, 0x18, 0x7e, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x18, 0x3c, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x3c, 0x18, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x0c, 0xfe, 0x0c, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x60, 0xfe, 0x60, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xc0, 0xc0, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x66, 0xff, 0x66, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x10, 0x38, 0x38, 0x7c, 0x7c, 0xfe, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0xfe, 0xfe, 0x7c, 0x7c, 0x38, 0x38, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x18, 0x3c, 0x3c, 0x3c, 0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x66, 0x66, 0x66, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x6c, 0x6c, 0xfe, 0x6c, 0x6c, 0x6c, 0xfe, 0x6c, 0x6c, 0x00, 0x00, 0x00, 0x00,
+	0x18, 0x18, 0x7c, 0xc6, 0xc2, 0xc0, 0x7c, 0x06, 0x06, 0x86, 0xc6, 0x7c, 0x18, 0x18, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0xc2, 0xc6, 0x0c, 0x18, 0x30, 0x60, 0xc6, 0x86, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x38, 0x6c, 0x6c, 0x38, 0x76, 0xdc, 0xcc, 0xcc, 0xcc, 0x76, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x30, 0x30, 0x30, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x0c, 0x18, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x18, 0x0c, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x30, 0x18, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x18, 0x30, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x3c, 0xff, 0x3c, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x7e, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x30, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x02, 0x06, 0x0c, 0x18, 0x30, 0x60, 0xc0, 0x80, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x3c, 0x66, 0xc3, 0xc3, 0xdb, 0xdb, 0xc3, 0xc3, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x18, 0x38, 0x78, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x7c, 0xc6, 0x06, 0x0c, 0x18, 0x30, 0x60, 0xc0, 0xc6, 0xfe, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x7c, 0xc6, 0x06, 0x06, 0x3c, 0x06, 0x06, 0x06, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x0c, 0x1c, 0x3c, 0x6c, 0xcc, 0xfe, 0x0c, 0x0c, 0x0c, 0x1e, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0xfe, 0xc0, 0xc0, 0xc0, 0xfc, 0x06, 0x06, 0x06, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x38, 0x60, 0xc0, 0xc0, 0xfc, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0xfe, 0xc6, 0x06, 0x06, 0x0c, 0x18, 0x30, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0x7e, 0x06, 0x06, 0x06, 0x0c, 0x78, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x18, 0x18, 0x30, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x06, 0x0c, 0x18, 0x30, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x0c, 0x18, 0x30, 0x60, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x7c, 0xc6, 0xc6, 0x0c, 0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xde, 0xde, 0xde, 0xdc, 0xc0, 0x7c, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x10, 0x38, 0x6c, 0xc6, 0xc6, 0xfe, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0xfc, 0x66, 0x66, 0x66, 0x7c, 0x66, 0x66, 0x66, 0x66, 0xfc, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x3c, 0x66, 0xc2, 0xc0, 0xc0, 0xc0, 0xc0, 0xc2, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0xf8, 0x6c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x6c, 0xf8, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0xfe, 0x66, 0x62, 0x68, 0x78, 0x68, 0x60, 0x62, 0x66, 0xfe, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0xfe, 0x66, 0x62, 0x68, 0x78, 0x68, 0x60, 0x60, 0x60, 0xf0, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x3c, 0x66, 0xc2, 0xc0, 0xc0, 0xde, 0xc6, 0xc6, 0x66, 0x3a, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xfe, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x3c, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x1e, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0xcc, 0xcc, 0xcc, 0x78, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0xe6, 0x66, 0x66, 0x6c, 0x78, 0x78, 0x6c, 0x66, 0x66, 0xe6, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0xf0, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x62, 0x66, 0xfe, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0xc3, 0xe7, 0xff, 0xff, 0xdb, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0xc6, 0xe6, 0xf6, 0xfe, 0xde, 0xce, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0xfc, 0x66, 0x66, 0x66, 0x7c, 0x60, 0x60, 0x60, 0x60, 0xf0, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xd6, 0xde, 0x7c, 0x0c, 0x0e, 0x00, 0x00,
+	0x00, 0x00, 0xfc, 0x66, 0x66, 0x66, 0x7c, 0x6c, 0x66, 0x66, 0x66, 0xe6, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x7c, 0xc6, 0xc6, 0x60, 0x38, 0x0c, 0x06, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0xff, 0xdb, 0x99, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0x66, 0x3c, 0x18, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xdb, 0xdb, 0xff, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0xc3, 0xc3, 0x66, 0x3c, 0x18, 0x18, 0x3c, 0x66, 0xc3, 0xc3, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0xc3, 0xc3, 0xc3, 0x66, 0x3c, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0xff, 0xc3, 0x86, 0x0c, 0x18, 0x30, 0x60, 0xc1, 0xc3, 0xff, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x3c, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3c, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x80, 0xc0, 0xe0, 0x70, 0x38, 0x1c, 0x0e, 0x06, 0x02, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x3c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x3c, 0x00, 0x00, 0x00, 0x00,
+	0x10, 0x38, 0x6c, 0xc6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00,
+	0x30, 0x30, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x0c, 0x7c, 0xcc, 0xcc, 0xcc, 0x76, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0xe0, 0x60, 0x60, 0x78, 0x6c, 0x66, 0x66, 0x66, 0x66, 0x7c, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0xc6, 0xc0, 0xc0, 0xc0, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x1c, 0x0c, 0x0c, 0x3c, 0x6c, 0xcc, 0xcc, 0xcc, 0xcc, 0x76, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0xc6, 0xfe, 0xc0, 0xc0, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x38, 0x6c, 0x64, 0x60, 0xf0, 0x60, 0x60, 0x60, 0x60, 0xf0, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0x7c, 0x0c, 0xcc, 0x78, 0x00,
+	0x00, 0x00, 0xe0, 0x60, 0x60, 0x6c, 0x76, 0x66, 0x66, 0x66, 0x66, 0xe6, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x18, 0x18, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x06, 0x06, 0x00, 0x0e, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x66, 0x66, 0x3c, 0x00,
+	0x00, 0x00, 0xe0, 0x60, 0x60, 0x66, 0x6c, 0x78, 0x78, 0x6c, 0x66, 0xe6, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0xe6, 0xff, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0xdc, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0xdc, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7c, 0x60, 0x60, 0xf0, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0x7c, 0x0c, 0x0c, 0x1e, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0xdc, 0x76, 0x66, 0x60, 0x60, 0x60, 0xf0, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0xc6, 0x60, 0x38, 0x0c, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x10, 0x30, 0x30, 0xfc, 0x30, 0x30, 0x30, 0x30, 0x36, 0x1c, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0x76, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0xc3, 0xc3, 0xc3, 0xc3, 0x66, 0x3c, 0x18, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0xc3, 0xc3, 0xc3, 0xdb, 0xdb, 0xff, 0x66, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0xc3, 0x66, 0x3c, 0x18, 0x3c, 0x66, 0xc3, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7e, 0x06, 0x0c, 0xf8, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xcc, 0x18, 0x30, 0x60, 0xc6, 0xfe, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x0e, 0x18, 0x18, 0x18, 0x70, 0x18, 0x18, 0x18, 0x18, 0x0e, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x70, 0x18, 0x18, 0x18, 0x0e, 0x18, 0x18, 0x18, 0x18, 0x70, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0xdc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x10, 0x38, 0x6c, 0xc6, 0xc6, 0xc6, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x3c, 0x66, 0xc2, 0xc0, 0xc0, 0xc0, 0xc2, 0x66, 0x3c, 0x0c, 0x06, 0x7c, 0x00, 0x00,
+	0x00, 0x00, 0xcc, 0x00, 0x00, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0x76, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x0c, 0x18, 0x30, 0x00, 0x7c, 0xc6, 0xfe, 0xc0, 0xc0, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x10, 0x38, 0x6c, 0x00, 0x78, 0x0c, 0x7c, 0xcc, 0xcc, 0xcc, 0x76, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0xcc, 0x00, 0x00, 0x78, 0x0c, 0x7c, 0xcc, 0xcc, 0xcc, 0x76, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x60, 0x30, 0x18, 0x00, 0x78, 0x0c, 0x7c, 0xcc, 0xcc, 0xcc, 0x76, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x38, 0x6c, 0x38, 0x00, 0x78, 0x0c, 0x7c, 0xcc, 0xcc, 0xcc, 0x76, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x60, 0x60, 0x66, 0x3c, 0x0c, 0x06, 0x3c, 0x00, 0x00, 0x00,
+	0x00, 0x10, 0x38, 0x6c, 0x00, 0x7c, 0xc6, 0xfe, 0xc0, 0xc0, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0xc6, 0x00, 0x00, 0x7c, 0xc6, 0xfe, 0xc0, 0xc0, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x60, 0x30, 0x18, 0x00, 0x7c, 0xc6, 0xfe, 0xc0, 0xc0, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x66, 0x00, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x18, 0x3c, 0x66, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x60, 0x30, 0x18, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0xc6, 0x00, 0x10, 0x38, 0x6c, 0xc6, 0xc6, 0xfe, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00,
+	0x38, 0x6c, 0x38, 0x00, 0x38, 0x6c, 0xc6, 0xc6, 0xfe, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00,
+	0x18, 0x30, 0x60, 0x00, 0xfe, 0x66, 0x60, 0x7c, 0x60, 0x60, 0x66, 0xfe, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x6e, 0x3b, 0x1b, 0x7e, 0xd8, 0xdc, 0x77, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x3e, 0x6c, 0xcc, 0xcc, 0xfe, 0xcc, 0xcc, 0xcc, 0xcc, 0xce, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x10, 0x38, 0x6c, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0xc6, 0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x60, 0x30, 0x18, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x30, 0x78, 0xcc, 0x00, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0x76, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x60, 0x30, 0x18, 0x00, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0x76, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0xc6, 0x00, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7e, 0x06, 0x0c, 0x78, 0x00,
+	0x00, 0xc6, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0xc6, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x18, 0x18, 0x7e, 0xc3, 0xc0, 0xc0, 0xc0, 0xc3, 0x7e, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x38, 0x6c, 0x64, 0x60, 0xf0, 0x60, 0x60, 0x60, 0x60, 0xe6, 0xfc, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0xc3, 0x66, 0x3c, 0x18, 0xff, 0x18, 0xff, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0xfc, 0x66, 0x66, 0x7c, 0x62, 0x66, 0x6f, 0x66, 0x66, 0x66, 0xf3, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x0e, 0x1b, 0x18, 0x18, 0x18, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0xd8, 0x70, 0x00, 0x00,
+	0x00, 0x18, 0x30, 0x60, 0x00, 0x78, 0x0c, 0x7c, 0xcc, 0xcc, 0xcc, 0x76, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x0c, 0x18, 0x30, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x18, 0x30, 0x60, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x18, 0x30, 0x60, 0x00, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0x76, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x76, 0xdc, 0x00, 0xdc, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00,
+	0x76, 0xdc, 0x00, 0xc6, 0xe6, 0xf6, 0xfe, 0xde, 0xce, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x3c, 0x6c, 0x6c, 0x3e, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x38, 0x6c, 0x6c, 0x38, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x30, 0x30, 0x00, 0x30, 0x30, 0x60, 0xc0, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xc0, 0xc0, 0xc0, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x06, 0x06, 0x06, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0xc0, 0xc0, 0xc2, 0xc6, 0xcc, 0x18, 0x30, 0x60, 0xce, 0x9b, 0x06, 0x0c, 0x1f, 0x00, 0x00,
+	0x00, 0xc0, 0xc0, 0xc2, 0xc6, 0xcc, 0x18, 0x30, 0x66, 0xce, 0x96, 0x3e, 0x06, 0x06, 0x00, 0x00,
+	0x00, 0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x3c, 0x3c, 0x3c, 0x18, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x6c, 0xd8, 0x6c, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0xd8, 0x6c, 0x36, 0x6c, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x11, 0x44, 0x11, 0x44, 0x11, 0x44, 0x11, 0x44, 0x11, 0x44, 0x11, 0x44, 0x11, 0x44, 0x11, 0x44,
+	0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa,
+	0xdd, 0x77, 0xdd, 0x77, 0xdd, 0x77, 0xdd, 0x77, 0xdd, 0x77, 0xdd, 0x77, 0xdd, 0x77, 0xdd, 0x77,
+	0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+	0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xf8, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+	0x18, 0x18, 0x18, 0x18, 0x18, 0xf8, 0x18, 0xf8, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+	0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0xf6, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x18, 0xf8, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+	0x36, 0x36, 0x36, 0x36, 0x36, 0xf6, 0x06, 0xf6, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
+	0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x06, 0xf6, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
+	0x36, 0x36, 0x36, 0x36, 0x36, 0xf6, 0x06, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x18, 0x18, 0x18, 0x18, 0x18, 0xf8, 0x18, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+	0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+	0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1f, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xff, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+	0x18, 0x18, 0x18, 0x18, 0x18, 0x1f, 0x18, 0x1f, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+	0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x37, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
+	0x36, 0x36, 0x36, 0x36, 0x36, 0x37, 0x30, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x30, 0x37, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
+	0x36, 0x36, 0x36, 0x36, 0x36, 0xf7, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xf7, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
+	0x36, 0x36, 0x36, 0x36, 0x36, 0x37, 0x30, 0x37, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x36, 0x36, 0x36, 0x36, 0x36, 0xf7, 0x00, 0xf7, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
+	0x18, 0x18, 0x18, 0x18, 0x18, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
+	0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x18, 0x18, 0x18, 0x18, 0x18, 0x1f, 0x18, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x18, 0x1f, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
+	0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0xff, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
+	0x18, 0x18, 0x18, 0x18, 0x18, 0xff, 0x18, 0xff, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+	0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+	0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0xdc, 0xd8, 0xd8, 0xd8, 0xdc, 0x76, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x78, 0xcc, 0xcc, 0xcc, 0xd8, 0xcc, 0xc6, 0xc6, 0xc6, 0xcc, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0xfe, 0xc6, 0xc6, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0xfe, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0xfe, 0xc6, 0x60, 0x30, 0x18, 0x30, 0x60, 0xc6, 0xfe, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0xd8, 0xd8, 0xd8, 0xd8, 0xd8, 0x70, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7c, 0x60, 0x60, 0xc0, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x76, 0xdc, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x7e, 0x18, 0x3c, 0x66, 0x66, 0x66, 0x3c, 0x18, 0x7e, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x38, 0x6c, 0xc6, 0xc6, 0xfe, 0xc6, 0xc6, 0x6c, 0x38, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x38, 0x6c, 0xc6, 0xc6, 0xc6, 0x6c, 0x6c, 0x6c, 0x6c, 0xee, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x1e, 0x30, 0x18, 0x0c, 0x3e, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0xdb, 0xdb, 0xdb, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x03, 0x06, 0x7e, 0xdb, 0xdb, 0xf3, 0x7e, 0x60, 0xc0, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x1c, 0x30, 0x60, 0x60, 0x7c, 0x60, 0x60, 0x60, 0x30, 0x1c, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0xfe, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x7e, 0x18, 0x18, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x30, 0x18, 0x0c, 0x06, 0x0c, 0x18, 0x30, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x0c, 0x18, 0x30, 0x60, 0x30, 0x18, 0x0c, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x0e, 0x1b, 0x1b, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+	0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xd8, 0xd8, 0xd8, 0x70, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x7e, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0xdc, 0x00, 0x76, 0xdc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x38, 0x6c, 0x6c, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x0f, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0xec, 0x6c, 0x6c, 0x3c, 0x1c, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0xd8, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x70, 0xd8, 0x30, 0x60, 0xc8, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
diff --git a/drivers/tty/tty.c b/drivers/tty/tty.c
new file mode 100644
index 0000000..c65aeaf
--- /dev/null
+++ b/drivers/tty/tty.c
@@ -0,0 +1,424 @@
+/*
+ * This file contains the implementation of the TTY Device.  Generates a device
+ * /dev/tty, which displays to the default text video output.  This file handles
+ * all the formatting required for a TTY.
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/fb.h>
+#include <termios.h>
+#include "../drivers.h"
+#include "../../task/task.h"
+#include "../../proc/proc.h"
+#include "../../mem/heap.h"
+#include "../../io.h"
+
+size_t tty_read(File *file, char *buf, size_t size, off_t offset);
+size_t tty_write(File *file, char *buf, size_t size, off_t offset);
+int tty_ioctl(File *file, unsigned long request, uintptr_t argp);
+int tty_open(File *file);
+
+FileOps ttyFileOps = {
+	.read = tty_read,
+	.write = tty_write,
+	.ioctl = tty_ioctl,
+	.open = tty_open,
+};
+
+Driver ttyDriver = {
+	.major = 5,
+	.ops = &ttyFileOps,
+	.next = NULL,
+};
+
+extern uint8_t font[];
+
+Termios tty;
+uintptr_t vgaLfb;
+int vgaWidth, vgaHeight, vgaBpp;
+int ttyMode;
+int ttyX, ttyY;
+int ttyW = 80, ttyH = 25;
+char ttyAttr = 0x07;
+char *lineBuf, *line;
+int lineBufLen = 0, lineLen = 0;
+unsigned char shift = 0, ctrl = 0;
+
+uint32_t colours[] = {
+	0x000000,
+	0x0000FF,
+	0x00FF00,
+	0x00FFFF,
+	0xFF0000,
+	0xFF00FF,
+	0x884422,
+	0xCCCCCC,
+	0x666666,
+	0x6666FF,
+	0x66FF66,
+	0x66FFFF,
+	0xFF6666,
+	0xFF66FF,
+	0xFFFF66,
+	0xFFFFFF,
+};
+
+TaskQueue ttyWait;
+
+/* Map of keys on the keyboard */
+char kbmap[128] = {
+	0, 27,
+	'1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', '\b',
+	'\t', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\n',
+	0, 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', '`',
+	0, '#', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', 0,
+	'*', 0, ' ', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	'-', 0, 0, 0, '+', 0, 0, 0, 0, 0, 0, 0, '\\', 0, 0, 0
+};
+char skbmap[128] = {
+	0,  27,
+	'!', '"', '$', '$', '%', '^', '&', '*', '(', ')', '_', '+', '\b',
+	'\t', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', '\n',
+	0, 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', '@', '|',
+	0, '~', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', '<', '>', '?', 0,
+	'*', 0, ' ', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	'-', 0, 0, 0, '+', 0, 0, 0, 0, 0, 0, 0, '|', 0, 0, 0
+};
+
+/* Convert a string to a number */
+static int
+number(char *str)
+{
+	int num = 0, col = 1, len;
+	for (len = 0; str[len] >= '0' && str[len] <= '9'; len++);
+	while (len--) {
+		num += (str[len] - '0') * col;
+		col *= 10;
+	}
+	return num;
+}
+
+/* Put a pixel on the screen */
+static void
+putpixel(int x, int y, uint32_t colour)
+{
+	/* This splits the framebuffer into bytes to be BPP agnostic */
+	uint8_t *screen = (uint8_t *) vgaLfb;
+	unsigned where = x*(vgaBpp/8) + y*vgaWidth*(vgaBpp/8);
+	screen[where + 0] = (colour >>  0) & 0xFF; /* BLUE */
+	screen[where + 1] = (colour >>  8) & 0xFF; /* GREEN */
+	screen[where + 2] = (colour >> 16) & 0xFF; /* RED */
+}
+
+/* Draw a character to the framebuffer using the font */
+static void
+draw_char(unsigned char c, int x, int y, uint32_t fgcolour, uint32_t bgcolour)
+{
+	int cx, cy;
+	unsigned char *glyph = font + ((int) c * 16);
+	for (cy = 0; cy < 16; cy++) {
+		for (cx = 0; cx < 8; cx++) {
+			putpixel(x + 7 - cx, y + cy,
+			         (glyph[cy] & (1 << cx)) ? fgcolour : bgcolour);
+		}
+	}
+}
+
+/* Set the ANSI attribute */
+static void
+set_ansi_attribute(int mode, int foreground, int background)
+{
+	char bold;
+
+	switch (mode) {
+	case 0: /* RESET */
+		ttyAttr = 0x07;
+		return;
+	case 1:
+		bold = 1;
+		break;
+	}
+
+	switch (foreground) {
+	case 0:  /* DEFAULT */
+		ttyAttr = (ttyAttr & 0xF0) + 7 + (bold ? 8 : 0);
+		break;
+	case 30: /* BLACK */
+		ttyAttr = (ttyAttr & 0xF0) + 0;
+		break;
+	case 31: /* RED */
+		ttyAttr = (ttyAttr & 0xF0) + 4 + (bold ? 8 : 0);
+		break;
+	case 32: /* GREEN */
+		ttyAttr = (ttyAttr & 0xF0) + 2 + (bold ? 8 : 0);
+		break;
+	case 33: /* YELLOW */
+		ttyAttr = (ttyAttr & 0xF0) + 14;
+		break;
+	case 34: /* BLUE */
+		ttyAttr = (ttyAttr & 0xF0) + 1 + (bold ? 8 : 0);
+		break;
+	case 35: /* MAGENTA */
+		ttyAttr = (ttyAttr & 0xF0) + 5 + (bold ? 8 : 0);
+		break;
+	case 36: /* CYAN */
+		ttyAttr = (ttyAttr & 0xF0) + 3 + (bold ? 8 : 0);
+		break;
+	}
+
+}
+
+/* Parse ANSI escape codes */
+static size_t
+ansi_escape(char *buf)
+{
+	int len;
+	char *end;
+	for (len = 0; buf[len] < 'A' || buf[len] > 'z'; len++);
+	end = buf + len + 1;
+	switch (buf[len]) {
+	case 'H':
+		/* Set the cursor position */
+		ttyX = 0;
+		ttyY = 0;
+		break;
+	case 'J':
+		/* 2J clears screen */
+		int jmode = 0;
+		jmode = number(buf);
+		if (jmode == 2) {
+			if (ttyMode == 0)
+				memset((void *) 0xB8000, '\0', ttyW * ttyH * 2);
+			else
+				memset((void *) vgaLfb, '\0',
+				       vgaWidth * vgaHeight * (vgaBpp / 8));
+		}
+		break;
+	case 'm':
+		/* Set attribute */
+		int mode = 0, foreground = 0, background = 0;
+		mode = number(buf);
+		while (*buf >= '0' && *buf <= '9') buf++;
+		buf++;
+		if (buf == end)
+			goto set;
+		foreground = number(buf);
+		while (*buf >= '0' && *buf <= '9') buf++;
+		buf++;
+		if (buf == end)
+			goto set;
+		background = number(buf);
+set:
+		set_ansi_attribute(mode, foreground, background);
+		break;
+	}
+
+	return len + 1;
+}
+
+/* Print a character to the screen */
+void
+print_char(char c)
+{
+	char *screen = (char *) 0xB8000;
+
+	if (ttyMode == 0)
+		screen[(((ttyY * ttyW) + ttyX) * 2) + 1] = ttyAttr;
+	else
+		draw_char(' ', ttyX*8, ttyY*16, 0xFFFFFF, 0x000000);
+
+	/* Characters */
+	if (!(tty.lflag & ECHO) && c != '\n')
+		goto curctl;
+	switch (c) {
+	case '\r':
+		ttyX = 0;
+		break;
+	case '\n':
+		ttyX = 0;
+		ttyY++;
+		break;
+	case '\b':
+		ttyX--;
+		if (ttyMode == 0)
+			screen[((ttyY * ttyW) + ttyX) * 2] = ' ';
+		else
+			draw_char(' ', ttyX*8, ttyY*16, colours[ttyAttr & 0xF],
+			          colours[ttyAttr >> 4]);
+		break;
+	case '\t':
+		ttyX += 8 - (ttyX % 8);
+		break;
+	default:
+		if (ttyMode == 0)
+			screen[((ttyY * ttyW) + ttyX) * 2] = c;
+		else
+			draw_char(c, ttyX*8, ttyY*16, colours[ttyAttr & 0xF],
+			          colours[ttyAttr >> 4]);
+		ttyX++;
+	}
+
+	/* Control cursor */
+curctl:
+	if (ttyX >= ttyW) {
+		ttyX = 0;
+		ttyY++;
+	}
+	if (ttyX < 0) {
+		ttyX = ttyW - 1;
+		ttyY--;
+	}
+	if (ttyY >= ttyH) {
+		if (ttyMode == 0) {
+			memcpy(screen, screen + (ttyW*2), ttyW * (ttyH-1) * 2);
+			memset(screen + (ttyW * 2 * (ttyH-1)), 0, ttyW * 2);
+		} else {
+			memcpy((void *) vgaLfb, (void *) vgaLfb + (vgaWidth*(vgaBpp/8)*16),
+			       vgaWidth*(vgaBpp/8)*(vgaHeight-16));
+			memset((void *) vgaLfb + (vgaWidth*(vgaBpp/8)*(vgaHeight-16)),
+			       '\0', vgaWidth*(vgaBpp/8)*16);
+		}
+		ttyY--;
+	}
+	if (ttyY < 0)
+		ttyY++;
+
+	if (ttyMode == 0)
+		screen[(((ttyY * ttyW) + ttyX) * 2) + 1] =
+		      ((ttyAttr & 0x0F) << 4) + ((ttyAttr >> 4) & 0x0F);
+	else
+		draw_char(' ', ttyX*8, ttyY*16, 0x000000, 0x888888);
+}
+
+/* Handle the keyboard interrupt */
+void
+keyboard_handler(InterruptFrame *frame)
+{
+	char c;
+	unsigned char key;
+	int i;
+	for (i = 0; i < 1000; i++) {
+		if ((inb(0x64) & 1) == 0)
+			continue;
+		key = inb(0x60);
+		break;
+	}
+	if (key == 0x1D)
+		ctrl = 128;
+	if (key == 0x9D)
+		ctrl = 0;
+	if (key == 0x2A || key == 0x36)
+		shift = 128;
+	if (key == 0xAA || key == 0xB6)
+		shift = 0;
+
+	if (key >= 128)
+		return;
+	if (shift)
+		c = skbmap[key];
+	else
+		c = kbmap[key];
+	if (!c)
+		return;
+
+	if (c == '\b' && lineBufLen == 0)
+		return;
+	if (c == '\b')
+		lineBuf[lineBufLen--] = '\0';
+	else
+		lineBuf[lineBufLen++] = c;
+	print_char(c);
+	if (c == '\n') {
+		memcpy(line, lineBuf, lineBufLen);
+		memset(lineBuf, 0, lineBufLen);
+		lineLen = lineBufLen;
+		lineBufLen = 0;
+		for (Task *tmp = ttyWait.start; tmp; tmp = tmp->next)
+			unblock_task(pop_from_queue(&ttyWait));
+	}
+}
+
+/* Initialise the TTY */
+void
+init_tty(void)
+{
+	register_driver(&ttyDriver);
+	mknod("/dev/tty", S_IFCHR | 0666, MKDEV(ttyDriver.major, 0));
+
+	tty.lflag = ICANON | ECHO;
+
+	int fd = open("/dev/fb", O_RDWR);
+	if (fd < 0) {
+		ttyMode = 0;
+		int x, y;
+		uint16_t *screen = (uint16_t *) 0xB8000;
+		for (y = 0; y < 25; y++)
+			for (x = 0; x < 80; x++)
+				screen[(y * 80) + x] = 0x0000;
+	} else {
+		FBVarInfo vinfo;
+		FBFixInfo finfo;
+		ioctl(fd, FBIOGET_VSCREENINFO, &vinfo);
+		ioctl(fd, FBIOGET_FSCREENINFO, &finfo);
+		close(fd);
+		vgaWidth = vinfo.xres;
+		vgaHeight = vinfo.yres;
+		vgaBpp = vinfo.bpp;
+		vgaLfb = finfo.fbmem;
+
+		ttyMode = 1;
+		ttyW = vgaWidth/8;
+		ttyH = vgaHeight/16;
+	}
+
+	lineBuf = kmalloc(4096);
+	line = kmalloc(4096);
+	register_interrupt(1, keyboard_handler);
+}
+
+/* Read from the TTY */
+size_t
+tty_read(File *file, char *buf, size_t size, off_t offset)
+{
+	if (!lineLen) {
+		add_to_queue(&ttyWait, current);
+		block_task(WAITING_FOR_READ);
+	}
+	size_t count = (size < lineLen) ? size : lineLen;
+	memcpy(buf, line, count);
+	if (size < lineLen) {
+		memcpy(line, line + size, lineLen - size);
+		lineLen -= size;
+	} else {
+		memset(line, 0, lineLen);
+		lineLen = 0;
+	}
+	return count;
+}
+
+/* Write to the TTY */
+size_t
+tty_write(File *file, char *buf, size_t size, off_t offset)
+{
+	size_t skip, count = 0;
+	while (size--) {
+		if (buf[0] == '\033' && buf[1] == '[') {
+			skip = ansi_escape(buf + 2);
+			buf += skip + 2;
+			size -= skip + 1;
+			continue;
+		}
+		print_char(*buf++);
+		count++;
+	}
+	return count;
+}
+
+/* Open the TTY */
+int
+tty_open(File *file)
+{
+	return 0;
+}
diff --git a/drivers/tty/tty_ioctl.c b/drivers/tty/tty_ioctl.c
new file mode 100644
index 0000000..5889c66
--- /dev/null
+++ b/drivers/tty/tty_ioctl.c
@@ -0,0 +1,39 @@
+/*
+ * This file implements the TTY ioctl() backend.  It stores many of the settings
+ * which are used throughout the rest of the TTY driver.
+ */
+
+#include <stdint.h>
+#include <stddef.h>
+#include <string.h>
+#include <termios.h>
+#include "../../vfs/vfs.h"
+
+extern Termios tty;
+
+extern int ttyW, ttyH;
+extern int vgaWidth, vgaHeight;
+
+int
+tty_ioctl(File *file, unsigned long request, uintptr_t argp)
+{
+	Termios *ts;
+	Winsize *ws;
+	switch (request) {
+	case TCGETS:
+		ts = (void *) argp;
+		memcpy(ts, &tty, sizeof(Termios));
+		return 0;
+	case TCSETS:
+		ts = (void *) argp;
+		memcpy(&tty, ts, sizeof(Termios));
+		return 0;
+	case TCGWINSZ:
+		ws = (void *) argp;
+		ws->rows = ttyH;
+		ws->cols = ttyW;
+		ws->xres = vgaWidth;
+		ws->yres = vgaHeight;
+		return 0;
+	}
+}
diff --git a/drivers/vga/bga.c b/drivers/vga/bga.c
new file mode 100644
index 0000000..aff91aa
--- /dev/null
+++ b/drivers/vga/bga.c
@@ -0,0 +1,131 @@
+/*
+ * This file implements the driver for the Bochs Graphics Adapter video card.
+ * It is a simple card available in virtual machines.  It creates a framebuffer
+ * device node, and handles the reading/writing to it, and also mapping the
+ * framebuffer into memory.
+ */
+
+#include <stdint.h>
+#include <string.h>
+#include <sys/fb.h>
+#include "../drivers.h"
+#include "../pci.h"
+#include "../../mem/paging.h"
+#include "../../vfs/vfs.h"
+#include "../../io.h"
+
+size_t bga_write(File *file, char *buf, size_t size, off_t offset);
+int bga_ioctl(File *file, unsigned long request, uintptr_t argp);
+
+FileOps bgaFileOps = {
+	.write = bga_write,
+	.ioctl = bga_ioctl,
+};
+
+Driver bgaDriver = {
+	.major = 3,
+	.ops = &bgaFileOps,
+	.next = NULL,
+};
+
+static uint32_t bgaWidth, bgaHeight, bgaBpp;
+static uintptr_t lfb;
+
+enum BGAIndex {
+	BGA_INDEX_ID,
+	BGA_INDEX_XRES,
+	BGA_INDEX_YRES,
+	BGA_INDEX_BPP,
+	BGA_INDEX_ENABLE,
+	BGA_INDEX_BANK,
+	BGA_INDEX_VIRT_WIDTH,
+	BGA_INDEX_VIRT_HEIGHT,
+	BGA_INDEX_XOFF,
+	BGA_INDEX_YOFF,
+};
+
+/* Write a BGA register */
+void
+bga_write_register(uint16_t index, uint16_t data)
+{
+	outw(0x1CE, index);
+	outw(0x1CF, data);
+}
+
+/* Set the video mode */
+void
+bga_set_mode(uint32_t width, uint32_t height, uint32_t bpp)
+{
+	bgaWidth = width;
+	bgaHeight = height;
+	bgaBpp = bpp;
+	bga_write_register(BGA_INDEX_ENABLE, 0);
+	bga_write_register(BGA_INDEX_XRES, width);
+	bga_write_register(BGA_INDEX_YRES, height);
+	bga_write_register(BGA_INDEX_BPP, bpp);
+	bga_write_register(BGA_INDEX_ENABLE, 1 | 0x40);
+	/* 0x40 for framebuffer */
+}
+
+/* Initialise the BGA card */
+void
+bga_init(uint8_t bus, uint8_t slot, uint8_t func)
+{
+	register_driver(&bgaDriver);
+
+	const int width = 1280;
+	const int height = 720;
+	const int bpp = 32;
+	bga_set_mode(width, height, bpp);
+	lfb = pci_read_dword(bus, slot, func, 0x10) & 0xFFFFFFF0;
+	for (uintptr_t i = 0; i < width*height*(bpp/8); i += 0x1000)
+		*get_page((void *) lfb + i) = (0xFD000000 + i) | PTE_PRESENT
+		                            | PTE_WRITE | PTE_GLOBAL;
+	/* use the MMAP method to map pages of it into a page dir */
+	mknod("/dev/fb", S_IFCHR | 0600, MKDEV(bgaDriver.major, 0));
+}
+
+/* Write to the BGA framebuffer */
+size_t
+bga_write(File *file, char *buf, size_t size, off_t offset)
+{
+	size_t max, count = 0;
+	page_t oldpg;
+	while (size) {
+		max = (size < 0x1000) ? size : 0x1000;
+		acquire(&quickPageLock);
+		oldpg = quick_page(lfb + PG_ADDR(offset) + count);
+		memcpy(QUICK_PAGE, buf + count, max);
+		quick_page(oldpg);
+		release(&quickPageLock);
+		count += max;
+		size -= max;
+	}
+	return count;
+}
+
+/* Control the BGA framebuffer/device */
+int
+bga_ioctl(File *file, unsigned long request, uintptr_t argp)
+{
+	FBVarInfo *vinfo;
+	FBFixInfo *finfo;
+
+	switch (request) {
+	case FBIOGET_VSCREENINFO:
+		vinfo = (void *) argp;
+		vinfo->xres = bgaWidth;
+		vinfo->yres = bgaHeight;
+		vinfo->bpp = bgaBpp;
+		return 0;
+	case FBIOPUT_VSCREENINFO:
+		vinfo = (void *) argp;
+		bga_set_mode(vinfo->xres, vinfo->yres, vinfo->bpp);
+		return 0;
+	case FBIOGET_FSCREENINFO:
+		finfo = (void *) argp;
+		finfo->fbmem = 0xFD000000; // lfb;
+		finfo->fbmemLen = bgaWidth*bgaHeight*(bgaBpp/8);
+		return 0;
+	}
+}
diff --git a/io.h b/io.h
new file mode 100644
index 0000000..c7ac8d3
--- /dev/null
+++ b/io.h
@@ -0,0 +1,98 @@
+#ifndef KERNEL_IO_H
+#define KERNEL_IO_H
+
+#include <stdint.h>
+#include <stddef.h>
+
+/* Read byte from port */
+static inline uint8_t
+inb(uint16_t port)
+{
+	uint8_t value;
+	asm volatile("inb %w1, %0" : "=a" (value) : "Nd" (port));
+	return value;
+}
+/* Write byte from port */
+static inline void
+outb(uint16_t port, uint8_t value)
+{
+	asm volatile("outb %b0, %w1" : : "a" (value), "Nd" (port));
+}
+
+/* Read byte from port */
+static inline uint16_t
+inw(uint16_t port)
+{
+	uint16_t value;
+	asm volatile("inw %w1, %0" : "=a" (value) : "Nd" (port));
+	return value;
+}
+/* Write byte from port */
+static inline void
+outw(uint16_t port, uint16_t value)
+{
+	asm volatile("outw %w0, %w1" : : "a" (value), "Nd" (port));
+}
+
+/* Read byte from port */
+static inline uint32_t
+inl(uint16_t port)
+{
+	uint32_t value;
+	asm volatile("inl %1, %0" : "=a" (value) : "Nd" (port));
+	return value;
+}
+/* Write byte from port */
+static inline void
+outl(uint16_t port, uint32_t value)
+{
+	asm volatile("outl %0, %1" : : "a" (value), "Nd" (port));
+}
+
+/* Wait for IO to be ready */
+static inline void
+io_wait(void)
+{
+	outb(0x80, 0);
+}
+
+/* Read words into buffer */
+static inline void
+insw(uint16_t port, void *addr, size_t cnt)
+{
+	asm volatile(
+		"cld;"
+		"repne; insw;"
+		: "=D" (addr), "=c" (cnt)
+		: "d" (port), "0" (addr), "1" (cnt)
+		: "memory", "cc"
+	);
+}
+
+/* Write words out from buffer */
+static inline void
+outsw(uint16_t port, void *addr, size_t cnt)
+{
+	asm volatile(
+		"cld;"
+		"repne; outsw;"
+		: "=D" (addr), "=c" (cnt)
+		: "d" (port), "0" (addr), "1" (cnt)
+		: "memory", "cc"
+	);
+}
+
+/* Read dwords into buffer */
+static inline void
+insl(uint16_t port, void *addr, size_t cnt)
+{
+	asm volatile(
+		"cld;"
+		"repne; insl;"
+		: "=D" (addr), "=c" (cnt)
+		: "d" (port), "0" (addr), "1" (cnt)
+		: "memory", "cc"
+	);
+}
+
+#endif
diff --git a/linker.ld b/linker.ld
new file mode 100644
index 0000000..ac4b5cd
--- /dev/null
+++ b/linker.ld
@@ -0,0 +1,35 @@
+ENTRY(_start)
+
+SECTIONS {
+
+	.text 1M :
+	{
+		KEEP(*(.multiboot))
+		_code = .;
+		*(.text*)
+		. = ALIGN(4096);
+	}
+
+	.data :
+	{
+		_data = .;
+		*(.rodata*)
+		*(.data*)
+		. = ALIGN(4096);
+	}
+
+	.bss :
+	{
+		_bss = .;
+		*(.bss)
+		*(COMMON)
+		. = ALIGN(4096);
+	}
+
+	_end = .;
+
+	/DISCARD/ :
+	{
+		*(.comment)
+	}
+}
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..d4e1421
--- /dev/null
+++ b/main.c
@@ -0,0 +1,118 @@
+/*
+ * This file contains the entrance point for the Kernel proper.  The code here
+ * is called by the bootloader when it finishes.  The main routine is
+ * responsible for setting up memory and starting the processes running.
+ * This file is also responsible for performing per-CPU setups, which is done
+ * before the Kernel starts multi-tasking.
+ */
+
+#include <stdint.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include "mem/frame.h"
+#include "mem/paging.h"
+#include "mem/heap.h"
+#include "mem/mem.h"
+#include "proc/proc.h"
+#include "task/task.h"
+#include "vfs/vfs.h"
+#include "drivers/drivers.h"
+#include "net/net.h"
+#include "io.h"
+#include "screen.h"
+#include "spinlock.h"
+#include "multiboot.h"
+
+#include <sys/mount.h>
+
+void init_tty(void);
+
+extern char _bss[], _end[];
+
+uintptr_t initialStack;
+void *initrd;
+
+/* Setup current CPU */
+void
+cpu_load(void)
+{
+	/* Initialise SSE */
+	asm volatile(
+		"mov %cr0, %eax;"
+		"andl $0xfffffffb, %eax;"
+		"orl $0x2, %eax;"
+		"mov %eax, %cr0;"
+		"mov %cr4, %eax;"
+		"orl $0x600, %eax;"
+		"mov %eax, %cr4;"
+	);
+
+	/* Initialise CR4.PGE */
+	asm volatile(
+		"mov %cr4, %eax;"
+		"orl $0x10, %eax;"
+		"mov %eax, %cr4;"
+	);
+
+	/* Tables */
+	cpu_load_idt();
+	cpu_load_gdt();
+}
+
+/* Kernel main function */
+void
+kmain(uint32_t esp, struct MultibootInfo *mbinfo)
+{
+	/* Disable the cursor */
+	outb(0x3D4, 0x0A);
+        outb(0x3D5, 0x20);
+
+	kprintf("Starting the Orion Operating System");
+	void *ebda = (void *) (*((uint16_t *) 0x040E) << 4);
+	initrd = (void *) *((uint32_t *) mbinfo->modsAddr);
+
+	/* Zero-fill the BSS */
+	memset(_bss, 0, _end - _bss);
+
+	/* Setup frame allocator */
+	init_frames(mbinfo->mmapLen, (void *) mbinfo->mmapAddr);
+
+	/* Processor startup */
+	initialStack = esp;
+	init_idt();
+	init_gdt();
+	init_pic();
+	cpu_load();
+	init_multicore(ebda);
+
+	/* Check if 64-bit CPU */
+	uint32_t lm;
+	asm("cpuid" : "=d" (lm) : "a" (0x80000001));
+	if ((lm >> 29) & 1)
+		kprintf("CPU supports 64-bit mode");
+
+	/* Initialise paging */
+	init_paging();
+	/* Initialise Multitasking */
+	init_tasking();
+	/* Initialise VFS */
+	init_vfs();
+	register_exception(14, page_fault_handler);
+	/* Search for devices */
+	init_drivers();
+	init_tty();
+	/* Initialise Networking */
+//	init_net();
+
+	/* Mount root disk */
+	mkdir("root", 0);
+	mount("/dev/hda", "/root", "Ext2FS", MS_RDONLY, NULL);
+	chroot("/root");
+	chdir("/");
+	mount("devfs", "/dev", "DevFS", 0, NULL);
+	mount("procfs", "/proc", "ProcFS", 0, NULL);
+
+	char *argv[] = { "init", NULL };
+	execve("/bin/init", argv, NULL);
+	panic("Could not run init");
+}
diff --git a/mem/frame.c b/mem/frame.c
new file mode 100644
index 0000000..ee88fd8
--- /dev/null
+++ b/mem/frame.c
@@ -0,0 +1,207 @@
+/*
+ * This file handles allocating and freeing of page frames.  It it the core of
+ * the physical memory manager.  The Kernel heap and paging systems both sit on
+ * top of this.  It keeps track of which frames of physical memory are free and
+ * hands them out as needed.  To do this it keeps a simple bitmap of the frames.
+ */
+
+#include <stdint.h>
+#include "mem.h"
+#include "paging.h"
+#include "frame.h"
+#include "../screen.h"
+
+#define INDEX(a) ((a)/32)
+#define OFFSET(a) ((a)%32)
+
+/* Descriptor of a Frame Region */
+typedef struct FrameRegion FrameRegion;
+struct FrameRegion {
+	uint32_t base;
+	size_t numFrames;
+	size_t usedFrames;
+	FrameRegion *next;
+	uint32_t bitmap[];
+};
+
+size_t numFrames, usedFrames;
+FrameRegion *regions;
+
+/* Count the number of pages a number of bytes occupies */
+static size_t
+page_count(size_t bytes)
+{
+	size_t pages = bytes/0x1000;
+	if (bytes & 0xFFF)
+		pages++;
+	return pages;
+}
+
+/* Set a bit in the frame bitset */
+static void
+set_frame(FrameRegion *region, uint32_t idx)
+{
+	region->bitmap[INDEX(idx)] |= (1 << OFFSET(idx));
+	region->usedFrames++;
+	usedFrames++;
+}
+
+/* Clear a bit in the frame bitset */
+static void
+clear_frame(FrameRegion *region, uint32_t idx)
+{
+	region->bitmap[INDEX(idx)] &= ~(1 << OFFSET(idx));
+	region->usedFrames--;
+	usedFrames--;
+}
+
+/* Test a bit in the frame bitset */
+static uint32_t
+test_frame(FrameRegion *region, uint32_t idx)
+{
+	return (region->bitmap[INDEX(idx)] & (1 << OFFSET(idx)));
+}
+
+/* Get n bits from position p */
+static inline uint32_t
+get_bits(uint32_t x, uint8_t p, uint8_t n)
+{
+	return (x >> (p+1-n)) & ~(~0 << n);
+}
+
+/* Find first free frame */
+static uint32_t
+find_frames(FrameRegion *region, size_t frames)
+{
+	/* TODO: best fit */
+	/* FIXME: frames can be at most 32 */
+	uint32_t i, j;
+	for (i = 0; i < INDEX(region->numFrames); i++) {
+		if (!~region->bitmap[i])
+			continue;
+		for (j = 0; j < 32; j++) {
+			if (get_bits(~region->bitmap[i], j, frames) == ~(~0 <<
+			    frames))
+				return (i*32+j)-frames+1;
+			/* TODO: check across uint32_t boundaries */
+		}
+	}
+	return (uint32_t) -1;
+}
+
+/* Allocate a set of contiguous page frames */
+uint32_t
+alloc_frames(size_t frames)
+{
+	uint32_t idx;
+	size_t i;
+	/* Walk the regions, first fit */
+	FrameRegion *region;
+	for (region = regions; region; region = region->next) {
+		idx = find_frames(region, frames);
+		if (idx != -1)
+			break;
+	}
+	if (idx == -1)
+		return -1;
+
+	for (i = 0; i < frames; i++)
+		set_frame(region, idx + i);
+
+	return (uint32_t) region->base + (idx << 12);
+}
+
+/* Free a page frame */
+void
+free_frame(uint32_t frame)
+{
+	/* Walk the regions */
+	FrameRegion *region = regions;
+	while (region) {
+		if ((uint32_t) region > frame) {
+			frame -= region->base;
+			clear_frame(region, frame >> 12);
+			break;
+		}
+		region = region->next;
+	}
+}
+
+/* Setup the frame allocator */
+void
+init_frames(uint32_t memMapSize, struct E820Entry *memMap)
+{
+	/* Relocate the memory map */
+	struct E820Entry *top = (void *) 0x20000;
+	if (memMap < top) {
+		memcpy(top, memMap, memMapSize);
+		memMap = (void *) top;
+	}
+
+	/*
+	 * When the OS starts, the bootloader passes the Kernel a map of memory.
+	 * This map must be read, so the memory manager can avoid bad areas of
+	 * memory, that as mapped to hardware, ACPI, etc.
+	 * The frame allocator should only create a bitmap for frames that it
+	 * can actually allocate.  This means setting avoiding bad areas, and
+	 * avoiding the Kernel.
+	 */
+	uint32_t i, j;
+	FrameRegion *prev = 0, *head = regions;
+	uint32_t bumpAlloc = 0x1000;
+	for (i = 0; i < memMapSize / sizeof(struct E820Entry); i++) {
+		kprintf("MemMap: base=%#.8x%.8x, length=%#.8x%.8x, type=%d",
+		        memMap[i].baseHigh, memMap[i].base,
+		        memMap[i].lengthHigh, memMap[i].length,
+		        memMap[i].type);
+		if (memMap[i].baseHigh > 0
+		 || memMap[i].base > 0xFFFFFFFF
+		 || memMap[i].type != 1)
+			continue;
+
+		/* Usable region - create bitmap */
+		size_t frameSize  = memMap[i].length / 0x1000;
+		size_t bitmapSize = (frameSize / 8);
+		head = (FrameRegion *) bumpAlloc;
+		bumpAlloc += bitmapSize + sizeof(FrameRegion);
+		head->base = memMap[i].base;
+		head->numFrames = frameSize;
+		head->usedFrames = 0;
+		memset(head->bitmap, 0, bitmapSize);
+		/* Set top bits to 1, so they're never allocated */
+		for (j = OFFSET(head->numFrames); j < 32; j++)
+			set_frame(head, head->numFrames + j);
+		if (prev) prev->next = head;
+		else regions = head;
+		prev = head;
+	}
+
+	/* Regions to be remapped */
+	struct {uint32_t start, end;} remaps[] = {
+		{.start = 0x0000,   .end = bumpAlloc}, /* PMM bitmaps */
+		{.start = 0xB0000,  .end = 0xC0000  }, /* VGA memory */
+		{.start = 0x100000, .end = 0x180000 }, /* Kernel */
+	};
+
+	kprintf("Bump allocator top @ %#.8x", bumpAlloc);
+
+	/* Check bitmaps */
+	usedFrames = 0;
+	numFrames = 0;
+	FrameRegion *region = regions;
+	uint32_t regionEnd;
+	while (region) {
+		numFrames += region->numFrames;
+		regionEnd = region->base + (region->numFrames * 0x1000);
+		/* Iterate the remaps[] to find overlapping regions */
+		for (i = 0; i < sizeof(remaps)/sizeof(remaps[0]); i++)
+			for (j = remaps[i].start; j < remaps[i].end
+			     && j >= region->base && j < regionEnd;
+			     j += 0x1000)
+				set_frame(region, (j - region->base) >> 12);
+		region = region->next;
+	}
+
+	if (numFrames < 1024) /* 4MB */
+		panic("Not enough memory");
+}
diff --git a/mem/frame.h b/mem/frame.h
new file mode 100644
index 0000000..ba68567
--- /dev/null
+++ b/mem/frame.h
@@ -0,0 +1,25 @@
+#ifndef KERNEL_MEM_FRAME_H
+#define KERNEL_MEM_FRAME_H
+
+#include <stdint.h>
+#include <stddef.h>
+
+/* Structure of an E820 entry */
+struct E820Entry {
+	uint32_t size;
+	uint32_t base, baseHigh;
+	uint32_t length, lengthHigh;
+	uint32_t type;
+} __attribute__((packed));
+
+/* Structure of an E820 Entry */
+//struct E820Entry {
+//	uint64_t base, length;
+//	uint32_t type, attr;
+//} __attribute__((packed));
+
+uint32_t alloc_frames(size_t frames);
+void free_frame(uint32_t frame);
+void init_frames(uint32_t memMapSize, struct E820Entry *memMap);
+
+#endif
diff --git a/mem/heap.c b/mem/heap.c
new file mode 100644
index 0000000..9f9bbe2
--- /dev/null
+++ b/mem/heap.c
@@ -0,0 +1,125 @@
+/*
+ * This file contains the functions related to the Kernel's heap.  It uses a
+ * simple method of allocating and freeing blocks from a pool in the Kernel's
+ * memory space.  This heap will be present in every Page Directory, so can be
+ * used to store any Kernel data structures.
+ */
+
+#include <stdint.h>
+#include <stddef.h>
+#include "mem.h"
+#include "heap.h"
+#include "frame.h"
+#include "../vfs/cache.h"
+#include "../screen.h"
+#include "../spinlock.h"
+
+#define KHEAP_START 0x200000 /* 2 MB */
+#define KHEAP_END   0x800000 /* 8 MB */
+#define BLOCK_SIZE  16
+
+/* Structure for a Memory Header */
+typedef struct Header {
+	struct Header *next, *prev;
+	size_t size;
+	char magic[4];
+} Header; /* 16 bytes */
+
+size_t objectsAllocated = 0;
+size_t freeSpace = KHEAP_END - KHEAP_START;
+
+/* Allocate a region of the heap */
+void *
+kmalloc(size_t size)
+{
+	size_t blockSize, gapSize;
+	uintptr_t blockEnd;
+	Header *prev, *head, *next;
+	head = prev = (void *) KHEAP_START;
+	next = NULL;
+	objectsAllocated++;
+
+	/* Minimum allocation */
+	if (size % BLOCK_SIZE)
+		size += BLOCK_SIZE - (size % BLOCK_SIZE);
+	freeSpace -= size + sizeof(Header);
+
+	/* Low-memory VFS cache reaper */
+	if (freeSpace < 0x10000) /* 64 KB */
+		cache_reaper();
+
+	/* Block does not exist, create heap */
+	if (head->prev != head) {
+		head->prev = head;
+		head->next = NULL;
+		head->size = size;
+		memcpy(head->magic, "HEAP", 4);
+		memset((void *) (head + 1), 0, size);
+		return (void *) (head + 1);
+	}
+
+	/* Find gap */
+	while (head->next) {
+		next = head->next;
+		blockSize = sizeof(Header) + head->size;
+		blockEnd = (uintptr_t) head + blockSize;
+		gapSize = (size_t) next - blockEnd;
+		prev = head;
+
+		/* Fit in gap */
+		if (gapSize >= size + sizeof(Header)) {
+			head = (void *) blockEnd;
+			head->next = next;
+			head->prev = prev;
+			prev->next = head;
+			next->prev = head;
+			head->size = size;
+			memcpy(head->magic, "HEAP", 4);
+			memset((void *) (head + 1), 0, size);
+			return (void *) (head + 1);
+		}
+
+		head = head->next;
+	}
+
+	/* Add to end */
+	blockSize = sizeof(Header) + head->size;
+	blockEnd = (uintptr_t) head + blockSize;
+	gapSize = (size_t) KHEAP_END - blockEnd;
+	/* Fit in gap */
+	if (gapSize >= size + sizeof(Header)) {
+		prev = head;
+		head = (void *) blockEnd;
+		head->next = NULL;
+		head->prev = prev;
+		prev->next = head;
+		head->size = size;
+		memcpy(head->magic, "HEAP", 4);
+		memset((void *) (head + 1), 0, size);
+		return (void *) (head + 1);
+	}
+
+	panic("Kernel heap exhausted");
+}
+
+/* Free an allocated region of the heap */
+void
+_kfree(void *addr, char *file, int line)
+{
+	Header *prev, *head, *next;
+	head = (Header *) addr - 1;
+	objectsAllocated--;
+	freeSpace += head->size + sizeof(Header);
+
+	if (memcmp(head->magic, "HEAP", 4))
+		panic("Bad Kernel heap reference\n"
+		      "Invalid target @ %#.8x (%s:%d)", addr, file, line);
+	prev = head->prev;
+	next = head->next;
+	memset(head, 0, sizeof(Header));
+
+	if (prev != head)
+		prev->next = next;
+	if (next)
+		next->prev = prev;
+}
diff --git a/mem/heap.h b/mem/heap.h
new file mode 100644
index 0000000..1923583
--- /dev/null
+++ b/mem/heap.h
@@ -0,0 +1,11 @@
+#ifndef KERNEL_MEM_HEAP_H
+#define KERNEL_MEM_HEAP_H
+
+#include <stdint.h>
+#include <stddef.h>
+
+void *kmalloc(size_t size);
+void _kfree(void *addr, char *file, int line);
+#define kfree(a) _kfree(a, __FILE__, __LINE__)
+
+#endif
diff --git a/mem/mem.c b/mem/mem.c
new file mode 100644
index 0000000..80a7c89
--- /dev/null
+++ b/mem/mem.c
@@ -0,0 +1,106 @@
+/*
+ * This file contains a few routines for the manipulation of memory and strings.
+ * The functions would normally be part of a C library, but this is for the
+ * Kernel.  The functions are standalone, and have no dependencies - they can be
+ * called immediately after boot.
+ */
+
+#include <stdint.h>
+#include <stddef.h>
+
+/* Fill a region of memory with the specified byte */
+void *
+memset(void *s, int c, size_t n)
+{
+	unsigned char *a = s;
+	if (n > 0) {
+		while (n-- > 0)
+			*a++ = c;
+	}
+	return s;
+}
+
+/* Copy one region of memory to another */
+void *
+memcpy(void *dest, void *src, size_t n)
+{
+	unsigned char *a = (unsigned char *) dest,
+	              *b = (unsigned char *) src;
+	while (n-- > 0)
+		*a++ = *b++;
+	return dest;
+}
+
+/* Compare two regions of memory */
+int
+memcmp(void *s1, void *s2, size_t n)
+{
+	unsigned char *a = (unsigned char *) s1,
+	              *b = (unsigned char *) s2;
+	while (n-- > 0)
+		if (*a++ != *b++)
+			return a[-1] - b[-1];
+	return 0;
+}
+
+/* Find the length of a string */
+size_t
+strlen(char *s)
+{
+	if (!s)
+		return 0;
+	size_t i;
+	for (i = 0; s[i]; i++);
+	return i;
+}
+
+/* Find the length of a string up to maximum */
+size_t
+strnlen(char *s, size_t maxlen)
+{
+	if (!s)
+		return 0;
+	size_t i;
+	for (i = 0; s[i] && i <= maxlen; i++);
+	return i;
+}
+
+/* Compare two strings */
+int
+strcmp(char *s1, char *s2)
+{
+	for (; *s1 == *s2 && *s1 && *s2; s1++, s2++);
+	return *(unsigned char *) s1 - *(unsigned char *) s2;
+}
+
+/* Compare two limited strings */
+int
+strncmp(char *s1, char *s2, size_t n)
+{
+	if (!n--) return 0;
+	for (; *s1 == *s2 && *s1 && *s2 && n; s1++, s2++, n--);
+	return *(unsigned char *) s1 - *(unsigned char *) s2;
+}
+
+/* Copy a string */
+char *
+strcpy(char *dest, const char *src)
+{
+	char *ret = dest;
+	while (*src)
+		*dest++ = *src++;
+	*dest = '\0';
+	return ret;
+}
+
+/* Copy a limited string */
+char *
+strncpy(char *dest, const char *src, size_t n)
+{
+	char *ret = dest;
+	while (*src && n--)
+		*dest++ = *src++;
+	*dest = '\0';
+	return ret;
+}
+
diff --git a/mem/mem.h b/mem/mem.h
new file mode 100644
index 0000000..fa6dfc4
--- /dev/null
+++ b/mem/mem.h
@@ -0,0 +1,17 @@
+#ifndef KERNEL_MEM_H
+#define KERNEL_MEM_H
+
+#include <stdint.h>
+#include <stddef.h>
+
+void *memset(void *s, int c, size_t n);
+void *memcpy(void *dest, void *src, size_t n);
+int memcmp(void *s1, void *s2, size_t n);
+size_t strlen(char *s);
+size_t strnlen(char *s, size_t maxlen);
+int strcmp(const char *s1, const char *s2);
+int strncmp(const char *s1, const char *s2, size_t n);
+char *strcpy(char *dest, const char *src);
+char *strncpy(char *dest, const char *src, size_t n);
+
+#endif
diff --git a/mem/page.S b/mem/page.S
new file mode 100644
index 0000000..5e1dd4a
--- /dev/null
+++ b/mem/page.S
@@ -0,0 +1,60 @@
+; This file implements some simple low level assembly helper functions for the
+; paging system.  The functions are done in assembly for the speed benefit.
+; They may be called frequently by the memory manager, so the benefit will be
+; significant.
+
+[bits 32]
+
+; Enable paging
+[global enable_paging]
+enable_paging:
+	mov edx, cr0
+	or edx, 0x80000000
+	mov cr0, edx
+	ret
+
+; Disable paging
+[global disable_paging]
+disable_paging:
+	mov edx, cr0
+	and edx, 0x7FFFFFFF
+	mov cr0, edx
+	ret
+
+; Copy the contents of a page frame
+[global copy_page_frame]
+copy_page_frame:
+;	push ebx
+;	pushf
+;	mov esi, [esp + 12]
+;	mov edi, [esp + 16]
+;	call disable_paging
+;	mov ecx, 1024
+;	rep movsd
+;	call enable_paging
+;	popf
+;	pop ebx
+;	ret
+	push ebx
+	pushf
+	cli
+	mov ebx, [esp + 12]
+	mov ecx, [esp + 16]
+	mov edx, cr0
+	and edx, 0x7FFFFFFF
+	mov cr0, edx
+	mov edx, 1024
+.loop:
+	mov eax, [ebx]
+	mov [ecx], eax
+	add ebx, 4
+	add ecx, 4
+	dec edx
+	jnz .loop
+	mov edx, cr0
+	or edx, 0x80000000
+	mov cr0, edx
+	sti
+	popf
+	pop ebx
+	ret
diff --git a/mem/pagefault.c b/mem/pagefault.c
new file mode 100644
index 0000000..14219b7
--- /dev/null
+++ b/mem/pagefault.c
@@ -0,0 +1,264 @@
+/*
+ * This is the page fault handler.  It handles/dispatches all handlers for page
+ * faults.  This includes various tasking functions.
+ */
+
+#include <stdint.h>
+#include <signal.h>
+#include "paging.h"
+#include "../vfs/cache.h"
+#include "../vfs/inode.h"
+#include "../vfs/tmpfs/fs.h"
+#include "../mem/heap.h"
+#include "../mem/mem.h"
+#include "../task/task.h"
+#include "../proc/proc.h"
+#include "../screen.h"
+
+extern size_t numFrames, usedFrames;
+
+void copy_page_frame(void *src, void *dest);
+
+/* Copy-On-Write */
+static void
+copy_on_write(VMRegion *region, uintptr_t addr)
+{
+	Page *page = NULL;
+	File *front = region->front,
+	     *back  = region->back;
+	off_t offset = ((addr & ~0xFFF) - region->start) + region->offset;
+	page_t *pg = get_page((void *) addr);
+
+	/* Create front if it doesn't exist and is needed */
+	uint8_t private = region->flags & MAP_PRIVATE;
+	uint8_t sharedanon = (region->flags & MAP_SHARED) &&
+	                     (region->flags & MAP_ANONYMOUS);
+	uint8_t created = 0;
+	if (!front && (private || sharedanon)) {
+		/*
+		 * A private mapping will always write to the front.  A shared
+		 * mapping will write to the back.  If a shared mapping is
+		 * anonymous, then the back is the front.  The front must be
+		 * created if it is required - which means if the mapping is
+		 * private, or if the mapping is shared & anonymous.
+		 */
+		front = kmalloc(sizeof(File));
+		front->inode = inode_get(kmalloc(sizeof(Inode)));
+		front->ops = &tmpfsFileOps;
+		region->front = file_get(front);
+		created++;
+	}
+
+	/* Find original page frame */
+	Inode *inode;
+	if (!page && front) {
+		inode = front->inode;
+		ASSERT(inode);
+		page = page_find(inode, offset);
+	}
+	if (!page && back) {
+		inode = back->inode;
+		ASSERT(inode);
+		page = page_find(inode, offset);
+	}
+	ASSERT(page);
+
+	/* Copy already happened, just link */
+	if (page->usage == 1 && page->frame != zeroFrame) {
+		*pg |= PTE_WRITE;
+		return;
+	}
+	/* Put that page, and create a new one */
+	*pg = 0;
+	alloc_page(pg, PTE_PRESENT | PTE_USER | PTE_WRITE, -1);
+	copy_page_frame((void *) PG_ADDR(page->frame),
+	                (void *) PG_ADDR(*pg));
+	page_remove(inode, page);
+	page = page_create(front->inode, PG_ADDR(*pg), offset);
+}
+
+/* Handle a not-present read page fault */
+static void
+not_present_read(VMRegion *region, uintptr_t addr)
+{
+	Page *page;
+	File *front = region->front,
+	     *back  = region->back;
+	off_t offset = ((addr & ~0xFFF) - region->start) + region->offset;
+	page_t *pg = get_page((void *) addr);
+
+	/* Handle uninitialised anonymous regions */
+	if (!front && (region->flags & MAP_ANONYMOUS)) {
+		front = kmalloc(sizeof(File));
+		front->inode = inode_get(kmalloc(sizeof(Inode)));
+		front->ops = &tmpfsFileOps;
+		region->front = file_get(front);
+	}
+
+	/* Attempt to use front */
+	if (front) {
+		page = page_find(front->inode, offset);
+		if (page) {
+			page_get(page);
+			alloc_page(pg, PTE_PRESENT | PTE_USER, page->frame);
+			return;
+		}
+		if (region->flags & MAP_ANONYMOUS) {
+			/* Must be anonymous, zero-fill */
+			alloc_page(pg, PTE_PRESENT | PTE_USER, zeroFrame);
+			page_create(front->inode, zeroFrame, offset);
+			return;
+		}
+	}
+
+	/* Use back */
+	ASSERT(back);
+	page = page_find(back->inode, offset);
+	if (page) {
+		page_get(page);
+		alloc_page(pg, PTE_PRESENT | PTE_USER, page->frame);
+		return;
+	}
+	/* Create new block cache entry */
+	alloc_page(pg, PTE_PRESENT | PTE_USER, -1);
+	file_mmap(back, (void *) PG_ADDR(addr), 0x1000, offset);
+	page_create(back->inode, PG_ADDR(*pg), offset);
+}
+
+/* Handle a not-present write page fault */
+static void
+not_present_write(VMRegion *region, uintptr_t addr)
+{
+	Page *page = NULL;
+	File *front = region->front,
+	     *back  = region->back;
+	off_t offset = ((addr & ~0xFFF) - region->start) + region->offset;
+	page_t *pg = get_page((void *) addr);
+
+	/* Handle uninitialised anonymous regions */
+	if (!front && ((region->flags & MAP_PRIVATE)
+	 || (region->flags & MAP_ANONYMOUS))) {
+		/*
+		 * This applies to all private regions, anonymous or not.
+		 * Unless the region is shared, the process should write to the
+		 * front, which will be the private copy.  If the region is
+		 * shared, and also anonymous, then the write will occur to the
+		 * front too.
+		 */
+		front = kmalloc(sizeof(File));
+		front->inode = inode_get(kmalloc(sizeof(Inode)));
+		front->ops = &tmpfsFileOps;
+		region->front = file_get(front);
+	}
+
+	/* Shared region, write-through to back */
+	if (region->flags & MAP_SHARED) {
+		if (region->flags & MAP_ANONYMOUS)
+			back = front;
+		ASSERT(back);
+		page = page_find(back->inode, offset);
+		if (page) {
+			page_get(page);
+			alloc_page(pg, PTE_PRESENT | PTE_USER | PTE_WRITE,
+			           page->frame);
+			return;
+		}
+		*pg = 0;
+		alloc_page(pg, PTE_PRESENT | PTE_USER | PTE_WRITE, -1);
+		memset((void *) PG_ADDR(addr), 0, 0x1000);
+		page_create(back->inode, PG_ADDR(*pg), offset);
+		return;
+	}
+
+	/* Private region, copy to front */
+	alloc_page(pg, PTE_PRESENT | PTE_USER | PTE_WRITE, -1);
+	if (front)
+		page = page_find(front->inode, offset);
+	if (page) {
+		copy_page_frame((void *) PG_ADDR(page->frame),
+		                (void *) PG_ADDR(*pg));
+		page_remove(front->inode, page);
+		page_create(front->inode, PG_ADDR(*pg), offset);
+		return;
+	}
+
+	/* Anonymous region, zero-fill */
+	if (region->flags & MAP_ANONYMOUS) {
+		memset((void *) PG_ADDR(addr), 0, 0x1000);
+		page_create(front->inode, PG_ADDR(*pg), offset);
+		return;
+	}
+
+	/* Use back */
+	ASSERT(back);
+	page = page_find(back->inode, offset);
+	if (page) {
+		copy_page_frame((void *) PG_ADDR(page->frame),
+		                (void *) PG_ADDR(*pg));
+		page_remove(back->inode, page);
+	} else {
+		file_mmap(back, (void *) PG_ADDR(addr), 0x1000, offset);
+	}
+	page_create(front->inode, PG_ADDR(*pg), offset);
+}
+
+/* Page fault handler */
+void
+page_fault_handler(InterruptFrame *frame)
+{
+	uintptr_t addr;
+	asm volatile("mov %%cr2, %0" : "=r" (addr));
+	uint8_t present = frame->errCode & (1 << 0);
+	uint8_t write   = frame->errCode & (1 << 1);
+	uint8_t user    = frame->errCode & (1 << 2);
+
+	/* Iterate VM Regions */
+	VMRegion *region;
+	for (region = current->vm->regions; region; region = region->next) {
+		if (region->start <= addr && region->end > addr)
+			break;
+	}
+	if (!region && current->stack) {
+		region = current->stack;
+		if (region->start > addr || region->end <= addr)
+			region = NULL;
+	}
+	if (!region && current->tls) {
+		region = current->tls;
+		if (region->start > addr || region->end <= addr)
+			region = NULL;
+	}
+	/* Not in a region */
+	if (!region) {
+		page_t *pg = get_page((void *) addr);
+		panic("Page Fault [%d:%d] (%#.8x -> %#.8x [tbl:%d, pg:%d][%#.8x]), %s, %s, %s",
+		      current->tgid, current->tid, frame->eip,
+		      addr, (addr>>12) / 1024, (addr>>12) % 1024, *pg,
+		      present ? "present" : "not present",
+		      write ? "write" : "read",
+		      user ? "user" : "kernel");
+	}
+
+	if (user && write && !(region->prot & PROT_WRITE))
+		return (void) kill(current->tgid, SIGSEGV);
+
+	if (present && write)
+		return copy_on_write(region, addr);
+
+	if (write)
+		return not_present_write(region, addr);
+	else
+		return not_present_read(region, addr);
+}
+
+/* Early (pre-VFS/tasking) page fault handler */
+void
+early_page_fault_handler(InterruptFrame *frame)
+{
+	uintptr_t addr;
+	asm volatile("mov %%cr2, %0" : "=r" (addr));
+	if (!PG_ADDR(addr))
+		panic("Null dereference @ %#.8x", frame->eip);
+	alloc_page(get_page((void *) addr),
+	           PTE_PRESENT | PTE_WRITE | PTE_GLOBAL, -1);
+}
diff --git a/mem/paging.c b/mem/paging.c
new file mode 100644
index 0000000..b44e843
--- /dev/null
+++ b/mem/paging.c
@@ -0,0 +1,228 @@
+/*
+ * This file contains all functions used to manipulate the virtual address
+ * spaces.  It has a static Kernel page directory and table, which it uses to
+ * initialsed an identity-paged environment for the Kernel to work in.  This is
+ * enough for the heap to function in.  The file also exposes several functions
+ * that allow a page directory to be manipulated and have pages added and moved.
+ * These functions are used by other components of the Kernel - mostly the heap
+ * and IPC.  There are also functions to create new and destroy existing page
+ * directories.  The paging system also implements features like copy-on-write.
+ */
+
+#include <stdint.h>
+#include <sys/mman.h>
+#include "frame.h"
+#include "heap.h"
+#include "mem.h"
+#include "paging.h"
+#include "../vfs/vfs.h"
+#include "../proc/proc.h"
+#include "../io.h"
+#include "../screen.h"
+
+Spinlock quickPageLock;
+
+page_table_t kernelDir;
+page_t zeroFrame;
+
+void enable_paging(void);
+void disable_paging(void);
+void copy_page_frame(void *src, void *dest);
+
+/* Switch page directory */
+static void
+switch_dir(page_dir_t dir)
+{
+	asm volatile("mov %0, %%cr3" :: "r" (dir));
+}
+
+/* Allocate a page a frame */
+void
+alloc_page(page_t *page, uint16_t flags, page_t frame)
+{
+	page_t *mappings = (void *) 0xFFC00000;
+	page_table_t *tables = (void *) 0xFFFFF000;
+	if ((tables[(page - mappings) / 1024] & PDE_PRESENT) == 0)
+		return;
+	if (*page & 0xFFFFF000)
+		return;
+
+	if (frame == (page_t) -1)
+		frame = alloc_frames(1);
+	if (frame == (page_t) -1)
+		return;
+
+	*page = frame | flags;
+	flush_tlb((page - mappings) << 12);
+}
+
+/* Release a page's frame */
+void
+free_page(page_t *page)
+{
+	page_t *mappings = (void *) 0xFFC00000;
+	page_table_t *tables = (void *) 0xFFFFF000;
+	if ((tables[(page - mappings) / 1024] & PDE_PRESENT) == 0)
+		return;
+	if ((*page & 0xFFFFF000) == 0)
+		return;
+
+	free_frame(*page & 0xFFFFF000);
+	*page = 0x00000000;
+	flush_tlb((page - mappings) << 12);
+}
+
+/* Get Page Table Entry from virtual address */
+page_t *
+get_page(void *addr)
+{
+	page_t *mappings = (void *) 0xFFC00000;
+	page_table_t *tables = (void *) 0xFFFFF000;
+	uint32_t address = (uint32_t) addr >> 12;
+	uint32_t tbl = address / 1024;
+	/* Create table not present */
+	if ((tables[tbl] & PDE_PRESENT) == 0) {
+		tables[tbl] = alloc_frames(1)
+		            | PDE_PRESENT | PDE_WRITE | PDE_USER;
+		memset((void *) mappings + (tbl * 0x1000), 0, 0x1000);
+	}
+	return &mappings[address];
+}
+
+/* Clone a page directory */
+page_dir_t
+clone_dir(void)
+{
+	page_table_t *oldTables = (void *) 0xFFFFF000;
+	page_table_t *newTables = (void *) 0xFFFFE000;
+	page_t *oldTable, *newTable;
+	page_dir_t dir = alloc_frames(1);
+	uint16_t i, tbl, pg;
+
+	/* Temporarily link new paging structures into current directory */
+	page_table_t restore = oldTables[1022];
+	oldTables[1022] = dir | PDE_PRESENT | PDE_WRITE;
+	for (i = 0; i < 1024; i++)
+		flush_tlb((uintptr_t) newTables + (0x1000 * i));
+
+	/* Iterate tables */
+	for (tbl = 0; tbl < 1022; tbl++) {
+		if ((oldTables[tbl] & PDE_PRESENT) == 0)
+			continue;
+
+		/* Link Kernel tables */
+		if (tbl < 2 || tbl >= 1008) { /* TODO: define kernel mem */
+			newTables[tbl] = oldTables[tbl];
+			continue;
+		}
+
+		/* Copy everything else */
+		newTables[tbl] = alloc_frames(1) | PG_ATTR(oldTables[tbl]);
+		oldTable = (page_t *) 0xFFC00000 + (tbl * 1024);
+		newTable = (page_t *) 0xFF800000 + (tbl * 1024);
+		for (pg = 0; pg < 1024; pg++) {
+			if ((oldTable[pg] & PTE_PRESENT) == 0) {
+				newTable[pg] = 0;
+				continue;
+			}
+
+			/* Copy-On-Write behaviour */
+			if (tbl < 960) {
+				oldTable[pg] &= ~PTE_WRITE;
+				flush_tlb((uintptr_t) (((tbl * 1024) + pg) << 12));
+				newTable[pg] = oldTable[pg];
+			} else {
+				newTable[pg] = alloc_frames(1) | PG_ATTR(oldTable[pg]);
+				copy_page_frame((void *) PG_ADDR(oldTable[pg]),
+				                (void *) PG_ADDR(newTable[pg]));
+			}
+			/* FIXME */
+		}
+	}
+	newTables[1023] = oldTables[1022];
+
+	/* Unlink paging structures */
+	oldTables[1022] = restore;
+	for (i = 0; i < 1024; i++)
+		flush_tlb((uintptr_t) newTables + (0x1000 * i));
+
+	return dir;
+}
+
+/* Free all (copied) pages in the current directory */
+void
+clean_dir(void)
+{
+	page_t *mappings = (void *) 0xFFC00000;
+	page_table_t *tables = (void *) 0xFFFFF000;
+	page_t *pages;
+	uint16_t tbl, pg;
+	for (tbl = 2; tbl < 1008; tbl++) {
+		if ((tables[tbl] & PDE_PRESENT) == 0)
+			continue;
+		pages = mappings + (tbl * 1024);
+		for (pg = 0; pg < 1024; pg++) {
+			if ((pages[pg] & PDE_PRESENT) == 0)
+				continue;
+			free_page(pages + pg);
+		}
+	}
+}
+
+/* Quickly map a page frame into view for temporary use */
+page_t
+quick_page(uintptr_t frame)
+{
+	page_t *mappings = (void *) 0xFFC00000;
+	page_t old;
+	old = mappings[2047];
+	mappings[2047] = PG_ADDR(frame) | PG_ATTR(old);
+	flush_tlb(0x7FF000);
+	return PG_ADDR(old);
+}
+
+/* Initialise paging */
+void
+init_paging(void)
+{
+	zeroFrame = alloc_frames(1);
+	memset((void *) zeroFrame, 0, 0x1000);
+
+	uint16_t tbl, pg;
+	page_t *table;
+	page_table_t *kernelTables;
+	kernelDir = alloc_frames(1);
+	kernelTables = (page_table_t *) kernelDir;
+	for (tbl = 0; tbl < 1024; tbl++)
+		kernelTables[tbl] = 0x00000000 | PDE_WRITE;
+	for (tbl = 0; tbl < 2; tbl++) {
+		table = (void *) alloc_frames(1);
+		kernelTables[tbl] = ((page_table_t) table)
+		                  | PDE_WRITE | PDE_PRESENT;
+		for (pg = 0; pg < 1024; pg++) {
+			if (!tbl && !pg)
+				continue;
+			table[pg] = (((tbl * 1024) + pg) << 12)
+			          | PTE_WRITE | PTE_PRESENT | PTE_GLOBAL;
+		}
+	}
+	/* Map the directory into itself */
+	kernelTables[1023] = kernelDir | PDE_WRITE | PDE_PRESENT;
+
+	/* Use Kernel directory */
+	switch_dir(kernelDir);
+	register_exception(14, early_page_fault_handler);
+	enable_paging();
+
+	/* Identity page the APIC registers */
+	*get_page((void *)  lapicPtr) =  lapicPtr
+	                              | PTE_PRESENT | PTE_WRITE | PTE_GLOBAL;
+	*get_page((void *) ioapicPtr) = ioapicPtr
+	                              | PTE_PRESENT | PTE_WRITE | PTE_GLOBAL;
+
+	/* Allocate Kernel stack */
+	uintptr_t stk;
+	for (stk = 0xF0400000; stk < 0xF0800000; stk += 0x1000)
+		alloc_page(get_page((void *) stk),
+		           PTE_PRESENT | PTE_WRITE | PTE_USER, -1);
+}
diff --git a/mem/paging.h b/mem/paging.h
new file mode 100644
index 0000000..a3acaf2
--- /dev/null
+++ b/mem/paging.h
@@ -0,0 +1,58 @@
+#ifndef KERNEL_MEM_PAGING_H
+#define KERNEL_MEM_PAGING_H
+
+#include <stdint.h>
+#include "../proc/proc.h"
+#include "../spinlock.h"
+
+#define PG_ADDR(pg) (pg & 0xFFFFF000)
+#define PG_ATTR(pg) (pg & 0x00000FFF)
+
+#define QUICK_PAGE ((void *) 0x7FF000)
+
+typedef uint32_t page_t;
+typedef uint32_t page_table_t;
+typedef uint32_t page_dir_t;
+
+/* Page flags */
+enum PageFlag {
+	PTE_PRESENT = (1 << 0),
+	PTE_WRITE   = (1 << 1),
+	PTE_USER    = (1 << 2),
+	PTE_THROUGH = (1 << 3),
+	PTE_NOCACHE = (1 << 4),
+	PTE_ACCESS  = (1 << 5),
+	PTE_DIRTY   = (1 << 6),
+	PTE_GLOBAL  = (1 << 8),
+};
+/* Page Table flags */
+enum PageTableFlag {
+	PDE_PRESENT = (1 << 0),
+	PDE_WRITE   = (1 << 1),
+	PDE_USER    = (1 << 2),
+	PDE_THROUGH = (1 << 3),
+	PDE_NOCACHE = (1 << 4),
+	PDE_ACCESS  = (1 << 5),
+};
+
+/* Flush Translation Lookaside Buffer */
+static inline void
+flush_tlb(uintptr_t addr)
+{
+	asm volatile("invlpg (%0)" :: "r" (addr) : "memory");
+}
+
+extern Spinlock quickPageLock;
+extern page_t zeroFrame;
+
+void init_paging(void);
+void early_page_fault_handler(InterruptFrame *frame);
+void page_fault_handler(InterruptFrame *frame);
+void alloc_page(page_t *page, uint16_t flags, page_t frame);
+void free_page(page_t *page);
+page_t *get_page(void *addr);
+page_dir_t clone_dir(void);
+void clean_dir(void);
+page_t quick_page(page_t frame);
+
+#endif
diff --git a/mem/user.c b/mem/user.c
new file mode 100644
index 0000000..b9ed273
--- /dev/null
+++ b/mem/user.c
@@ -0,0 +1,37 @@
+/*
+ * This file handles safely getting data from userspace for the Kernel.  This is
+ * for security reasons to prevent the user from tricking a syscall into
+ * manipulating/leaking Kernel data structures.  User memory is defined as any
+ * address range that completely sits in a Virtual Memory Region.
+ */
+
+#include <stdint.h>
+#include <string.h>
+#include "vm.h"
+#include "../task/task.h"
+
+/* User can read this address */
+int
+verify_access(const void *addr, size_t len, int prot)
+{
+	if (!in_syscall() || !addr || !len)
+		return 1;
+
+	/* Iterate all user memory regions */
+	VMRegion *head;
+	for (head = current->vm->regions; head; head = head->next) {
+		if ((uintptr_t) addr >= head->start
+		 && ((uintptr_t) addr + len) < head->end)
+			break;
+	}
+	if (!head) {
+		head = current->stack;
+		if ((uintptr_t) addr < head->start
+		 || ((uintptr_t) addr + len) >= head->end)
+			head = NULL;
+	}
+	/* No fitting region */
+	if (!head)
+		return 0;
+	return (head->prot & prot);
+}
diff --git a/mem/vm.c b/mem/vm.c
new file mode 100644
index 0000000..46b40fc
--- /dev/null
+++ b/mem/vm.c
@@ -0,0 +1,261 @@
+/*
+ * This file handles the Virtual Memory system for processes.  It splits each
+ * process into several memory regions, and points each of those reasons to a
+ * memory object.  Each object can be modified on demand, and can be made up of
+ * several pages, and backed by various stores.  This allows objects such as
+ * files to be easily mapped into an address space, or for large regions to be
+ * shared between processes.
+ */
+
+#include <stdint.h>
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <errno.h>
+#include "heap.h"
+#include "paging.h"
+#include "vm.h"
+#include "../vfs/vfs.h"
+#include "../vfs/inode.h"
+#include "../vfs/cache.h"
+#include "../vfs/tmpfs/fs.h"
+#include "../task/task.h"
+#include "../proc/proc.h"
+#include "../screen.h"
+
+/* Unmap a range of pages from page directory */
+static void
+unmap_page_range(uintptr_t start, uintptr_t end)
+{
+	uintptr_t addr;
+	for (addr = start; addr < end; addr += 0x1000) {
+        	*get_page((void *) addr) = 0x00000000;
+	        flush_tlb(addr);
+	}
+}
+
+/* Remove a range of pages from a region's page cache */
+static void
+remove_cache_range(VMRegion *region, uintptr_t start, uintptr_t end)
+{
+	Page *page;
+	Inode *inode;
+	uintptr_t p;
+	for (p = 0; p < end - start; p += 0x1000) {
+		page = NULL;
+		if (!page && region->front) {
+			inode = region->front->inode;
+			page = page_find(inode, region->offset + p);
+		}
+		if (!page && region->back) {
+			inode = region->back->inode;
+			page = page_find(inode, region->offset + p);
+		}
+		if (page)
+			page_remove(inode, page);
+	}
+}
+
+/* Create a new VM Region */
+VMRegion *
+vm_create_region(void *addr, size_t len, int prot, int flags, off_t offset,
+                 File *back)
+{
+	/* Create new region */
+	VMRegion *head, *next, *insert, *region = kmalloc(sizeof(VMRegion));
+	region->end = (uintptr_t) addr + len;
+	if (region->end % 0x1000)
+		region->end += 0x1000 - (region->end % 0x1000);
+	region->start = (uintptr_t) addr & ~0xFFF;
+	region->prot = prot;
+	region->flags = flags;
+	region->offset = offset;
+	region->front = NULL;
+	region->back = NULL;
+	if (back)
+		region->back = file_get(back);
+
+	/* Create new list */
+	if (!current->vm->regions) {
+		current->vm->regions = region;
+		return region;
+	}
+
+	/* Fix overlaps */
+	uintptr_t p;
+	for (head = current->vm->regions; head; head = next) {
+		next = head->next; /* head may be destroyed during iteration */
+		if (head->start >= region->end || head->end <= region->start)
+			continue;
+
+		/* Middle eclipsed */
+		if (head->start < region->start && head->end > region->end) {
+			/* Create region after current */
+			insert = kmalloc(sizeof(VMRegion));
+			insert->end = head->end;
+			insert->start = head->end = region->start;
+			insert->prot = head->prot;
+			insert->flags = head->flags;
+			insert->offset = head->offset;
+			insert->offset += (insert->start - head->start);
+			if (head->front)
+				insert->front = file_get(head->front);
+			if (head->back)
+				insert->back = file_get(head->back);
+			/* Insert into list */
+			insert->next = head->next;
+			head->next = insert;
+			insert->prev = head;
+			insert->next->prev = insert;
+			/* Inserted region will be dealt with on next pass */
+		}
+		/* Start eclipsed */
+		if (head->start >= region->start && head->end > region->end) {
+			unmap_page_range(head->start, region->end);
+			remove_cache_range(head, head->start, region->end);
+			head->start = region->end;
+			head->offset += (region->end - head->start);
+		}
+		/* End eclipsed */
+		if (head->start < region->start && head->end <= region->end) {
+			unmap_page_range(region->start, head->end);
+			remove_cache_range(head, region->start, head->end);
+			head->end = region->start;
+		}
+		/* Total eclipse */
+		if (head->start >= region->start && head->end <= region->end)
+			vm_destroy_region(head);
+	}
+	/* Add to ordered list */
+	for (head = current->vm->regions; head->next; head = head->next)
+		if (head->end <= region->start
+		 && head->next->start >= region->end)
+			break;
+	region->next = head->next;
+	region->prev = head;
+	region->prev->next = region;
+	if (region->next)
+		region->next->prev = region;
+
+	return region;
+}
+
+/* Remove a VM Region */
+void
+vm_remove_region(VMRegion *region)
+{
+	/* Remove from list */
+	if (current->vm->regions == region)
+		current->vm->regions = region->next;
+	if (region->prev)
+		region->prev->next = region->next;
+	if (region->next)
+		region->next->prev = region->prev;
+//	region->prev = region->next = NULL;
+}
+
+/* Destroy a VM Region */
+void
+vm_destroy_region(VMRegion *region)
+{
+	/* Unlink files */
+	if (region->front)
+		file_put(region->front);
+	if (region->back)
+		file_put(region->back);
+
+	/* Clean page directory */
+	unmap_page_range(region->start, region->end);
+
+	vm_remove_region(region);
+	kfree(region);
+}
+
+/* Clone a set of VM Regions */
+VMRegion *
+vm_clone_regions(VMRegion *head)
+{
+	if (!head)
+		return NULL;
+
+	VMRegion *newhead = NULL, *newcurr, *newprev = NULL;
+	VMRegion *curr = head;
+	off_t i;
+	Page *page;
+	File *file;
+
+	while (curr) {
+		newcurr = kmalloc(sizeof(VMRegion));
+		if (!newhead)
+			newhead = newcurr;
+
+		newcurr->prev = newprev;
+		newcurr->next = NULL;
+		if (newprev)
+			newprev->next = newcurr;
+
+		newcurr->start = curr->start;
+		newcurr->end = curr->end;
+		newcurr->prot = curr->prot;
+		newcurr->flags = curr->flags;
+		newcurr->offset = curr->offset;
+		/* Front (anonymous regions) */
+		if (curr->front && (curr->flags & MAP_PRIVATE)) {
+			/* Copy the file */
+			file = kmalloc(sizeof(File));
+			file->inode = inode_get(kmalloc(sizeof(Inode)));
+			file->ops = &tmpfsFileOps;
+			newcurr->front = file_get(file);
+			for (i = 0; i < curr->end - curr->start; i += 0x1000) {
+				page = page_find(curr->front->inode,
+				                 i + curr->offset);
+				if (page)
+					page_add(file->inode, page);
+			}
+		} else if (curr->front) {
+			newcurr->front = file_get(curr->front);
+		}
+		/* Back (always a file) */
+		if (curr->back)
+			newcurr->back = file_get(curr->back);
+
+		curr = curr->next;
+		newprev = newcurr;
+	};
+
+	return newhead;
+}
+
+/* Map an object into memory */
+void *
+mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off)
+{
+	VMRegion *region;
+
+	/* Find gap big enough */
+	if (!addr) {
+		for (region = current->vm->regions;
+		     region->next; region = region->next) {
+			if (region->next->start - region->end >= len)
+				break;
+		}
+		addr = (void *) region->end;
+	}
+
+	/* Map anonymous memory */
+	if (flags & MAP_ANONYMOUS) {
+		region = vm_create_region(addr, len, prot, flags, 0, NULL);
+		goto end;
+	}
+
+	/* Map a file */
+	if (fildes < 0 || fildes >= NFILES)
+		return (void *) -EBADF;
+	File *file = current->files->fd[fildes];
+	if (!file)
+		return (void *) -EBADF;
+	region = vm_create_region(addr, len, prot, flags, off, file);
+end:
+	if (!region)
+		return (void *) -ENOMEM;
+	return (void *) region->start;
+}
diff --git a/mem/vm.h b/mem/vm.h
new file mode 100644
index 0000000..eab852d
--- /dev/null
+++ b/mem/vm.h
@@ -0,0 +1,37 @@
+#ifndef KERNEL_MEM_VM_H
+#define KERNEL_MEM_VM_H
+
+#include <stddef.h>
+#include <stdint.h>
+#include <sys/mman.h>
+#include "../vfs/vfs.h"
+
+typedef struct VirtualMemory VirtualMemory;
+typedef struct VMRegion VMRegion;
+typedef struct VMObject VMObject;
+
+/* Virtual Memory Namespace */
+struct VirtualMemory {
+	VMRegion *regions;
+	refcount_t usage;
+};
+
+/* Structure for a Virtual Memory Map Entry */
+struct VMRegion {
+	VMRegion *prev, *next;
+	uintptr_t start, end;
+	int prot;
+	int flags;
+	off_t offset;
+	File *front, *back;
+};
+
+VMRegion *vm_create_region(void *addr, size_t len, int prot, int flags,
+                           off_t offset, File *back);
+void vm_remove_region(VMRegion *region);
+void vm_destroy_region(VMRegion *region);
+VMRegion *vm_clone_regions(VMRegion *head);
+
+int verify_access(const void *addr, size_t len, int prot);
+
+#endif
diff --git a/multiboot.h b/multiboot.h
new file mode 100644
index 0000000..3fde8d0
--- /dev/null
+++ b/multiboot.h
@@ -0,0 +1,37 @@
+#ifndef MULTIBOOT_H
+#define MULTIBOOT_H
+
+#include <stdint.h>
+
+struct MultibootInfo {
+	uint32_t flags;
+	uint32_t memLower;
+	uint32_t memHigher;
+	uint32_t bootDev;
+	uint32_t cmdline;
+	uint32_t modsCount;
+	uint32_t modsAddr;
+
+	uint32_t symsNum;
+	uint32_t symsSize;
+	uint32_t symsAddr;
+	uint32_t symsShndx;
+
+	uint32_t mmapLen;
+	uint32_t mmapAddr;
+	uint32_t drivesLen;
+	uint32_t drivesAddr;
+
+	uint32_t configTable;
+	uint32_t bootLoader;
+	uint32_t apmTable;
+
+	uint32_t vbeControlInfo;
+	uint32_t vbeModeInfo;
+	uint16_t vbeMode;
+	uint16_t vbeInterfaceSeg;
+	uint16_t vbeInterfaceOff;
+	uint16_t vbeInterfaceLen;
+};
+
+#endif
diff --git a/net/arp.c b/net/arp.c
new file mode 100644
index 0000000..8906abb
--- /dev/null
+++ b/net/arp.c
@@ -0,0 +1,118 @@
+/*
+ * This file contains the implementation of ARP for the Network Stack.  It
+ * handles converting IP addresses to MAC addresses, and sits between the
+ * Network layer and the Ethernet layer.
+ */
+
+#include <stdint.h>
+#include <stddef.h>
+#include <string.h>
+#include "net.h"
+#include "../mem/heap.h"
+#include "../screen.h"
+
+typedef struct ARPCacheEntry ARPCacheEntry;
+
+/* Structure for an ARP Cache Entry */
+struct ARPCacheEntry {
+	uint8_t mac[6], ip[4];
+	ARPCacheEntry *next;
+};
+
+/* Structure for an ARP Packet */
+typedef struct Packet {
+	uint16_t hardwareType;
+	uint16_t protocolType;
+	uint8_t hardwareSize;
+	uint8_t protocolSize;
+	uint16_t opcode;
+	uint8_t srcMac[6], srcIp[4];
+	uint8_t dstMac[6], dstIp[4];
+} __attribute__((packed)) Packet;
+
+ARPCacheEntry *arpCache;
+
+/* Request a MAC address from IP with ARP */
+void
+arp_request(NetIF *netif, uint8_t dstIp[4], uint8_t *res)
+{
+	uint8_t dstMac[6] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
+	Packet packet = {
+		.hardwareType = htons(netif->type),
+		.protocolType = htons(0x0800), /* EtherType IPv4 */
+		.hardwareSize = 6,
+		.protocolSize = 4,
+		.opcode = htons(0x0001), /* ARP Request */
+	};
+	memcpy(packet.srcMac, netif->mac, 6);
+	memcpy(packet.srcIp, netif->ip, 4);
+	memcpy(packet.dstMac, dstMac, 6);
+	memcpy(packet.dstIp, dstIp, 4);
+
+	ethernet_send_frame(netif, dstMac, 0x0806, &packet, sizeof(Packet));
+
+	if (!res)
+		return;
+	/* Wait for result */
+	ARPCacheEntry *ce;
+search:
+	for (ce = arpCache; ce; ce = ce->next)
+		if (!memcmp(ce->ip, dstIp, 4))
+			break;
+	if (!ce)
+		goto search;
+	memcpy(res, ce->mac, 6);
+}
+
+/* Reply to an ARP Request */
+void
+arp_reply(NetIF *netif, Packet *request)
+{
+	Packet reply = {
+		.hardwareType = htons(netif->type),
+		.hardwareSize = request->hardwareSize,
+		.protocolType = htons(request->protocolType),
+		.protocolSize = request->protocolSize,
+		.opcode = htons(0x0002), /* ARP Reply */
+	};
+	memcpy(reply.srcMac, netif->mac, 6);
+	memcpy(reply.srcIp, netif->ip, 4);
+	memcpy(reply.dstMac, request->srcMac, 6);
+	memcpy(reply.dstIp, request->srcIp, 4);
+
+	ethernet_send_frame(netif, request->srcMac, 0x0806, &reply,
+	                    sizeof(Packet));
+}
+
+/* Handle an incoming ARP packet */
+void
+arp_receive_packet(NetIF *netif, void *data, size_t len)
+{
+	Packet packet;
+	memcpy(&packet, data, sizeof(Packet));
+	packet.hardwareType = ntohs(packet.hardwareType);
+	packet.protocolType = ntohs(packet.protocolType);
+	packet.opcode = ntohs(packet.opcode);
+
+	switch (packet.opcode) {
+	case 0x0001: /* ARP Request */
+		if (!memcmp(packet.dstIp, netif->ip, 4))
+			arp_reply(netif, &packet);
+		/* FALLTHROUGH */
+	case 0x0002: /* ARP Reply */
+		ARPCacheEntry *ce;
+		for (ce = arpCache; ce; ce = ce->next)
+			if (!memcmp(ce->ip, packet.srcIp, 4))
+				break;
+		if (ce) {
+			memcpy(ce->mac, packet.srcMac, 6);
+			break;
+		}
+		ce = kmalloc(sizeof(ARPCacheEntry));
+		memcpy(ce->mac, packet.srcMac, 6);
+		memcpy(ce->ip, packet.srcIp, 4);
+		ce->next = arpCache;
+		arpCache = ce;
+		break;
+	}
+}
diff --git a/net/ethernet.c b/net/ethernet.c
new file mode 100644
index 0000000..98d6692
--- /dev/null
+++ b/net/ethernet.c
@@ -0,0 +1,63 @@
+/*
+ * This file implements the Ethernet layer of the Network Stack.  It wraps any
+ * packet sent to it with the Ethernet Header.  Equally, it unwraps any packets
+ * before passing them to the next layer, determined by the EtherType in the
+ * header.
+ */
+
+#include <stdint.h>
+#include <stddef.h>
+#include <string.h>
+#include "net.h"
+#include "../mem/heap.h"
+#include "../screen.h"
+
+/* Structure for an Ethernet Header */
+typedef struct Header {
+	uint8_t dst[6];
+	uint8_t src[6];
+	uint16_t ethertype;
+} __attribute__((packed)) Header;
+
+/* Receive a frame of data */
+void
+ethernet_receive_frame(NetIF *netif, void *data, size_t len)
+{
+	Header header;
+	memcpy(&header, data, sizeof(Header));
+	header.ethertype = ntohs(header.ethertype);
+
+	/* Handle appropriately */
+	switch (header.ethertype) {
+	case 0x0800: /* IPv4 */
+		ipv4_receive_packet(netif, data + sizeof(Header),
+		                    len - sizeof(Header));
+		break;
+	case 0x0806: /* ARP */
+		arp_receive_packet(netif, data + sizeof(Header),
+		                   len - sizeof(Header));
+		break;
+//	default:
+//		kprintf("Unhandled type (%04x)", header.ethertype);
+	}
+}
+
+/* Send a frame of data */
+void
+ethernet_send_frame(NetIF *netif, uint8_t dst[6], uint16_t type,
+                    void *data, size_t len)
+{
+	Header header;
+	header.ethertype = htons(type);
+	memcpy(header.src, netif->mac, 6);
+	memcpy(header.dst, dst, 6);
+	size_t frameLen = sizeof(Header) + len;
+	if (frameLen < 64)
+		frameLen = 64;
+
+	char *frame = kmalloc(frameLen);
+	memcpy(frame, &header, sizeof(Header));
+	memcpy(frame + sizeof(Header), data, len);
+	netif->transmit(frame, frameLen);
+	kfree(frame);
+}
diff --git a/net/ipv4.c b/net/ipv4.c
new file mode 100644
index 0000000..ce27ba7
--- /dev/null
+++ b/net/ipv4.c
@@ -0,0 +1,97 @@
+/*
+ * This file contains the IPv4 implementation in the Network Stack.  It sits
+ * above the Ethernet layer, but makes heavy use of the Address Resolution
+ * Protocol.
+ */
+
+#include <stdint.h>
+#include <stddef.h>
+#include <string.h>
+#include "net.h"
+#include "../mem/heap.h"
+#include "../screen.h"
+
+/* Structure for an IPv4 Packet Header */
+typedef struct Header {
+	/* ihl and version are swapped to work with network byte order */
+	uint8_t ihl : 4;
+	uint8_t version : 4;
+	uint8_t service;
+	uint16_t len;
+	uint16_t id;
+	uint16_t flags;
+	uint8_t ttl;
+	uint8_t proto;
+	uint16_t checksum;
+	uint32_t src, dst;
+} __attribute__((packed)) Header;
+
+uint16_t ipid = 1;
+
+/* Calculate an IPv4 checksum */
+static uint16_t
+ipv4_checksum(void *addr, size_t len)
+{
+	register uint32_t sum = 0;
+	while (len > 1) {
+		sum += *(uint16_t *) addr++;
+		len -= 2;
+	}
+	if (len > 0)
+		sum += *(uint8_t *) addr;
+
+	while (sum >> 16)
+		sum = (sum & 0xFFFF) + (sum >> 16);
+
+	return ~sum;
+}
+
+/* Receive an IPv4 Packet */
+void
+ipv4_receive_packet(NetIF *netif, void *data, size_t len)
+{
+	Header header;
+	memcpy(&header, data, sizeof(Header));
+
+	uint8_t src[4];
+	memcpy(src, &header.src, 4);
+	kprintf("Received IPv4 Packet from %d.%d.%d.%d",
+	        src[0], src[1], src[2], src[3]);
+
+	/* Handle packet */
+	switch (header.proto) {
+//	case 0x01: /* ICMP */
+//		break;
+//	case 0x02: /* IGMP */
+//	case 0x06: /* TCP */
+//	case 0x11: /* UDP */
+	default:
+		kprintf("Unhandled IP packet (proto %d)", header.proto);
+	}
+}
+
+/* Transmit an IPv4 Packet */
+void
+ipv4_send_packet(NetIF *netif, uint8_t dst[4], uint8_t proto, uint16_t flags,
+                 void *data, size_t len)
+{
+	Header header;
+	header.ihl = 5;
+	header.version = 4;
+	header.service = 0;
+	header.len = htons(len + sizeof(Header));
+	header.id = htons(ipid++);
+	header.flags = htons(flags);
+	header.ttl = 255;
+	header.proto = proto;
+	memcpy(&header.src, netif->ip, 4);
+	memcpy(&header.dst, dst, 4);
+	header.checksum = ipv4_checksum(&header, sizeof(Header));
+
+	char *packet = kmalloc(len + sizeof(Header));
+	memcpy(packet, &header, sizeof(Header));
+	memcpy(packet + sizeof(Header), data, len);
+	ethernet_send_frame(netif, netif->gatewayMac,
+	                    0x800, packet, len + sizeof(Header));
+	kfree(packet);
+}
diff --git a/net/net.c b/net/net.c
new file mode 100644
index 0000000..5c963dc
--- /dev/null
+++ b/net/net.c
@@ -0,0 +1,90 @@
+/*
+ * This file represents the core of the Kernel Networking System.  It contains
+ * the outwards facing interface, as well as the internal interface for the
+ * lower levels to make use of.
+ */
+
+#include <stdint.h>
+#include <string.h>
+#include "net.h"
+#include "../mem/heap.h"
+
+NetIF *netifs;
+
+/* Switch the endian of integers */
+static uint16_t
+switch_endian16(uint16_t nb)
+{
+	return (nb>>8) | (nb<<8);
+}
+static uint32_t
+switch_endian32(uint32_t nb)
+{
+	return ((nb>>24)&0xff)      |
+	       ((nb<<8)&0xff0000)   |
+	       ((nb>>8)&0xff00)     |
+	       ((nb<<24)&0xff000000);
+}
+
+/* Host to Network byte order */
+uint16_t
+htons(uint16_t hostshort)
+{
+	return switch_endian16(hostshort);
+}
+uint32_t
+htonl(uint32_t hostlong)
+{
+	return switch_endian32(hostlong);
+}
+
+/* Network to Host byte order */
+uint16_t
+ntohs(uint16_t netshort)
+{
+	return switch_endian16(netshort);
+}
+uint32_t
+ntohl(uint32_t netlong)
+{
+	return switch_endian32(netlong);
+}
+
+/* Intialise Networking */
+void
+init_net(void)
+{
+	char ip[] = {10,0,2,2};
+	memcpy(netifs->gatewayIp, ip, 4);
+	arp_request(netifs, ip, netifs->gatewayMac);
+
+	/* Send ICMP ECHO */
+	char icmp[12] = {
+		8, 0,
+		0xF7, 0xFE,
+		0x00, 0x01,
+		0, 0
+	};
+	char pip[] = {1,1,1,1};
+	ipv4_send_packet(netifs, pip, 1, 0, icmp, 12);
+}
+
+/* Register a Network Driver */
+NetIF *
+net_create_interface(uint16_t type, void (*transmit)(void *, size_t),
+                     void (*get_mac)(char *buf))
+{
+	NetIF *netif = kmalloc(sizeof(NetIF));
+	netif->type = type;
+	netif->transmit = transmit;
+	netif->get_mac = get_mac;
+	netif->get_mac(netif->mac);
+	netif->ip[0] = 10;
+	netif->ip[1] =  0;
+	netif->ip[2] =  2;
+	netif->ip[3] = 15;
+
+	/* Link into list */
+	netif->next = netifs;
+	netifs = netif;
+}
diff --git a/net/net.h b/net/net.h
new file mode 100644
index 0000000..3fd113b
--- /dev/null
+++ b/net/net.h
@@ -0,0 +1,40 @@
+#ifndef KERNEL_NET_H
+#define KERNEL_NET_H
+
+#include <stdint.h>
+#include <stddef.h>
+
+typedef struct NetIF NetIF;
+
+/* Structure for a Network Interface */
+struct NetIF {
+	uint16_t type;
+	void (*transmit)(void *, size_t);
+	void (*get_mac)(char *);
+
+	uint8_t ip[4], mac[6];
+	uint8_t gatewayIp[4], gatewayMac[6];
+	uint8_t dnsIp[4], dnsMac[6];
+	NetIF *next;
+};
+
+uint16_t htons(uint16_t hostshort);
+uint32_t htonl(uint32_t hostlong);
+uint16_t ntohs(uint16_t hostshort);
+uint32_t ntohl(uint32_t hostlong);
+void init_net(void);
+NetIF *net_create_interface(uint16_t type, void (*transmit)(void *, size_t),
+                            void (*get_mac)(char *buf));
+
+void ethernet_receive_frame(NetIF *netif, void *data, size_t len);
+void ethernet_send_frame(NetIF *netif, uint8_t dst[6], uint16_t type,
+                         void *data, size_t len);
+
+void arp_receive_packet(NetIF *netif, void *data, size_t len);
+void arp_request(NetIF *netif, uint8_t dstIp[4], uint8_t *res);
+
+void ipv4_receive_packet(NetIF *netif, void *data, size_t len);
+void ipv4_send_packet(NetIF *netif, uint8_t dst[4], uint8_t proto,
+                      uint16_t flags, void *data, size_t len);
+
+#endif
diff --git a/proc/apic.c b/proc/apic.c
new file mode 100644
index 0000000..a784f50
--- /dev/null
+++ b/proc/apic.c
@@ -0,0 +1,258 @@
+/*
+ * This file contains code dealing with the Advanced Programmable Interrupt
+ * Controller system.  It should proved a drop-in replacement for the standard
+ * PIC system, abstracting it in the same way.  It should also be able to handle
+ * a non-SMP system, and fall back on the PIC.
+ */
+
+#include <stdint.h>
+#include "proc.h"
+#include "../mem/heap.h"
+#include "../mem/mem.h"
+#include "../task/task.h"
+#include "../io.h"
+#include "../screen.h"
+#include "../spinlock.h"
+
+/* RSDP Descriptor */
+typedef struct RSDPDescriptor {
+	char signature[8];
+	uint8_t checksum;
+	char oem[6];
+	uint8_t revision;
+	uint32_t rsdt;
+} __attribute__((packed)) RSDPDescriptor;
+
+/* System Descriptor Table Header */
+typedef struct SDTHeader {
+	char signature[4];
+	uint32_t length;
+	uint8_t revision;
+	uint8_t checksum;
+	char oem[6];
+	char oemTable[8];
+	uint32_t oemRevision;
+	uint32_t creator;
+	uint32_t creatorRevision;
+} SDTHeader;
+
+/* IOAPIC Interrupt Source Override */
+typedef struct IOAPICOverride {
+	uint8_t type, length;
+	uint8_t bus, irq;
+	uint8_t gsi, flags;
+} IOAPICOverride;
+
+void cpu_load(void);
+extern void ap_trampoline(void);
+
+uint8_t numCores;
+uint8_t bspId;
+uint32_t lapicPtr, ioapicPtr;
+uint8_t lapicIds[MAX_CPUS], lapicNums[MAX_CPUS];
+Spinlock timerLock;
+
+/* Enable APIC */
+static void
+enable_apic(void)
+{
+	LAPIC(0xF0) = 0x1FF;
+	LAPIC(0x80) = 0;
+}
+
+/* Read from the IOAPIC */
+static uint32_t
+read_io_apic(uint32_t reg)
+{
+	uint32_t volatile *ioapic = (uint32_t volatile *) ioapicPtr;
+	ioapic[0] = (reg & 0xFF);
+	return ioapic[4];
+}
+
+/* Write to the IOAPIC */
+static void
+write_io_apic(uint32_t reg, uint32_t value)
+{
+	uint32_t volatile *ioapic = (uint32_t volatile *) ioapicPtr;
+	ioapic[0] = (reg & 0xFF);
+	ioapic[4] = value;
+}
+
+/* Delay in gaps of 100 microseconds */
+static void
+delay(uint32_t time)
+{
+	float calc = (1193182 / 10000) * time;
+	uint32_t divisor = (uint32_t) calc;
+	outb(0x43, 0x30);
+	outb(0x40, divisor & 0xFF);
+	outb(0x40, (divisor >> 8) & 0xFF);
+	do outb(0x43, 0xE2); while (!(inb(0x40) & (1 << 7)));
+}
+
+/* Start the APIC timer */
+static void
+apic_start_timer(void)
+{
+	/* Don't run while another timer is calibrating */
+	acquire(&timerLock);
+
+	/* Start LAPIC countdown from -1 */
+	LAPIC(0x3E0) = 3;
+	LAPIC(0x380) = 0xFFFFFFFF;
+	delay(10);
+
+	/* Stop after PIT fires, count LAPIC ticks */
+	LAPIC(0x320) = 0x10000;
+	uint32_t ticks = 0xFFFFFFFF - LAPIC(0x390);
+
+	/* Tick every 1ms */
+	LAPIC(0x320) = 0x20000 | 32;
+	LAPIC(0x3E0) = 3;
+	LAPIC(0x380) = ticks;
+
+	release(&timerLock);
+}
+
+/* Initialise SMP (RSDT method) */
+void
+init_multicore(void *ebda)
+{
+	numCores = 1;
+	memset(lapicIds, 0, MAX_CPUS);
+	memset(lapicNums, 0, MAX_CPUS);
+
+	init_lock(&timerLock);
+
+	/* Search for SMP tables, start from EBDA start */
+	char *signature = (char *) ebda;
+	uint8_t found;
+	while (signature < (char *) 0x100000) {
+		/* RSDT */
+		if (!memcmp(signature, "RSD PTR ", 8)) {
+			found = 1;
+			break;
+		}
+		signature++;
+	}
+	/* Fall back to PIC */
+	if (!found)
+		return;
+	/* RSDT checksum */
+	uint32_t check = 0, i, j;
+	for (i = 0; i < sizeof(RSDPDescriptor); i++)
+		check += signature[i];
+	if (check % 0x100)
+		return;
+
+	uint8_t *ptr, *ptr2;
+	uint8_t *rsdt, len;
+	uint8_t overrides[16] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
+	IOAPICOverride *iso;
+
+	/* Find APIC table */
+	kprintf("Searching for APIC table");
+	asm volatile("mov $1, %%eax; cpuid; shrl $24, %%ebx" : "=b" (bspId));
+	numCores = 0;
+	rsdt = (uint8_t *) ((RSDPDescriptor *) signature)->rsdt;
+	len = *((uint32_t *) (rsdt + 4));
+	for (ptr2 = rsdt + 36; ptr2 < rsdt + len; ptr2 += 4) {
+		ptr = *((uint8_t **) ptr2);
+		if (!memcmp(ptr, "APIC", 4))
+			break;
+	}
+	if (memcmp(ptr, "APIC", 4))
+		return;
+
+	/* Read APIC table */
+	lapicPtr = *((uint32_t *) (ptr + 0x24));
+	ptr2 = ptr + *((uint32_t *) (ptr + 4));
+	for (ptr += 44; ptr < ptr2; ptr += ptr[1]) {
+		/* Entries */
+		switch (ptr[0]) {
+		case 0: /* Processor Local APIC */
+			if (numCores >= MAX_CPUS)
+				continue;
+			if (ptr[4] & 1) {
+				lapicNums[ptr[3]] = numCores;
+				lapicIds[numCores] = ptr[3];
+				numCores++;
+			}
+			break;
+		case 1: /* IOAPIC */
+			ioapicPtr = *((uint32_t *) (ptr + 4));
+			break;
+		case 2: /* IOAPIC ISO */
+			iso = (void *) ptr;
+			overrides[iso->irq] = iso->gsi;
+			kprintf("  IRQ#%.2d -> GSI#%.2d",
+			        iso->irq, iso->gsi);
+			break;
+		}
+	}
+
+	/* Send INT#1 to IRQ#33 on CPU#0 */
+	write_io_apic(0x10 + (2 * overrides[1] + 0), 33);
+	write_io_apic(0x10 + (2 * overrides[1] + 1), bspId);
+
+	kprintf("Detected %d CPU%c", numCores, (numCores>1)?'s':'\0');
+
+	/* Initialise APs */
+	uintptr_t apTrampoline = 0x2000;
+	memcpy((void *) apTrampoline, &ap_trampoline, 0x1000);
+	for (i = 0; i < numCores; i++) {
+		if (lapicIds[i] == bspId)
+			continue;
+		/* Give each CPU a separate stack */
+		*((uint32_t *) (apTrampoline + 0xFF0)) = 0x7A000 - (i * 0x2000);
+		/* Send INIT IPI */
+		LAPIC(0x280) = 0;
+		LAPIC(0x310) = (LAPIC(0x310) & 0x00FFFFFF)
+		             | (lapicIds[i] << 24);
+		LAPIC(0x300) = (LAPIC(0x300) & 0xFFF00000)
+		             | 0xC500;
+		do {
+			asm("pause" ::: "memory");
+		} while (LAPIC(0x300) & (1 << 12));
+		LAPIC(0x310) = (LAPIC(0x310) & 0x00FFFFFF)
+		             | (lapicIds[i] << 24);
+		LAPIC(0x300) = (LAPIC(0x300) & 0xFFF00000)
+		             | 0x8500;
+		do {
+			asm("pause" ::: "memory");
+		} while (LAPIC(0x300) & (1 << 12));
+		delay(100);
+		/* Send STARTUP IPI (twice) */
+		for (j = 0; j < 2; j++) {
+			LAPIC(0x280) = 0;
+			LAPIC(0x310) = (LAPIC(0x310) & 0x00FFFFFF)
+			             | (lapicIds[i] << 24);
+			LAPIC(0x300) = (LAPIC(0x300) & 0xFFF0F800)
+			             | (0x0600 + ((apTrampoline >> 12) & 0xFF));
+			delay(2);
+			do {
+				asm("pause" ::: "memory");
+			} while (LAPIC(0x300) & (1 << 12));
+		}
+	}
+
+	/* Enable APIC */
+	enable_apic();
+	/* Disable PIC */
+	outb(0x21, 0xFF);
+	outb(0xA1, 0xFF);
+
+	apic_start_timer();
+}
+
+/* AP initialisation routine */
+void
+ap_startup(void)
+{
+//	enable_apic();
+	cpu_load();
+//	apic_start_timer();
+	current = NULL;
+	kprintf("CPU#%d idling", CPUID);
+	while (1) asm volatile("hlt");
+}
diff --git a/proc/gdt.c b/proc/gdt.c
new file mode 100644
index 0000000..6d9a7c7
--- /dev/null
+++ b/proc/gdt.c
@@ -0,0 +1,104 @@
+/*
+ * This file deals with the Global Descriptor Table.  It creates a simple GDT,
+ * suitable for use in the Kernel, and attaches a TSS to the end.  The TSS,
+ * which can be used to switch from ring 3 to ring 0, allows processes access to
+ * all IO ports.
+ */
+
+#include <stdint.h>
+#include "../mem/heap.h"
+#include "../mem/mem.h"
+#include "../mem/frame.h"
+#include "../proc/proc.h"
+#include "../screen.h"
+
+#define GDT_ENTRIES 6
+
+/* Structure for GDT entry */
+static struct GDTEntry {
+	uint16_t limitLower, baseLower;
+	uint8_t baseMiddle, access, gran, baseHigher;
+} __attribute__((packed)) *GDT;
+
+/* Structure for TSS entry */
+static struct TSSEntry {
+	uint32_t prevTSS;
+	uint32_t esp0, ss0;
+	uint32_t esp1, ss1;
+	uint32_t esp2, ss2;
+	uint32_t cr3, eip, eflags;
+	uint32_t eax, ecx, edx, ebx;
+	uint32_t esp, ebp, esi, edi;
+	uint32_t es, cs, ss, ds, fs, gs;
+	uint32_t ldt;
+	uint16_t trap, iomapBase;
+//	uint32_t iomap[2048];
+} __attribute__((packed)) *TSS; /* Per CPU */
+
+void load_gdt(uint32_t gdtPtr);
+void load_tss(void);
+
+/* Set a gate of the GDT */
+static void
+gdt_set_gate(uint8_t num, uint32_t base, uint32_t limit,
+             uint8_t access, uint8_t gran)
+{
+	GDT[num].baseLower = (base & 0xFFFF);
+	GDT[num].baseMiddle = (base >> 16) & 0xFF;
+	GDT[num].baseHigher = (base >> 24) & 0xFF;
+	GDT[num].limitLower = (limit & 0xFFFF);
+	GDT[num].gran = (limit >> 16) & 0x0F;
+	GDT[num].gran |= gran & 0xF0;
+	GDT[num].access = access;
+}
+
+/* Create a TSS */
+static void
+write_tss(uint8_t num, uint32_t esp)
+{
+	uint32_t base = (uint32_t) TSS;
+	uint32_t limit = sizeof(struct TSSEntry) - 1;
+
+	gdt_set_gate(num, base, limit, 0xE9, 0x00);
+	num -= 5;
+	memset(&TSS[num], 0, sizeof(struct TSSEntry));
+	TSS[num].ss0 = 0x10;
+	TSS[num].esp0 = esp;
+	TSS[num].cs = 0x08 | 0;
+	TSS[num].ds = TSS[num].es = TSS[num].ss = 0x10 | 0;
+	TSS[num].fs = TSS[num].gs = 0x10 | 3;
+	TSS[num].iomapBase = 104;
+}
+
+/* Initialise the Kernel GDT */
+void
+init_gdt(void)
+{
+	GDT = (void *) alloc_frames(1);
+	TSS = (void *) (GDT + GDT_ENTRIES);
+	kprintf("Loaded GDT @ %#.8x, TSS @ %#.8x", GDT, TSS);
+}
+
+/* Load the Kernel GDT */
+void
+cpu_load_gdt(void)
+{
+	memset(GDT, 0, sizeof(GDT[0]) * GDT_ENTRIES);
+
+	gdt_set_gate(0, 0x00000000, 0x00000000, 0x00, 0x00); /* Null segment */
+	/* Ring 0 */
+	gdt_set_gate(1, 0x00000000, 0xFFFFFFFF, 0x9A, 0xCF); /* Code segment */
+	gdt_set_gate(2, 0x00000000, 0xFFFFFFFF, 0x92, 0xCF); /* Data segment */
+	/* Ring 3 */
+	gdt_set_gate(3, 0x00000000, 0xFFFFFFFF, 0xFA, 0xCF); /* Code segment */
+	gdt_set_gate(4, 0x00000000, 0xFFFFFFFF, 0xF2, 0xCF); /* Data segment */
+	/* TSS */
+	write_tss(5, 0xF0800000 - sizeof(uintptr_t));
+
+	uint16_t gdtPtr[3];
+	gdtPtr[0] = (sizeof(GDT[0]) * GDT_ENTRIES) - 1;
+	gdtPtr[1] = ((uint32_t) GDT) & 0xFFFF;
+	gdtPtr[2] = ((uint32_t) GDT) >> 16;
+	load_gdt((uint32_t) &gdtPtr);
+	load_tss();
+}
diff --git a/proc/idt.c b/proc/idt.c
new file mode 100644
index 0000000..a1552c2
--- /dev/null
+++ b/proc/idt.c
@@ -0,0 +1,148 @@
+/*
+ * This file deals with the Interrupt Descriptor Table.  It creates a simple
+ * IDT, with hard-coded handler functions.  It is these handler functions that
+ * can run custom handlers, registered at runtime.  This file does not deal with
+ * dispatching messages/signals to processes on an interrupt.
+ */
+
+#include <stdint.h>
+#include <string.h>
+#include "proc.h"
+#include "../mem/heap.h"
+#include "../mem/frame.h"
+#include "../screen.h"
+
+/* Structure for IDT entry */
+static struct IDTEntry {
+	uint16_t offsetLower, selector;
+	uint8_t zero, typeAttr;
+	uint16_t offsetHigher;
+} __attribute__((packed)) *IDT;
+
+void load_idt(uint32_t ptr[2]);
+/* Exceptions */
+void exc0(void);
+void exc1(void);
+void exc2(void);
+void exc3(void);
+void exc4(void);
+void exc5(void);
+void exc6(void);
+void exc7(void);
+void exc8(void);
+void exc9(void);
+void exc10(void);
+void exc11(void);
+void exc12(void);
+void exc13(void);
+void exc14(void);
+void exc15(void);
+void exc16(void);
+void exc17(void);
+void exc18(void);
+void exc19(void);
+void exc20(void);
+void exc30(void);
+void exc128(void); /* syscall */
+/* Interrutps */
+void irq0(void);
+void irq1(void);
+void irq2(void);
+void irq3(void);
+void irq4(void);
+void irq5(void);
+void irq6(void);
+void irq7(void);
+void irq8(void);
+void irq9(void);
+void irq10(void);
+void irq11(void);
+void irq12(void);
+void irq13(void);
+void irq14(void);
+void irq15(void);
+
+extern void (**exceptions)(InterruptFrame *);
+extern void (**interrupts)(InterruptFrame *);
+
+/* Install an IDT entry */
+static void
+install_idt_entry(uint8_t num, uint32_t addr)
+{
+	IDT[num].offsetLower = addr & 0xFFFF;
+	IDT[num].selector = 0x08;
+	IDT[num].zero = 0;
+	IDT[num].typeAttr = 0x8E | 0x60; /* Allowed from ring 3 */
+	IDT[num].offsetHigher = addr >> 16;
+}
+
+/* Initialise the IDT */
+void
+init_idt(void)
+{
+	IDT = (void *) alloc_frames(1);
+
+	/* Install exceptions */
+	install_idt_entry(0, (uint32_t) exc0);
+	install_idt_entry(1, (uint32_t) exc1);
+	install_idt_entry(2, (uint32_t) exc2);
+	install_idt_entry(3, (uint32_t) exc3);
+	install_idt_entry(4, (uint32_t) exc4);
+	install_idt_entry(5, (uint32_t) exc5);
+	install_idt_entry(6, (uint32_t) exc6);
+	install_idt_entry(7, (uint32_t) exc7);
+	install_idt_entry(8, (uint32_t) exc8);
+	install_idt_entry(9, (uint32_t) exc9);
+	install_idt_entry(10, (uint32_t) exc10);
+	install_idt_entry(11, (uint32_t) exc11);
+	install_idt_entry(12, (uint32_t) exc12);
+	install_idt_entry(13, (uint32_t) exc13);
+	install_idt_entry(14, (uint32_t) exc14);
+	install_idt_entry(15, (uint32_t) exc15);
+	install_idt_entry(16, (uint32_t) exc16);
+	install_idt_entry(17, (uint32_t) exc17);
+	install_idt_entry(18, (uint32_t) exc18);
+	install_idt_entry(19, (uint32_t) exc19);
+	install_idt_entry(20, (uint32_t) exc20);
+	install_idt_entry(30, (uint32_t) exc30);
+
+	/* Install interrutps */
+	install_idt_entry(32, (uint32_t) irq0);
+	install_idt_entry(33, (uint32_t) irq1);
+	install_idt_entry(34, (uint32_t) irq2);
+	install_idt_entry(35, (uint32_t) irq3);
+	install_idt_entry(36, (uint32_t) irq4);
+	install_idt_entry(37, (uint32_t) irq5);
+	install_idt_entry(38, (uint32_t) irq6);
+	install_idt_entry(39, (uint32_t) irq7);
+	install_idt_entry(40, (uint32_t) irq8);
+	install_idt_entry(41, (uint32_t) irq9);
+	install_idt_entry(42, (uint32_t) irq10);
+	install_idt_entry(43, (uint32_t) irq11);
+	install_idt_entry(44, (uint32_t) irq12);
+	install_idt_entry(45, (uint32_t) irq13);
+	install_idt_entry(46, (uint32_t) irq14);
+	install_idt_entry(47, (uint32_t) irq15);
+
+	/* Install syscall handler */
+	install_idt_entry(128, (uint32_t) exc128);
+
+	exceptions = (void (**)(InterruptFrame *)) (IDT + 256);
+	interrupts = (void (**)(InterruptFrame *)) (exceptions + 32);
+	memset(exceptions, 0, 1024);
+
+	kprintf("Loaded IDT @ %#.8x, Exceptions @ %#.8x, Interrupts @ %#.8x",
+	        IDT, exceptions, interrupts);
+}
+
+/* Load the IDT */
+void
+cpu_load_idt(void)
+{
+	uint32_t idtAddr, idtPtr[2];
+	idtAddr = (uint32_t) IDT;
+	idtPtr[0] = sizeof(struct IDTEntry) * 256;
+	idtPtr[0] += (idtAddr & 0xFFFF) << 16;
+	idtPtr[1] = idtAddr >> 16;
+	load_idt(idtPtr);
+}
diff --git a/proc/irqs.c b/proc/irqs.c
new file mode 100644
index 0000000..f9a523c
--- /dev/null
+++ b/proc/irqs.c
@@ -0,0 +1,80 @@
+/*
+ * This file contains the abstraction for exceptions and interrupts.  It
+ * presents a system of registrable exception/interrupt handlers.  It also
+ * contains the generic handlers, which pass control to the appropriate
+ * registered handler.  This file does not deal with dispatching
+ * messages/signals to processes on an interrupt.
+ */
+
+#include <stdint.h>
+#include "proc.h"
+#include "../proc/proc.h"
+#include "../task/task.h"
+#include "../io.h"
+#include "../screen.h"
+
+void (**exceptions)(InterruptFrame *);
+void (**interrupts)(InterruptFrame *);
+
+/* Register an exception handler */
+void
+register_exception(int num, void (*addr)(InterruptFrame *))
+{
+	if (num >= 0 && num < 32)
+		exceptions[num] = addr;
+}
+
+/* Register an interrupt handler */
+void
+register_interrupt(int num, void (*addr)(InterruptFrame *))
+{
+	if (num >= 0 && num < 224)
+		interrupts[num] = addr;
+}
+
+/* Call the appropriate exception handler */
+uintptr_t
+exc_handler(InterruptFrame frame)
+{
+	uint8_t num = frame.intNo & 0xFF;
+
+	/* System Call */
+	if (num == 0x80)
+		syscall_handler(&frame);
+	/* Other exception */
+	else if (exceptions[num])
+		exceptions[num](&frame);
+	else
+		panic("Unhandled exception %d (%#x) @ %#.8x [CPU#%d]\n"
+		      "EFLAGS: %#.8x",
+		      num, frame.errCode, frame.eip, CPUID, frame.eflags);
+
+	/* Send EOI */
+	LAPIC(0xB0) = 0;
+
+	if (current)
+		if (current->tls)
+			return current->tls->start + 4;
+	return 0;
+}
+
+/* Call the appropriate interrupt handler */
+uintptr_t
+irq_handler(InterruptFrame frame)
+{
+	uint8_t num = (frame.intNo & 0xFF) - 32;
+
+	if (interrupts[num])
+		interrupts[num](&frame);
+
+	/* Send EOI */
+	outb(0x20, 0x20);
+	if (num >= 8)
+		outb(0xA0, 0x20);
+	LAPIC(0xB0) = 0;
+
+	if (current)
+		if (current->tls)
+			return current->tls->start + 4;
+	return 0;
+}
diff --git a/proc/load.S b/proc/load.S
new file mode 100644
index 0000000..98330a0
--- /dev/null
+++ b/proc/load.S
@@ -0,0 +1,38 @@
+; This file is responsible for loading the tables onto the CPU.  The functions
+; will load the GDT, IDT and TSS onto the CPU running the function.  They only
+; provide a wrapper for the relevant x86 instruction, and do not do any table
+; creation.  See the relevant C files for the table setup routines.  The GDT and
+; IDT require a pointer to the table, while the TSS can be loaded from the
+; currently loaded GDT.
+
+[bits 32]
+
+; Load the IDT
+[global load_idt]
+load_idt:
+	mov eax, [esp + 4]
+	lidt [eax]
+	ret
+
+; Load the GDT
+[global load_gdt]
+load_gdt:
+	mov eax, [esp + 4]
+	lgdt [eax]
+
+	mov ax, 0x10
+	mov ds, ax
+	mov es, ax
+	mov fs, ax
+	mov gs, ax
+	mov ss, ax
+	jmp 0x08:.flush
+.flush:
+	ret
+
+; Load the TSS
+[global load_tss]
+load_tss:
+	mov ax, 0x28 | 3
+	ltr ax
+	ret
diff --git a/proc/pic.c b/proc/pic.c
new file mode 100644
index 0000000..e478921
--- /dev/null
+++ b/proc/pic.c
@@ -0,0 +1,40 @@
+/*
+ * This file deals with the Programmable Interrupt Controller.  It is usually
+ * disable and replaced by the APIC.
+ */
+
+#include "../io.h"
+
+/* Initialise the PIC to a specified frequency */
+static void
+init_pit(void)
+{
+	uint32_t divisor = 1193182 / 1000;
+	outb(0x43, 0x36);
+	outb(0x40, divisor & 0xFF);
+	outb(0x40, (divisor >> 8) & 0xFF);
+}
+
+/* Initialise the PIC */
+void
+init_pic(void)
+{
+	/*
+	 * By default interrupts and exceptions both start at IRQ#0.  To avoid
+	 * collision we can map interrupts to start at IRQ#32.  Since lower
+	 * numbered IRQ lines are higher priority, exceptions have priority.
+	 */
+	outb(0x20, 0x11); io_wait();
+	outb(0xA0, 0x11); io_wait();
+	outb(0x21, 0x20); io_wait();
+	outb(0xA1, 0x28); io_wait();
+	outb(0x21, 0x04); io_wait();
+	outb(0xA1, 0x02); io_wait();
+	outb(0x21, 0x01); io_wait();
+	outb(0xA1, 0x01); io_wait();
+	outb(0x21, 0x00); io_wait();
+	outb(0xA1, 0x00); io_wait();
+
+	init_pit();
+}
+
diff --git a/proc/proc.h b/proc/proc.h
new file mode 100644
index 0000000..bba44cf
--- /dev/null
+++ b/proc/proc.h
@@ -0,0 +1,32 @@
+#ifndef KERNEL_PROC_H
+#define KERNEL_PROC_H
+
+#include <stdint.h>
+
+/* Structure pushed on interrupt */
+typedef struct InterruptFrame {
+	uint32_t ds, fs, gs;
+	uint32_t edi, esi, ebp, esp, ebx, edx, ecx, eax;
+	uint32_t intNo, errCode;
+	uint32_t eip, cs, eflags, useresp, ss;
+} InterruptFrame;
+
+extern uint8_t numCores;
+extern uint32_t lapicPtr, ioapicPtr;
+extern uint8_t lapicNums[];
+#define LAPIC(off)  (*((uint32_t *) ((uint32_t)  lapicPtr + (off))))
+#define IOAPIC(off) (*((uint32_t *) ((uint32_t) ioapicPtr + (off))))
+#define CPUID lapicNums[(uint8_t) (LAPIC(0x20) >> 24)]
+#define MAX_CPUS 2
+
+void init_pic(void);
+void init_multicore(void *ebda);
+
+void init_idt(void);
+void init_gdt(void);
+void cpu_load_idt(void);
+void cpu_load_gdt(void);
+void register_exception(int num, void (*handler)(InterruptFrame *));
+void register_interrupt(int num, void (*handler)(InterruptFrame *));
+
+#endif
diff --git a/proc/stub.S b/proc/stub.S
new file mode 100644
index 0000000..a16d014
--- /dev/null
+++ b/proc/stub.S
@@ -0,0 +1,149 @@
+; This file contains the stubs for the interrupt handlers, which can push more
+; useful information on the the stack, before calling a generic handler.  Since
+; each exception/interrupt must have it's own handler, macros are used to create
+; a handler for each of them.
+
+[bits 32]
+
+[extern irq_handler]
+[extern exc_handler]
+
+; Exception without error code
+%macro EXC_NOERRCODE 1
+[global exc%1]
+exc%1:
+	cli
+	push byte 0
+	push %1
+	jmp exc_stub
+%endmacro
+
+; Exception with error code
+%macro EXC_ERRCODE 1
+[global exc%1]
+exc%1:
+	cli
+	push %1
+	jmp exc_stub
+%endmacro
+
+; Interrupt
+%macro IRQ 2
+[global irq%1]
+irq%1:
+	cli
+	push byte 0
+	push byte %2
+	jmp irq_stub
+%endmacro
+
+EXC_NOERRCODE 0
+EXC_NOERRCODE 1
+EXC_NOERRCODE 2
+EXC_NOERRCODE 3
+EXC_NOERRCODE 4
+EXC_NOERRCODE 5
+EXC_NOERRCODE 6
+EXC_NOERRCODE 7
+EXC_ERRCODE   8
+EXC_NOERRCODE 9
+EXC_ERRCODE   10
+EXC_ERRCODE   11
+EXC_ERRCODE   12
+EXC_ERRCODE   13
+EXC_ERRCODE   14
+EXC_NOERRCODE 15
+EXC_NOERRCODE 16
+EXC_ERRCODE   17
+EXC_NOERRCODE 18
+EXC_NOERRCODE 19
+EXC_NOERRCODE 20
+EXC_NOERRCODE 30
+EXC_NOERRCODE 128 ; syscall
+
+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
+
+; Handle an exception
+exc_stub:
+	pusha
+	mov ax, gs
+	push eax
+	mov ax, fs
+	push eax
+	mov ax, ds
+	push eax
+
+	mov ax, 0x10
+	mov ds, ax
+	mov es, ax
+	mov fs, ax
+	mov gs, ax
+
+	call exc_handler
+
+	pop ebx
+	mov ds, bx
+	mov es, bx
+	pop ebx
+	mov fs, bx
+	pop ebx
+	mov gs, bx
+
+	mov ecx, 0xC0000101
+	xor edx, edx
+	wrmsr
+
+	popa
+	add esp, 8
+	sti
+	iret
+
+; Handle an interrupt
+irq_stub:
+	pusha
+	mov ax, gs
+	push eax
+	mov ax, fs
+	push eax
+	mov ax, ds
+	push eax
+
+	mov ax, 0x10
+	mov ds, ax
+	mov es, ax
+	mov fs, ax
+	mov gs, ax
+
+	call irq_handler
+
+	pop ebx
+	mov ds, bx
+	mov es, bx
+	pop ebx
+	mov fs, bx
+	pop ebx
+	mov gs, bx
+
+	mov ecx, 0xC0000101
+	xor edx, edx
+	wrmsr
+
+	popa
+	add esp, 8
+	sti
+	iret
diff --git a/proc/trampoline.S b/proc/trampoline.S
new file mode 100644
index 0000000..8a8656f
--- /dev/null
+++ b/proc/trampoline.S
@@ -0,0 +1,52 @@
+; This file contains the AP trampoline code to bounce it back into protected
+; mode and run the Kernel on them.  The location of the stack is set by the
+; calling code.  This code runs initially in real (16-bit) mode, at 0x0000 in
+; physical memory.  Modifying this code should be done with care, since it makes
+; use of fixed offsets in memory to jump around.
+
+[bits 16]
+
+[extern ap_startup]
+
+; This is the code run by the APs on startup.  They need to initialise
+; themselves, which includes setting up a GDT and switching to protected mode.
+; When they're in protected mode they can just run their main C function.
+[global ap_trampoline]
+ap_trampoline:
+	cli
+	cld
+	jmp 0x0000:0x2040 ; continue
+
+align 16
+.gdt_start:
+	dd 0, 0
+	dd 0x0000FFFF, 0x00CF9A00 ; Flat Code
+	dd 0x0000FFFF, 0x00CF9200 ; Flat Data
+	dd 0x00000068, 0x00CF8900 ; TSS
+.gdt_desc:
+	dw .gdt_desc - .gdt_start - 1
+	dd 0x2010 ; gdt_start
+	dd 0, 0
+
+align 64
+.continue:
+	xor ax, ax
+	mov ds, ax
+	lgdt [0x2030] ; gdt_desc
+	mov eax, cr0
+	or eax, 0x01
+	mov cr0, eax
+	jmp 0x08:0x2060 ; pm
+
+align 32
+[bits 32]
+.pm:
+	mov ax, 0x10
+	mov ds, ax
+	mov ss, ax
+	; Load passed stack
+	mov eax, [0x2FF0]
+	mov esp, eax
+	mov ebp, esp
+	sti
+	jmp 0x08:ap_startup
diff --git a/screen.c b/screen.c
new file mode 100644
index 0000000..3a722cc
--- /dev/null
+++ b/screen.c
@@ -0,0 +1,356 @@
+/*
+ * This file controls the kernel screen.  It has several routines which can
+ * output text to the debug port and the the VGA Text Monitor.  Most of these
+ * functions are for logging - except the Kernel Panic routine.  Hopefully it's
+ * never called.  It should probably also halt execution on all CPUs, not just
+ * the local one.  This would need IPIs to be sent, which can be implemented
+ * later.
+ */
+
+#include <stdarg.h>
+#include "mem/mem.h"
+#include "mem/vm.h"
+#include "task/task.h"
+#include "io.h"
+#include "spinlock.h"
+
+#define IS_DIGIT(c) ((c) >= '0' && (c) <= '9')
+
+#define ZEROPAD	1  /* Pad with zero */
+#define SIGN    2  /* Insigned/signed long */
+#define PLUS    4  /* Show plus */
+#define SPACE   8  /* Space if plus */
+#define LEFT    16 /* Left justified */
+#define SPECIAL 32 /* 0x */
+#define SMALL   64 /* Use 'abcdef' instead of 'ABCDEF' */
+
+#define DO_DIV(n,base) ({                                                     \
+	int __res;                                                            \
+	__asm__("divl %4":"=a" (n),"=d" (__res):"0" (n),"1" (0),"r" (base));  \
+	__res;                                                                \
+})
+
+Spinlock screenLock;
+
+/* Do not convert */
+static int
+skip_atoi(const char **s)
+{
+	int i = 0;
+
+	while (IS_DIGIT(**s))
+		i = i*10 + *((*s)++) - '0';
+
+	return i;
+}
+
+/* Convert a number to ASCII */
+static char *
+number(char *str, int num, int base, int size, int precision, int type)
+{
+	char c, sign, tmp[36];
+	const char *digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+	int i;
+
+	if (type & SMALL)
+		digits = "0123456789abcdefghijklmnopqrstuvwxyz";
+	if (type & LEFT)
+		type &= ~ZEROPAD;
+	if (base < 2 || base > 36)
+		return 0;
+	c = (type & ZEROPAD) ? '0' : ' ' ;
+	if (type & SIGN && num < 0) {
+		sign = '-';
+		num = -num;
+	} else {
+		sign = (type & PLUS) ? '+' : ((type & SPACE) ? ' ' : 0);
+	}
+	if (sign)
+		size--;
+	if (type & SPECIAL)
+		if (base == 16)
+			size -= 2;
+		else if (base == 8)
+			size--;
+	i = 0;
+	if (num == 0)
+		tmp[i++] = '0';
+	else while (num != 0)
+		tmp[i++] = digits[DO_DIV(num,base)];
+	if (i > precision)
+		precision = i;
+	size -= precision;
+	if (!(type & (ZEROPAD + LEFT)))
+		while (size-- > 0)
+			*str++ = ' ';
+	if (sign)
+		*str++ = sign;
+	if (type & SPECIAL)
+		if (base == 8) {
+			*str++ = '0';
+		} else if (base == 16) {
+			*str++ = '0';
+			*str++ = digits[33];
+		}
+	if (!(type & LEFT))
+		while (size-- > 0)
+			*str++ = c;
+	while (i < precision--)
+		*str++ = '0';
+	while (i-- > 0)
+		*str++ = tmp[i];
+	while (size-- > 0)
+		*str++ = ' ';
+	return str;
+}
+
+/* Print formatted to a buffer */
+static int
+vsprintf(char *buf, const char *fmt, va_list args)
+{
+	int len, i;
+	char *str, *s, *p;
+	int *ip, flags;
+	int field_width, precision, qualifier;
+
+	for (str = buf; *fmt; fmt++) {
+		if (*fmt != '%') {
+			*str++ = *fmt;
+			continue;
+		}
+
+		/* Process flags */
+		flags = 0;
+repeat:
+		fmt++;
+		switch (*fmt) {
+		case '-':
+			flags |= LEFT;
+			goto repeat;
+		case '+':
+			flags |= PLUS;
+			goto repeat;
+		case ' ':
+			flags |= SPACE;
+			goto repeat;
+		case '#':
+			flags |= SPECIAL;
+			goto repeat;
+		case '0':
+			flags |= ZEROPAD;
+			goto repeat;
+		}
+
+		/* Get field width */
+		field_width = -1;
+		if (IS_DIGIT(*fmt)) {
+			field_width = skip_atoi(&fmt);
+		} else if (*fmt == '*') {
+			field_width = va_arg(args, int);
+			if (field_width < 0) {
+				field_width = -field_width;
+				flags |= LEFT;
+			}
+		}
+
+		/* Get the precision */
+		precision = -1;
+		if (*fmt == '.') {
+			fmt++;
+			if (IS_DIGIT(*fmt)) {
+				precision = skip_atoi(&fmt);
+			} else if (*fmt == '*') {
+				precision = va_arg(args, int);
+			}
+			if (precision < 0)
+				precision = 0;
+		}
+
+		/* Get the conversion qualifier */
+		qualifier = -1;
+		if (*fmt == 'h' || *fmt == 'l' || *fmt == 'L') {
+			qualifier = *fmt;
+			fmt++;
+		}
+
+		switch (*fmt) {
+		case 'c':
+			if (!(flags & LEFT))
+				while (--field_width > 0)
+					*str++ = ' ';
+			*str++ = (unsigned char) va_arg(args, int);
+			while (--field_width > 0)
+				*str++ = ' ';
+			break;
+
+		case 's':
+			s = va_arg(args, char *);
+			len = 0;
+			p = s;
+			while (*p++) len++;
+			if (precision < 0)
+				precision = len;
+			else if (len > precision)
+				len = precision;
+
+			if (!(flags & LEFT))
+				while (len < field_width--)
+					*str++ = ' ';
+			for (i = 0; i < len; ++i)
+				*str++ = *s++;
+			while (len < field_width--)
+				*str++ = ' ';
+			break;
+
+		case 'o':
+			str = number(str, va_arg(args, unsigned long), 8,
+				field_width, precision, flags);
+			break;
+
+		case 'p':
+			if (field_width == -1) {
+				field_width = 8;
+				flags |= ZEROPAD;
+			}
+			str = number(str,
+				(unsigned long) va_arg(args, void *), 16,
+				field_width, precision, flags);
+			break;
+
+		case 'x':
+			flags |= SMALL;
+			/* FALLTHROUGH */
+		case 'X':
+			str = number(str, va_arg(args, unsigned long), 16,
+				field_width, precision, flags);
+			break;
+
+		case 'd': /* FALLTHROUGH */
+		case 'i':
+			flags |= SIGN;
+			/* FALLTHROUGH */
+		case 'u':
+			str = number(str, va_arg(args, unsigned long), 10,
+				field_width, precision, flags);
+			break;
+
+		case 'n':
+			ip = va_arg(args, int *);
+			*ip = (str - buf);
+			break;
+
+		default:
+			if (*fmt != '%')
+				*str++ = '%';
+			if (*fmt)
+				*str++ = *fmt;
+			else
+				--fmt;
+			break;
+		}
+	}
+	*str = '\0';
+	return str-buf;
+}
+
+/* Format a string into a buffer */
+int
+sprintf(char *buf, char *fmt, ...)
+{
+	int ret;
+	va_list args;
+	va_start(args, fmt);
+	ret = vsprintf(buf, fmt, args);
+	va_end(args);
+	return ret;
+}
+
+/* Print a character */
+static inline void
+print_char(char c)
+{
+	outb(0xE9, c);
+}
+
+/* Set the output colour */
+static inline void
+set_attribute(char c, char *esc)
+{
+	while (*esc)
+		outb(0xE9, *esc++);
+}
+
+/* Kernel panic */
+void
+panic(char *fmt, ...)
+{
+	char buf[1024], *error = buf;
+
+	/* Print error to serial port */
+	acquire(&screenLock);
+	set_attribute(0x04, "\033[31m");
+	va_list args;
+	va_start(args, fmt);
+	vsprintf(buf, fmt, args);
+	va_end(args);
+	while (*error) print_char(*error++);
+	print_char('\n');
+	release(&screenLock);
+
+	/* Hang */
+	while (1) asm("hlt");
+}
+
+/* Kernel print */
+void
+kprintf(char *fmt, ...)
+{
+	char buf[1024], *error = buf;
+
+	/* Print to debug port */
+	acquire(&screenLock);
+	set_attribute(0x03, "\033[36m");
+	va_list args;
+	va_start(args, fmt);
+	vsprintf(buf, fmt, args);
+	va_end(args);
+	while (*error) print_char(*error++);
+	print_char('\n');
+	release(&screenLock);
+}
+
+/* Debug prefix print */
+static void
+dbgprefix(char *fmt, ...)
+{
+	char buf[1024], *error = buf;
+
+	/* Print to debug port */
+	set_attribute(0x0E, "\033[93;01m");
+	va_list args;
+	va_start(args, fmt);
+	vsprintf(buf, fmt, args);
+	va_end(args);
+	while (*error) print_char(*error++);
+}
+
+/* Debug print */
+void
+dbgprintf(char *msg)
+{
+	size_t len = 1024;
+
+	if (!msg)
+		return;
+
+	if (!verify_access(msg, strnlen(msg, len), PROT_READ))
+		return;
+
+	/* Print to debug port */
+	acquire(&screenLock);
+	dbgprefix("%s(%d:%d): ", current->name, current->tgid, current->tid);
+	set_attribute(0x0F, "\033[0m");
+	while (*msg && len--) print_char(*msg++);
+	print_char('\n');
+	release(&screenLock);
+}
diff --git a/screen.h b/screen.h
new file mode 100644
index 0000000..3fbda71
--- /dev/null
+++ b/screen.h
@@ -0,0 +1,16 @@
+#ifndef KERNEL_SCREEN_H
+#define KERNEL_SCREEN_H
+
+int sprintf(char *buf, char *fmt, ...);
+void panic(char *fmt, ...);
+void kprintf(char *fmt, ...);
+void dbgprintf(char *msg);
+
+#define ASSERT(c)  ({ \
+        if (!(c)) \
+        	panic("Assertion failed (%s:%d): " #c, \
+        	      __FILE__, __LINE__); \
+        c; \
+})
+
+#endif
diff --git a/spinlock.c b/spinlock.c
new file mode 100644
index 0000000..059f65e
--- /dev/null
+++ b/spinlock.c
@@ -0,0 +1,70 @@
+/*
+ * This file contains the implementation of spinlocks.  It makes heavy use of
+ * GCC's atomic built-ins for synchronization.  The spinlocks have some simple
+ * mechanisms for preventing deadlocks.  Each spinlock knows which CPU/task is
+ * holding it, and can allow that CPU/task to acquire it multiple times and
+ * safely release it.
+ */
+
+#include <stdint.h>
+#include "task/task.h"
+#include "proc/proc.h"
+#include "spinlock.h"
+#include "screen.h"
+#include "io.h"
+
+/* Check if already holding */
+static uint8_t
+holding(Spinlock *lock)
+{
+	uint8_t r;
+	r = lock->locked;
+	r &= (current ? lock->task == current->tid : lock->cpu == CPUID);
+	return r;
+}
+
+/* Create a new lock */
+void
+init_lock(Spinlock *lock)
+{
+	lock->locked = 0;
+	lock->task = 0;
+	lock->cpu = 0;
+	lock->ref = 0;
+}
+
+/* Acquire a lock */
+void
+acquire(Spinlock *lock)
+{
+	/*
+	 * Reference count the lock so it can be safely acquired by the same
+	 * holder multiple times.  This stops a lock from deadlocking itself.
+	 */
+	if (holding(lock)) {
+		lock->ref++;
+		return;
+	}
+	while (!__sync_bool_compare_and_swap(&lock->locked, 0, 1))
+		asm volatile("pause");
+	__sync_synchronize();
+	if (current)
+		lock->task = current->tid;
+	lock->cpu = CPUID;
+}
+
+/* Release a lock */
+void
+release(Spinlock *lock)
+{
+	if (!holding(lock))
+		panic("Cannot release unheld lock");
+	if (lock->ref) {
+		lock->ref--;
+		return;
+	}
+	lock->task = 0;
+	lock->cpu = 0;
+	__sync_lock_release(&lock->locked);
+	__sync_synchronize();
+}
diff --git a/spinlock.h b/spinlock.h
new file mode 100644
index 0000000..75d7154
--- /dev/null
+++ b/spinlock.h
@@ -0,0 +1,19 @@
+#ifndef KERNEL_SPINLOCK_H
+#define KERNEL_SPINLOCK_H
+
+#include <stdint.h>
+#include <sys/types.h>
+
+/* Structure for a spinlock */
+typedef struct Spinlock {
+	uint32_t locked;
+	pid_t task;
+	uint8_t cpu;
+	uint8_t ref;
+} Spinlock;
+
+void init_lock(Spinlock *lock);
+void acquire(Spinlock *lock);
+void release(Spinlock *lock);
+
+#endif
diff --git a/task/exec.c b/task/exec.c
new file mode 100644
index 0000000..dffc92b
--- /dev/null
+++ b/task/exec.c
@@ -0,0 +1,321 @@
+/*
+ * This file deals with loading programs from the Kernel File System.  The
+ * programs in KernelFS are statically linked, so they just need to be copied
+ * into their address space and run.  Since KernelFS is present in memory, this
+ * process is very simple.
+ */
+
+#include <stdint.h>
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+#include "../proc/proc.h"
+#include "../task/task.h"
+#include "../mem/heap.h"
+#include "../mem/paging.h"
+#include "../mem/vm.h"
+#include "../vfs/vfs.h"
+
+#define KFS_START ((void *) 0x100000)
+
+/* Structure of a KernelFS file listing */
+typedef struct KFSEntry {
+	char name[27];
+	uint8_t size;
+	uint32_t start;
+} __attribute__((packed)) KFSEntry;
+
+/* ELF File Header */
+typedef struct ELFHeader {
+	char magic[4];
+	uint8_t arch;
+	uint8_t endian;
+	uint8_t headerVersion;
+	uint8_t osAbi;
+	uint8_t reserved[8];
+	uint16_t type;
+	uint16_t isa;
+	uint32_t version;
+	uint32_t entry;
+	uint32_t programHeader;
+	uint32_t sectionHeader;
+	uint32_t flags;
+	uint16_t headerSize;
+	uint16_t programEntrySize;
+	uint16_t numProgramEntries;
+	uint16_t sectionEntrySize;
+	uint16_t numSectionEntries;
+	uint16_t sectionNames;
+} ELFHeader;
+
+/* ELF Section Header */
+typedef struct SectionHeader {
+	uint32_t name;
+	uint32_t type;
+	uint32_t flags;
+	uint32_t address;
+	uint32_t offset;
+	uint32_t size;
+	uint32_t link;
+	uint32_t info;
+	uint32_t align;
+	uint32_t entrySize;
+} SectionHeader;
+
+/* ELF Program Header */
+typedef struct ProgramHeader {
+	uint32_t type;
+	uint32_t offset;
+	uint32_t address;
+	uint32_t reserved;
+	uint32_t filesz;
+	uint32_t memsz;
+	uint32_t flags;
+	uint32_t align;
+} ProgramHeader;
+
+static KFSEntry *
+get_file_by_name(char *name)
+{
+	KFSEntry *index;
+	for (index = KFS_START;
+	     index < (KFSEntry *) (KFS_START + 512);
+	     index++) {
+		if (!strcmp(name, index->name))
+			return index;
+	}
+	return (KFSEntry *) 0;
+}
+
+/* Get SectionHeader by index */
+static SectionHeader *
+section_header(ELFHeader *header, uint16_t index)
+{
+	SectionHeader *section;
+	section = (void *) ((char *) header + header->sectionHeader +
+	          (index * header->sectionEntrySize));
+	return section;
+}
+
+/* Get a Section name */
+static char *
+section_name(ELFHeader *header, uint32_t index)
+{
+	char *data = (char *) header + section_header(header,
+	             header->sectionNames)->offset;
+	return data + index;
+}
+
+/* Get ProgramHeader by index */
+static ProgramHeader *
+program_header(ELFHeader *header, uint16_t index)
+{
+	ProgramHeader *program;
+	program = (void *) ((char *) header + header->programHeader +
+	          (index * header->programEntrySize));
+	return program;
+}
+
+extern TaskQueue tasks;
+
+/* Execute a program */
+int
+execve(const char *file, char *argv[], char *envp[])
+{
+	if (current->tid != current->tgid) {
+		/*
+		 * TODO: This should execute the program to execute in the
+		 * "thread group leader" (the Task where tid = current->tgid)
+		 * and all other threads in the group should be exited.
+		 */
+		return -EFAULT;
+	}
+
+	if (!verify_access(file, strnlen(file, PATH_MAX), PROT_READ))
+		return -EFAULT;
+
+	/* Count argv and envp */
+	int argc, argi, envc, envi;
+	if (argv == NULL) argc = 0;
+	else for (argc = 0; argv[argc] != NULL; argc++);
+	if (envp == NULL) envc = 0;
+	else for (envc = 0; envp[envc] != NULL; envc++);
+	/* Find size of argv and envp strings */
+	size_t ssz = sizeof(int)
+	           + (sizeof(uintptr_t) * (argc + 1))
+	           + (sizeof(uintptr_t) * (envc + 1));
+	for (argi = 0; argi < argc; argi++) {
+		if (!verify_access(argv[argi], strlen(argv[argi]), PROT_READ))
+			return -EFAULT;
+		ssz += strlen(argv[argi]) + 1;
+	}
+	for (envi = 0; envi < envc; envi++) {
+		if (!verify_access(envp[envi], strlen(envp[envi]), PROT_READ))
+			return -EFAULT;
+		ssz += strlen(envp[envi]) + 1;
+	}
+
+	/* Read ELF header */
+	ELFHeader header;
+	int fd = open(file, O_RDONLY);
+	if (fd < 0)
+		return fd;
+
+	current->inSyscall = 0;
+
+	/* Only execute regular files */
+	if (!S_ISREG(current->files->fd[fd]->mode)) {
+		close(fd);
+		return -EACCES;
+	}
+
+	/* Read file header */
+	read(fd, &header, sizeof(ELFHeader));
+	if (memcmp(header.magic, "\x7F""ELF", 4) || header.isa != 3) {
+		close(fd);
+		return -ENOEXEC;
+	}
+	if (header.type != 2) { /* 1: relocatable, 2: executable */
+		close(fd);
+		return -ENOEXEC;
+	}
+
+	/*
+	 * POINT OF NO RETURN
+	 */
+
+	/* Set process name */
+	kfree(current->name);
+	current->name = kmalloc(strlen(file)+1);
+	memcpy(current->name, file, strlen(file)+1);
+
+	/* Store everything (pointers adjusted) in temporary buffer */
+	uintptr_t esp = 0xE0000000 - ssz;
+	char *istack = kmalloc(ssz);
+	char *isp = istack + ssz;
+	for (envi = envc - 1; envi >= 0; envi--) {
+		isp -= strlen(envp[envi]) + 1;
+		memcpy(isp, envp[envi], strlen(envp[envi]) + 1);
+		envp[envi] = (char *) (isp - istack) + esp;
+	}
+	if (envp)
+		envp[envc] = NULL;
+	for (argi = argc - 1; argi >= 0; argi--) {
+		isp -= strlen(argv[argi]) + 1;
+		memcpy(isp, argv[argi], strlen(argv[argi]) + 1);
+		argv[argi] = (char *) (isp - istack) + esp;
+	}
+	if (argv)
+		argv[argc] = NULL;
+	isp -= sizeof(uintptr_t);
+	*((uintptr_t *) isp ) = (uintptr_t) NULL;
+	isp -= sizeof(uintptr_t) * envc;
+	memcpy(isp, envp, sizeof(uintptr_t) * envc);
+	isp -= sizeof(uintptr_t);
+	*((uintptr_t *) isp ) = (uintptr_t) NULL;
+	isp -= sizeof(uintptr_t) * argc;
+	memcpy(isp, argv, sizeof(uintptr_t) * argc);
+	isp -= sizeof(int);
+	*((int *) isp) = argc;
+
+	/* Destroy previous executable */
+	VMRegion *head;
+	for (head = current->vm->regions; head; head = head->next)
+		vm_destroy_region(head);
+
+	/* Program headers */
+	size_t p;
+	off_t off;
+	uintptr_t pgbrk, heapEnd;
+	ProgramHeader ph, tlsph;
+	memset(&tlsph, 0, sizeof(ProgramHeader));
+	for (p = 0; p < header.numProgramEntries; p++) {
+		off = header.programHeader + (p * header.programEntrySize);
+		lseek(fd, off, 0);
+		read(fd, &ph, sizeof(ProgramHeader));
+		if (ph.type != 1) {
+			if (ph.type == 7)
+				memcpy(&tlsph, &ph, sizeof(ProgramHeader));
+			continue;
+		}
+
+		/* Map data into region */
+		mmap((void *) ph.address, ph.filesz, ph.flags,
+		     MAP_PRIVATE, fd, ph.offset & ~0xFFF);
+		/* Space left before */
+		if (ph.address & 0xFFF) {
+			memset((void *) (ph.address & ~0xFFF), 0,
+			       ph.address - (ph.address & ~0xFFF));
+		}
+		/* Unset memory */
+		if (ph.memsz > ph.filesz) {
+			pgbrk = (ph.address + ph.filesz + 0xFFF) & ~0xFFF;
+			memset((void *) (ph.address + ph.filesz), 0,
+			       pgbrk - ph.address - ph.filesz);
+			if (ph.memsz > pgbrk - ph.address)
+				mmap((void *) pgbrk,
+				     ph.memsz - (pgbrk - ph.address),
+				     ph.flags, MAP_PRIVATE | MAP_ANONYMOUS,
+				     -1, 0);
+		}
+		if (ph.address + ph.memsz > heapEnd)
+			heapEnd = ph.address + ph.memsz;
+	}
+
+	/* Store executable */
+	current->executable = file_get(current->files->fd[fd]);
+	close(fd);
+
+	/* Thread Local Storage */
+	/* FIXME */
+	if (current->tls)
+		vm_destroy_region(current->tls);
+	if (tlsph.type == 7) {
+		/* should be filesz not memsz */
+		tlsph.flags |= PROT_WRITE;
+		current->tls = vm_create_region((void *) ((heapEnd + 0xFFF) & ~0xFFF),
+		                                tlsph.memsz + 4, tlsph.flags,
+		                                MAP_PRIVATE | MAP_ANONYMOUS,
+		                                0, NULL);
+//		                                tlsph.offset, current->executable);
+		vm_remove_region(current->tls);
+		*((uint32_t *) current->tls->start + 1) = current->tls->start + 4;
+		if (tlsph.filesz)
+			memcpy((void *) current->tls->start,
+			       (void *) tlsph.address, tlsph.filesz);
+	}
+
+	/* Stack area */
+	VMRegion *oldstack = current->stack;
+	current->stack = vm_create_region((void *) 0xDFC00000, 0x400000,
+	                                  PROT_READ | PROT_WRITE,
+	                                  MAP_PRIVATE | MAP_ANONYMOUS, 0, NULL);
+	vm_remove_region(current->stack);
+	if (oldstack)
+		vm_destroy_region(oldstack);
+
+	memcpy((void *) esp, istack, ssz);
+	kfree(istack);
+
+	/* Switch to user-mode */
+	asm volatile(
+		"cli;"
+		"mov $0x23, %%ax;"
+		"mov %%ax, %%ds;"
+		"mov %%ax, %%es;"
+		"mov %%ax, %%fs;"
+		"mov %%ax, %%gs;"
+		"mov %%esi, %%eax;" // "movl %%esp, %%eax;"
+		"pushl $0x23;"
+		"pushl %%eax;"
+		"pushf;"
+		"pop %%eax;"
+		"or $0x200, %%eax;" /* Enable interrupts */
+		"push %%eax;"
+		"pushl $0x1B;"
+		"pushl %%ebx;"
+		"iret;"
+		: : "b" (header.entry), "S" (esp)
+	);
+	/* UNREACHED */
+}
diff --git a/task/ipc.c b/task/ipc.c
new file mode 100644
index 0000000..1faafd9
--- /dev/null
+++ b/task/ipc.c
@@ -0,0 +1,131 @@
+/*
+ * This file contains all the functions related to message passing and
+ * inter-process communication.
+ */
+
+#include <stdint.h>
+#include <stddef.h>
+#include <sys/ipc.h>
+#include "task.h"
+#include "../proc/proc.h"
+#include "../mem/mem.h"
+#include "../mem/heap.h"
+#include "../spinlock.h"
+
+extern TaskQueue readyQueue;
+
+/* Block until a message is received */
+static uint8_t
+block_recv(Task *task, uint32_t from)
+{
+//	Message *head, *prev;
+//	/* Blocking for RECEIVE from any */
+//	if (!task->msgQueue)
+//		return 0;
+//	/* Block for RECEIVE from specific process */
+//	if (task->msgQueue->from != from && from != ANY) {
+//		for (head = task->msgQueue;
+//		     head->from != from;
+//		     prev = head, head = head->next);
+//		/* End of list */
+//		if (!head) return 0;
+//		/* Move message to start of queue */
+//		if (head != task->msgQueue) {
+//			prev->next = head->next;
+//			head->next = task->msgQueue;
+//			task->msgQueue = head;
+//		}
+//	}
+//
+//	return 1;
+}
+
+/* Send a message */
+Message *
+nb_send_msg(pid_t to, uint16_t type, MessageContent *msg)
+{
+//	Message *item, *msgSearch;
+//	Task *taskSearch;
+//
+//	item = kmalloc(sizeof(Message));
+//	memcpy(&item->msg, msg, sizeof(MessageContent));
+//	item->from = current->tid;
+//	item->type = type;
+//	item->next = 0;
+//
+//	/* Find target process */
+//	for (taskSearch = readyQueue;
+//	     taskSearch->tid != to && taskSearch;
+//	     taskSearch = taskSearch->next);
+//	/* Add message to queue */
+//	if (taskSearch) {
+//		acquire(&taskSearch->lock);
+//		if (taskSearch->msgQueue) {
+//			for (msgSearch = taskSearch->msgQueue;
+//			     msgSearch->next;
+//			     msgSearch = msgSearch->next);
+//			msgSearch->next = item;
+//		} else {
+//			taskSearch->msgQueue = item;
+//		}
+//		release(&taskSearch->lock);
+//	}
+//
+//	if (taskSearch)
+//		return item;
+//	kfree(item);
+//	return NULL;
+}
+
+/* Send a message and block until it is delivered */
+Message *
+send_msg(pid_t to, uint16_t type, MessageContent *msg)
+{
+//	Message *nb = nb_send_msg(to, type, msg);
+//	if (!nb) return NULL;
+//	block(block_send, (uint32_t) nb);
+//	return nb;
+}
+
+/* Receive a message */
+pid_t
+nb_recv_msg(Message *buf, pid_t from)
+{
+//	Message *msg;
+//	Task *taskSearch;
+//
+//	acquire(&current->lock);
+//	msg = current->msgQueue;
+//	if (msg && (msg->from == from || from == ANY)) {
+//		current->msgQueue = msg->next;
+//		memcpy(buf, msg, sizeof(Message));
+//		kfree(msg);
+//	}
+//	release(&current->lock);
+//
+//	if (msg && (buf->from == from || from == ANY)) {
+//		/* Find sending process */
+//		for (taskSearch = readyQueue;
+//		     taskSearch->tid != buf->from && taskSearch;
+//		     taskSearch = taskSearch->next);
+//		if (taskSearch) {
+//			if (taskSearch->block.function == block_send
+//			 && taskSearch->block.condition == (uint32_t) msg)
+//				taskSearch->block.function = NULL;
+//		}
+//		return buf->from;
+//	}
+//	return 0;
+}
+
+/* Block until a message is received */
+pid_t
+recv_msg(Message *buf, pid_t from)
+{
+//	pid_t nb;
+//check:
+//	nb = nb_recv_msg(buf, from);
+//	if (nb) return nb;
+//	block(block_recv, from);
+//	goto check;
+}
diff --git a/task/process.S b/task/process.S
new file mode 100644
index 0000000..131481f
--- /dev/null
+++ b/task/process.S
@@ -0,0 +1,23 @@
+; This file contains a few assembly routines related to tasking.  These routines
+; should be minimal, and get called from within various functions from the
+; tasking system.  See the relevant C source for a better description of how the
+; functions work.
+
+; Get the return address
+[global read_eip]
+read_eip:
+	mov eax, [esp]
+	ret
+
+; Switch to a task's context
+[global context_switch]
+context_switch:
+	cli
+	mov ecx, [esp + 4]
+	mov eax, [esp + 8]
+	mov ebp, [esp + 12]
+	mov esp, [esp + 16]
+	mov cr3, eax
+	mov eax, 0x10032004 ; Magic number
+	sti
+	jmp ecx
diff --git a/task/schedule.c b/task/schedule.c
new file mode 100644
index 0000000..1c00576
--- /dev/null
+++ b/task/schedule.c
@@ -0,0 +1,182 @@
+/*
+ * This file controls the Kernel's scheduler.  It decides when a new task must
+ * be scheduled, and which tasks can actually be scheduled.
+ */
+
+#include <stdint.h>
+#include "../proc/proc.h"
+#include "../mem/paging.h"
+#include "task.h"
+
+uintptr_t read_eip(void);
+void context_switch(uintptr_t eip, page_dir_t phys,
+                    uintptr_t ebp, uintptr_t esp);
+void handle_signals(void);
+
+TaskQueue readyQueue, tasks;
+Task *currentTask[MAX_CPUS];
+
+/* Switch to a task */
+static void
+switch_to_task(Task *task)
+{
+	uintptr_t esp, ebp, eip;
+	asm volatile("mov %%esp, %0" : "=r" (esp));
+	asm volatile("mov %%ebp, %0" : "=r" (ebp));
+	eip = read_eip();
+	if (eip == 0x10032004) /* Magic number */
+		return;
+
+	acquire(&current->lock);
+	current->eip = eip;
+	current->esp = esp;
+	current->ebp = ebp;
+	release(&current->lock);
+	current = task;
+	eip = current->eip;
+	esp = current->esp;
+	ebp = current->ebp;
+
+	context_switch(eip, current->pageDir, ebp, esp);
+	/* UNREACHED */
+
+	/*
+	 * This code actually returns to the read_eip() call above.  This is due
+	 * to how the call works.  The context switch jumps to the address
+	 * stored in the eip variable.  This address is the return address of
+	 * the read_eip() call.  The context_switch() function sets the return
+	 * value before jumping, so it appears as though read_eip() returned
+	 * that value.  The code uses the magic number 0x10032004 to tell when
+	 * this is occurring and just returns early.
+	 */
+}
+
+/* Find task by ID */
+Task *
+find_task(pid_t tid)
+{
+	Task *task;
+	for (task = tasks.start; task && task->tid != tid; task = task->tnext);
+	return task;
+}
+
+/* Add a task to a task queue */
+void
+add_to_queue(TaskQueue *queue, Task *task)
+{
+	if (!queue->start) {
+		queue->start = task;
+		queue->end = task;
+	} else {
+		queue->end->next = task;
+		queue->end = task;
+	}
+	task->next = NULL;
+}
+
+/* Remove a task from a task queue */
+void
+remove_from_queue(TaskQueue *queue, Task *task)
+{
+	/* Start of list */
+	if (queue->start == task) {
+		queue->start = task->next;
+		if (!queue->start)
+			queue->end = NULL;
+		return;
+	}
+
+	/* Search */
+	Task *prev;
+	for (prev = queue->start; prev->next; prev = prev->next)
+		if (prev->next == task)
+			break;
+	if (prev->next) {
+		prev->next = task->next;
+		if (queue->end == task)
+			queue->end = prev;
+	}
+}
+
+/* Remove the first task from a task queue */
+Task *
+pop_from_queue(TaskQueue *queue)
+{
+	Task *head = queue->start;
+	queue->start = head->next;
+	if (!queue->start)
+		queue->end = NULL;
+	return head;
+}
+
+/* Block a task */
+void
+block_task(int reason)
+{
+	acquire(&current->lock);
+	current->state = reason;
+	release(&current->lock);
+	schedule();
+}
+
+/* Unblock a task */
+void
+unblock_task(Task *task)
+{
+	if (task->state == READY || task->state == RUNNING)
+		return;
+	task->state = READY;
+	if (!readyQueue.start || task->priority > current->priority) {
+		acquire(&readyQueue.lock);
+		task->next = readyQueue.start;
+		readyQueue.start = task;
+		if (!readyQueue.end)
+			readyQueue.end = task;
+		release(&readyQueue.lock);
+	} else {
+		add_to_queue(&readyQueue, task);
+	}
+}
+
+/* Schedule next task */
+void
+schedule(void)
+{
+	Task *task = current;
+
+	/* Next schedulable task */
+	if (readyQueue.start) {
+		acquire(&readyQueue.lock);
+		task = readyQueue.start;
+		readyQueue.start = task->next;
+		if (!readyQueue.start)
+			readyQueue.end = NULL;
+		task->state = RUNNING;
+		task->next = NULL;
+		if (current->state == RUNNING) {
+			current->state = READY;
+			add_to_queue(&readyQueue, current);
+		}
+		release(&readyQueue.lock);
+		switch_to_task(task);
+	/* Idle task */
+	} else if (current->state != RUNNING) {
+		current = NULL;
+		asm volatile("sti");
+		while (!readyQueue.start)
+			asm volatile("hlt");
+		asm volatile("cli");
+		current = task;
+		acquire(&readyQueue.lock);
+		task = readyQueue.start;
+		readyQueue.start = task->next;
+		if (!readyQueue.start)
+			readyQueue.end = NULL;
+		task->state = RUNNING;
+		task->next = NULL;
+		release(&readyQueue.lock);
+		switch_to_task(task);
+	}
+
+	handle_signals();
+}
diff --git a/task/signal.c b/task/signal.c
new file mode 100644
index 0000000..c80c279
--- /dev/null
+++ b/task/signal.c
@@ -0,0 +1,63 @@
+/*
+ * This file handles signals to tasks.  It handles blocking signals, and
+ * registering the signal handlers.  It send/dispatches signals to tasks and
+ * runs the registered signal handlers when appropriate.
+ */
+
+#include <stdint.h>
+#include <errno.h>
+#include "task.h"
+
+extern TaskQueue tasks;
+
+/* Handle the signals for a task */
+void
+handle_signals(void)
+{
+	if (!(current->sigset & ~current->blockedSignals))
+		return;
+
+	int signal;
+	for (signal = 0; signal < 32; signal++) {
+		if (!(current->sigset & (1 << (signal - 1))))
+			continue;
+		current->status = signal;
+		terminate();
+	}
+}
+
+/* Send a signal to a thread */
+int
+tgkill(pid_t tgid, pid_t tid, int sig)
+{
+	if (sig < 0 || sig > 31)
+		return -EINVAL;
+
+	Task *task = find_task(tid);
+	if (!task || task->tgid != tgid)
+		return -ESRCH;
+	if (sig)
+		task->sigset |= (1 << (sig - 1));
+
+	return 0;
+}
+
+/* Send a signal to a process */
+int
+kill(pid_t pid, int sig)
+{
+	if (sig < 0 || sig > 31)
+		return -EINVAL;
+
+	int sent = 0;
+	Task *task;
+	for (task = tasks.start; task; task = task->tnext) {
+		if (task->tgid != pid)
+			continue;
+		if (sig)
+			task->sigset |= (1 << (sig - 1));
+		sent++;
+	}
+
+	return sent ? 0 : -ESRCH;
+}
diff --git a/task/syscall.c b/task/syscall.c
new file mode 100644
index 0000000..d43542d
--- /dev/null
+++ b/task/syscall.c
@@ -0,0 +1,96 @@
+/*
+ * This file handles system calls.  Every system call gets passed to the
+ * syscall_handler() function, which decides which Kernel function to run.
+ * There is an array of syscalls, which are indexed numerically, these are the
+ * syscall numbers.
+ */
+
+#include <sys/syscall.h>
+#include <sys/mman.h>
+#include <errno.h>
+#include "task.h"
+#include "../screen.h"
+
+/* List of syscalls */
+void *syscalls[] = {
+	/* Tasking */
+	[SYSCALL_DBGPRINTF] = dbgprintf,
+	[SYSCALL_CLONE]     = clone,
+	[SYSCALL_EXIT]      = exit,
+	[SYSCALL_GETPID]    = getpid,
+	[SYSCALL_GETUID]    = getuid,
+	[SYSCALL_SETUID]    = setuid,
+	[SYSCALL_GETEUID]   = geteuid,
+	[SYSCALL_SETEUID]   = seteuid,
+	[SYSCALL_GETGID]    = getgid,
+	[SYSCALL_SETGID]    = setgid,
+	[SYSCALL_GETEGID]   = getegid,
+	[SYSCALL_SETEGID]   = setegid,
+	[SYSCALL_EXECVE]    = execve,
+	[SYSCALL_WAITPID]   = waitpid,
+	[SYSCALL_TGKILL]    = tgkill,
+	[SYSCALL_KILL]      = kill,
+	[SYSCALL_TIME]      = time,
+	[SYSCALL_TIMES]     = times,
+	[SYSCALL_SLEEP]     = sleep,
+
+	/* Files */
+	[SYSCALL_OPEN]     = open,
+	[SYSCALL_CLOSE]    = close,
+	[SYSCALL_READ]     = read,
+	[SYSCALL_WRITE]    = write,
+	[SYSCALL_IOCTL]    = ioctl,
+	[SYSCALL_LSEEK]    = lseek,
+	[SYSCALL_STAT]     = stat,
+	[SYSCALL_GETDENTS] = getdents,
+	[SYSCALL_MKDIR]    = mkdir,
+	[SYSCALL_RMDIR]    = rmdir,
+	[SYSCALL_MKNOD]    = mknod,
+	[SYSCALL_RENAME]   = rename,
+	[SYSCALL_DUP]      = dup,
+	[SYSCALL_DUP2]     = dup2,
+	[SYSCALL_ISATTY]   = isatty,
+
+	/* File System */
+	[SYSCALL_MOUNT]  = mount,
+	[SYSCALL_CHDIR]  = chdir,
+	[SYSCALL_CHROOT] = chroot,
+	[SYSCALL_GETCWD] = getcwd,
+
+	/* Memory */
+	[SYSCALL_MMAP] = mmap,
+
+	/* Messaging */
+	[SYSCALL_NB_SEND_MSG] = nb_send_msg,
+	[SYSCALL_SEND_MSG]    = send_msg,
+	[SYSCALL_NB_RECV_MSG] = nb_recv_msg,
+	[SYSCALL_RECV_MSG]    = recv_msg,
+};
+
+/* Handle a syscall */
+void
+syscall_handler(InterruptFrame *frame)
+{
+	if (frame->eax >= sizeof(syscalls)/sizeof(syscalls[0])) {
+		frame->eax = -EINVAL;
+		return;
+	}
+	void *handler = syscalls[frame->eax];
+	int ret;
+	current->inSyscall = 1;
+
+	size_t num;
+	int *val = (int *) frame->esi;
+	if (!verify_access(val, frame->ecx * sizeof(int), PROT_READ)) {
+		frame->eax = -EINVAL;
+		return;
+	}
+	/* Push args onto stack */
+	for (num = frame->ecx; num; num--)
+		asm volatile("pushl %0" :: "r" (val[num - 1]));
+	/* Call handler */
+	asm volatile("call *%1" : "=a" (ret) : "r" (handler));
+
+	frame->eax = ret;
+	current->inSyscall = 0;
+}
diff --git a/task/task.c b/task/task.c
new file mode 100644
index 0000000..294b3cc
--- /dev/null
+++ b/task/task.c
@@ -0,0 +1,420 @@
+/*
+ * This file is responsible for the core functions related to multitasking.  It
+ * relies on a decent paging implementation.  It contains several routines which
+ * are related to the creation, modification and deletion of tasks.
+ */
+
+#include <stdint.h>
+#include <sys/sched.h>
+#include <errno.h>
+#include "task.h"
+#include "../vfs/vfs.h"
+#include "../vfs/inode.h"
+#include "../vfs/cache.h"
+#include "../vfs/tmpfs/fs.h"
+#include "../mem/mem.h"
+#include "../mem/heap.h"
+#include "../mem/paging.h"
+#include "../proc/proc.h"
+#include "../screen.h"
+
+extern TaskQueue readyQueue, tasks;
+pid_t nextPid = 1;
+
+extern uint32_t initialStack;
+extern page_dir_t kernelDir;
+
+uintptr_t read_eip(void);
+
+/* Move the initial stack to a new position */
+static void
+move_stack(void *start, size_t size)
+{
+	uintptr_t i, tmp, *tmpp;
+	uintptr_t oldStackPointer, oldBasePointer, offset;
+	uintptr_t newStackPointer, newBasePointer;
+
+	for (i = (size_t) start - size; i <= (size_t) start; i += 0x1000) {
+		page_t *pg = get_page((void *) i);
+		alloc_page(pg, PTE_PRESENT | PTE_WRITE | PTE_USER, -1);
+	}
+
+	asm volatile("mov %%esp, %0" : "=r" (oldStackPointer));
+	asm volatile("mov %%ebp, %0" : "=r" (oldBasePointer));
+	offset = (uint32_t) start - initialStack;
+	newStackPointer = oldStackPointer + offset;
+	newBasePointer = oldBasePointer + offset;
+
+	memcpy((void *) newStackPointer, (void *) oldStackPointer,
+	       initialStack - oldStackPointer);
+
+	/* Update pointers on stack */
+	for (i = (size_t) start; i > (size_t) start - size; i -= 4) {
+		tmp = *(uint32_t *) i;
+		if ((tmp > oldStackPointer) && (tmp < initialStack)) {
+			tmp += offset;
+			tmpp = (uintptr_t *) i;
+			*tmpp = tmp;
+		}
+	}
+
+	asm volatile("mov %0, %%esp" :: "r" (newStackPointer));
+	asm volatile("mov %0, %%ebp" :: "r" (newBasePointer));
+}
+
+/* Fork a task */
+pid_t
+fork(void)
+{
+	return clone(CLONE_NONE);
+}
+
+/* Clone a task */
+pid_t
+clone(int flags)
+{
+	Task *parent = current, *child = kmalloc(sizeof(Task)), *tmp;
+
+	if (flags & CLONE_THREAD) {
+		flags |= CLONE_PARENT;
+		flags |= CLONE_VM;
+	}
+
+	child->tid = nextPid++;
+	if (flags & CLONE_THREAD)
+		child->tgid = parent->tgid;
+	else
+		child->tgid = child->tid;
+	child->priority = NORMAL;
+	child->state = READY;
+	child->status = 0;
+	child->inSyscall = parent->inSyscall;
+	child->name = kmalloc(strlen(parent->name)+1);
+	memcpy(child->name, parent->name, strlen(parent->name)+1);
+
+	/* Set parent */
+	child->parent = parent;
+	if (flags & CLONE_PARENT)
+		child->parent = parent->parent;
+	child->ppid = child->parent->tgid;
+
+	child->executable = file_get(parent->executable);
+
+	/* Add to list of tasks */
+	tasks.end->tnext = child;
+	tasks.end = child;
+
+	/* Clone parent's file descriptors */
+	int fd;
+	File *file;
+	if (flags & CLONE_FILES) {
+		child->files = parent->files;
+		child->files->usage++;
+	} else {
+		child->files = kmalloc(sizeof(Files));
+		child->files->usage = 1;
+		for (fd = 0; fd < NFILES; fd++) {
+			file = parent->files->fd[fd];
+			if (!file) continue;
+			child->files->fd[fd] = file_get(file);
+		}
+	}
+
+	/* Clone parent's file system context */
+	if (flags & CLONE_FS) {
+		child->fs = parent->fs;
+		child->fs->usage++;
+	} else {
+		child->fs = kmalloc(sizeof(FileSystem));
+		child->fs->usage = 1;
+		child->fs->cwd = parent->fs->cwd;
+		child->fs->cwd->usage++;
+		init_custody_chain(&child->fs->cwdCustody);
+		copy_custody_chain(&parent->fs->cwdCustody,
+		                   &child->fs->cwdCustody);
+		child->fs->root = parent->fs->root;
+		child->fs->root->usage++;
+	}
+
+	/* Clone page directory */
+	if (flags & CLONE_VM) {
+		child->vm = parent->vm;
+		child->vm->usage++;
+	} else {
+		child->vm = kmalloc(sizeof(VirtualMemory));
+		child->vm->usage = 1;
+	}
+	child->pageDir = clone_dir();
+
+	/* Clone parent's VM Regions in child */
+	VMRegion *head;
+	Page *page;
+	off_t i;
+	if (child->vm != parent->vm)
+		child->vm->regions = vm_clone_regions(parent->vm->regions);
+	child->stack = kmalloc(sizeof(VMRegion));
+	memcpy(child->stack, parent->stack, sizeof(VMRegion));
+	child->stack->next = child->stack->prev = NULL;
+	/* Copy stack */
+	if (parent->stack && parent->stack->front) {
+		file = kmalloc(sizeof(File));
+		file->inode = inode_get(kmalloc(sizeof(Inode)));
+		file->ops = &tmpfsFileOps;
+		child->stack->front = file_get(file);
+		for (i = 0; i < child->stack->end - child->stack->start;
+		     i += 0x1000) {
+			page = page_find(parent->stack->front->inode, i);
+			if (page)
+				page_add(file->inode, page);
+		}
+	}
+	/* Copy thread local storage */
+	if (parent->tls) {
+		child->tls = kmalloc(sizeof(VMRegion));
+		memcpy(child->tls, parent->tls, sizeof(VMRegion));
+		child->tls->next = child->tls->prev = NULL;
+		if (parent->tls->front) {
+			file = kmalloc(sizeof(File));
+			file->inode = inode_get(kmalloc(sizeof(Inode)));
+			file->ops = &tmpfsFileOps;
+			child->tls->front = file_get(file);
+			for (i = 0; i < child->tls->end - child->tls->start;
+			     i += 0x1000) {
+				page = page_find(parent->tls->front->inode, i);
+				if (page)
+					page_add(file->inode, page);
+			}
+		}
+		if (parent->tls->back)
+			child->tls->back = file_get(parent->tls->back);
+	}
+
+	/* Split tasks here */
+	uintptr_t esp, ebp, eip;
+	eip = read_eip();
+	if (current == parent) {
+		asm volatile("mov %%esp, %0" : "=r" (esp));
+		asm volatile("mov %%ebp, %0" : "=r" (ebp));
+		child->esp = esp;
+		child->ebp = ebp;
+		child->eip = eip;
+		add_to_queue(&readyQueue, child);
+		return child->tid;
+	}
+	return 0;
+}
+
+/* Terminate the current task */
+void
+terminate(void)
+{
+	/* Close files */
+	int fd;
+	if (--current->files->usage == 0) {
+		for (fd = 0; fd < NFILES; fd++)
+			if (current->files->fd[fd])
+				close(fd);
+		kfree(current->files);
+	}
+	if (current->executable)
+		file_put(current->executable);
+
+	/* Clean File System info */
+	if (--current->fs->usage == 0)
+		kfree(current->fs);
+
+	/* Clean VM Regions and unreferenced VM Objects */
+	VMRegion *head;
+	if (--current->vm->usage == 0) {
+		for (head = current->vm->regions; head; head = head->next)
+			vm_destroy_region(head);
+		kfree(current->vm);
+	}
+	vm_destroy_region(current->stack);
+
+	/* Clean unread IPC messages */
+	/* TODO */
+
+	/* Clean signals */
+	/* TODO */
+
+	/* Deschedule */
+	current->state = TERMINATED;
+	acquire(&readyQueue.lock);
+	Task *tmp, *next;
+	for (tmp = current->waiting.start; tmp; tmp = next) {
+		next = tmp->next;
+		add_to_queue(&readyQueue, tmp);
+	}
+	release(&readyQueue.lock);
+	schedule();
+	panic("Unreached");
+
+	/* Clean task - FIXME */
+	kfree(current->name);
+	kfree(current);
+	clean_dir();
+	schedule();
+	/* UNREACHED */
+}
+
+/* Exit the current task */
+void
+exit(int status)
+{
+	if (current->tid == 1)
+		panic("Attempted to exit init! Exit code %d", status);
+	current->status = (1 << 31) | (status & 0x0F);
+	terminate();
+}
+
+/* Wait for a child process to change state */
+pid_t
+waitpid(pid_t pid, int *wstatus, int options)
+{
+	if (!verify_access(wstatus, sizeof(int), PROT_WRITE))
+		return -EFAULT;
+
+	Task *task = find_task(pid);
+	if (!task)
+		return -ECHILD;
+	if (task->ppid != current->tgid && task->tgid != current->tgid)
+		return -ECHILD;
+
+	if (task->state != TERMINATED) {
+		add_to_queue(&task->waiting, current);
+		block_task(WAITING_FOR_CHILD);
+	}
+
+	if (wstatus)
+		*wstatus = task->status;
+	return task->tid;
+}
+
+/* Get current task's PID */
+pid_t
+getpid(void)
+{
+	return current->tgid;
+}
+
+/* Get current task's UID */
+uid_t
+getuid(void)
+{
+	return current->uid;
+}
+
+/* Set current task's (E)UID */
+int
+setuid(uid_t uid)
+{
+	if (uid != current->uid && uid != current->suid && !super_user())
+		return -EPERM;
+	if (super_user()) {
+		current->uid = uid;
+		current->suid = uid;
+	}
+	current->euid = uid;
+	return 0;
+}
+
+/* Get current task's EUID */
+uid_t
+geteuid(void)
+{
+	return current->euid;
+}
+
+/* Set the current task's EUID */
+int
+seteuid(uid_t euid)
+{
+	if (euid != current->uid
+	 && euid != current->euid
+	 && euid != current->suid
+	 && !super_user())
+		return -EPERM;
+	current->euid = euid;
+	return 0;
+}
+
+/* Get current task's GID */
+gid_t
+getgid(void)
+{
+	return current->gid;
+}
+
+/* Set current task's (E)GID */
+int
+setgid(gid_t gid)
+{
+	if (gid != current->gid
+	 && gid != current->sgid
+	 && !super_user())
+		return -EPERM;
+	if (super_user()) {
+		current->gid = gid;
+		current->sgid = gid;
+	}
+	current->egid = gid;
+	return 0;
+}
+
+/* Get current task's EGID */
+gid_t
+getegid(void)
+{
+	return current->egid;
+}
+
+/* Set the current task's EUID */
+int
+setegid(gid_t egid)
+{
+	if (egid != current->gid
+	 && egid != current->egid
+	 && egid != current->sgid
+	 && !super_user())
+		return -EPERM;
+	current->egid = egid;
+	return 0;
+}
+
+/* Initialse tasking */
+void
+init_tasking(void)
+{
+	move_stack((void *) (0xF0800000 - sizeof(uintptr_t)), 0x2000);
+
+	/* Initialise the Kernel Task */
+	tasks.start = tasks.end = current = kmalloc(sizeof(Task));
+	current->tid = nextPid++;
+	current->tgid = current->tid;
+	current->priority = NORMAL;
+	current->name = kmalloc(7);
+	current->state = RUNNING;
+	memcpy(current->name, "kernel", 7);
+	current->pageDir = kernelDir;
+
+	/* Files Namespace */
+	current->files = kmalloc(sizeof(Files));
+	current->files->usage = 1;
+
+	/* File System Namespace */
+	current->fs = kmalloc(sizeof(FileSystem));
+	init_custody_chain(&current->fs->cwdCustody);
+	current->fs->usage = 1;
+
+	/* Virtual Memory Namespace */
+	current->vm = kmalloc(sizeof(Files));
+	current->vm->regions = NULL;
+	current->vm->usage = 1;
+
+	/* Inter-Process Communication Namespace */
+
+	/* Signals Namespace */
+
+	register_interrupt(0, timer_handler);
+}
diff --git a/task/task.h b/task/task.h
new file mode 100644
index 0000000..228c3d8
--- /dev/null
+++ b/task/task.h
@@ -0,0 +1,128 @@
+#ifndef KERNEL_TASK_H
+#define KERNEL_TASK_H
+
+#include <stdint.h>
+#include <sys/ipc.h>
+#include <sys/times.h>
+#include <time.h>
+#include <signal.h>
+#include "../mem/paging.h"
+#include "../mem/vm.h"
+#include "../proc/proc.h"
+#include "../vfs/vfs.h"
+#include "../spinlock.h"
+
+typedef struct Task Task;
+typedef struct TaskQueue TaskQueue;
+
+/* Process priorities */
+enum Priority {
+	NONE,
+	LOWEST,
+	LOW,
+	NORMAL,
+	HIGH,
+	HIGHEST,
+};
+
+/* Task states */
+enum States {
+	RUNNING,
+	READY,
+	TERMINATED,
+	WAITING_FOR_CHILD,
+	WAITING_FOR_READ,
+	SLEEP,
+};
+
+/* Structure for a Task Queue */
+struct TaskQueue {
+	Task *start, *end;
+	Spinlock lock;
+};
+
+/* Structure of a Task */
+struct Task {
+	pid_t tid, tgid;
+	uid_t uid, euid, suid;
+	gid_t gid, egid, sgid;
+	uint8_t priority;
+	char *name;
+	uint32_t usertime, systime;
+	int state;
+	uint64_t sleepExpiry;
+	int status;
+	uintptr_t esp, ebp, eip;
+	Task *next, *tnext, *parent;
+	pid_t ppid;
+	page_dir_t pageDir;
+	Spinlock lock;
+	uint8_t inSyscall;
+	File *executable;
+	VMRegion *stack, *tls;
+	TaskQueue waiting;
+	sigset_t sigset;
+	sigset_t blockedSignals;
+	void (*sig_handler[32])(int);
+
+	/* Messages */
+	Message *msgQueue;
+
+	/* Namespaces */
+	FileSystem *fs;
+	Files *files;
+	VirtualMemory *vm;
+//	Messages *ipc;
+//	Signals *signals;
+};
+
+extern Task *currentTask[];
+#define current currentTask[CPUID]
+
+/* Check if a routine is running as a syscall */
+static inline uint8_t
+in_syscall(void)
+{
+	if (!current)
+		return 0;
+	return (current->inSyscall);
+}
+
+/* Check if super-user */
+static inline int
+super_user(void)
+{
+	return (current->euid == 0);
+}
+
+void init_tasking(void);
+void syscall_handler(InterruptFrame *frame);
+Task *find_task(pid_t tid);
+void add_to_queue(TaskQueue *queue, Task *task);
+void remove_from_queue(TaskQueue *queue, Task *task);
+Task *pop_from_queue(TaskQueue *queue);
+void timer_handler(InterruptFrame *frame);
+void block_task(int reason);
+void unblock_task(Task *task);
+void schedule(void);
+pid_t fork(void);
+pid_t clone(int flags);
+void terminate(void);
+void exit(int status);
+pid_t waitpid(pid_t pid, int *wstatus, int options);
+pid_t getpid(void);
+uid_t getuid(void);
+int setuid(uid_t uid);
+uid_t geteuid(void);
+int seteuid(uid_t euid);
+gid_t getgid(void);
+int setgid(gid_t gid);
+gid_t getegid(void);
+int setegid(gid_t egid);
+int isatty(int fd);
+int execve(const char *file, char *argv[], char *envp[]);
+
+int tgkill(pid_t tgid, pid_t tid, int sig);
+int kill(pid_t pid, int sig);
+
+#endif
diff --git a/task/time.c b/task/time.c
new file mode 100644
index 0000000..f81d496
--- /dev/null
+++ b/task/time.c
@@ -0,0 +1,183 @@
+/*
+ * This file controls the system clock and contains the functions related to
+ * getting and setting the time from various sources.  It keeps a monotonic
+ * clock internally, but can also make use of the tasks' clocks, and the RTC.
+ */
+
+#include <stdint.h>
+#include <sys/times.h>
+#include <errno.h>
+#include "task.h"
+#include "../proc/proc.h"
+#include "../io.h"
+
+#define RTC_SECONDS 0x00
+#define RTC_MINUTES 0x02
+#define RTC_HOURS   0x04
+#define RTC_WEEKDAY 0x06
+#define RTC_DAY     0x07
+#define RTC_MONTH   0x08
+#define RTC_YEAR    0x09
+#define RTC_CENTURY 0x32
+
+#define LEAP_YEAR(x) ((((x % 4) == 0) && ((x % 100) != 0)) || ((x % 400) == 0))
+
+extern TaskQueue readyQueue;
+
+uint32_t monotonicClock = 0, millis = 0;
+uint8_t slice[MAX_CPUS] = {0};
+TaskQueue sleepQueue;
+
+/* Read an RTC register */
+static uint8_t
+read_rtc(uint8_t index)
+{
+	outb(0x70, 0x80 | index);
+	io_wait();
+	return inb(0x71);
+}
+
+/* Timer interrupt */
+void
+timer_handler(InterruptFrame *frame)
+{
+	/* Monotonic clock */
+	millis++;
+	if (millis == 1000) {
+		monotonicClock++;
+		millis = 0;
+	}
+
+	/* Sleeping processes */
+	Task *proc = sleepQueue.start, *prev = NULL;
+	for (proc = sleepQueue.start; proc; prev = proc, proc = proc->next) {
+		if (proc->sleepExpiry > (monotonicClock * 1000) + millis)
+			break;
+		if (prev)
+			prev->next = proc->next;
+		if (sleepQueue.start == proc)
+			sleepQueue.start = proc->next;
+		if (sleepQueue.end == proc)
+			sleepQueue.end = NULL;
+		add_to_queue(&readyQueue, proc);
+	}
+
+	/* Account timeslice */
+	slice[CPUID]++;
+	if (!current)
+		return;
+
+	/* Account task time */
+	if (in_syscall())
+		current->systime++;
+	else
+		current->usertime++;
+
+	/* Call scheduler */
+	if (slice[CPUID] < current->priority)
+		return;
+	slice[CPUID] = 0;
+	schedule();
+}
+
+/* Sleep for a specified time (milliseconds) */
+int
+sleep(uint32_t ms)
+{
+	current->sleepExpiry = (monotonicClock * 1000) + millis + ms;
+
+	/* Add to sorted sleep TaskQueue */
+	TaskQueue *q = &sleepQueue;
+	Task *task, *prev = NULL;
+	acquire(&q->lock);
+	if (!q->start) {
+		q->start = current;
+		q->end = current;
+		current->next = NULL;
+	} else {
+		for (task = q->start; task; prev = task, task = task->next)
+			if (task->sleepExpiry > current->sleepExpiry)
+				break;
+		if (!prev) {
+			current->next = q->start;
+			q->start = current;
+		} else {
+			current->next = task;
+			prev->next = current;
+		}
+	}
+	release(&q->lock);
+
+	block_task(SLEEP);
+	return 0;
+}
+
+/* Get epoch time */
+time_t
+time(time_t *tloc)
+{
+	if (!verify_access(tloc, sizeof(time_t), PROT_WRITE))
+		return -EFAULT;
+
+	/* Length of months for normal and leap years */
+	const uint16_t monthLen[2][12] = {
+		{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
+		{31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
+	};
+
+	uint16_t i;
+	uint8_t second, minute, hour, day, month, year, cent;
+	time_t time = 0;
+	uint8_t years = 0, leapYears = 0;
+
+	second = read_rtc(RTC_SECONDS);
+	minute = read_rtc(RTC_MINUTES);
+	hour   = read_rtc(RTC_HOURS);
+	day    = read_rtc(RTC_DAY);
+	month  = read_rtc(RTC_MONTH);
+	year   = read_rtc(RTC_YEAR);
+	cent   = read_rtc(RTC_CENTURY);
+
+	second = (second & 0x0F) + ((second        / 16) * 10);
+	minute = (minute & 0x0F) + ((minute        / 16) * 10);
+	hour   = ((hour  & 0x0F) + (((hour & 0x70) / 16) * 10)) | (hour & 0x80);
+	day    = (day    & 0x0F) + ((day           / 16) * 10);
+	month  = (month  & 0x0F) + ((month         / 16) * 10);
+	year   = (year   & 0x0F) + ((year          / 16) * 10);
+	cent   = (cent   & 0x0F) + ((cent          / 16) * 10);
+
+	for (i = 1970; i < (cent * 100) + year; i++)
+		if (LEAP_YEAR(i))
+			leapYears++;
+		else
+			years++;
+	time += ((years * 365) + (leapYears * 366)) * 86400;
+
+	for (i = 0; i < month - 1; i++)
+		time += monthLen[LEAP_YEAR(year)][i] * 86400;
+
+	time += (day - 1) * 86400;
+	time += hour * 3600;
+	time += minute * 60;
+	time += second;
+
+	if (tloc)
+		*tloc = time;
+
+	return time;
+}
+
+/* Get process times */
+clock_t
+times(Times *buf)
+{
+	if (!verify_access(buf, sizeof(Times), PROT_WRITE))
+		return -EFAULT;
+	if (buf) {
+		buf->utime = current->usertime;
+		buf->stime = current->systime;
+		buf->cutime = current->usertime;
+		buf->cstime = current->systime;
+	}
+	return (monotonicClock * 1000) + millis;
+}
diff --git a/vfs/cache.c b/vfs/cache.c
new file mode 100644
index 0000000..54a7495
--- /dev/null
+++ b/vfs/cache.c
@@ -0,0 +1,307 @@
+/*
+ * This file handles the VFS cache.  This includes the VFS tree cache and all
+ * the functions for managing the associated structures.  One such structure
+ * is the Inode's list of Directory Entries.  Every DirEntry has a name, but
+ * also has a hash.  This is for fast comparisons - when iterating the list to
+ * search for a name, first check if the hash matches.  This saves a lot of time
+ * rather than actually comparing the names.  Entries with hash collisions are
+ * placed adjacent in the list.
+ */
+
+#include <stdint.h>
+#include <string.h>
+#include "vfs.h"
+#include "inode.h"
+#include "cache.h"
+#include "../mem/heap.h"
+#include "../mem/paging.h"
+#include "../mem/frame.h"
+#include "../task/task.h"
+
+/* Hash a file name */
+static uint32_t
+name_hash(const char *name)
+{
+	uint32_t digest = 5381;
+	int c;
+	while (c = *name++)
+		digest = ((digest << 5) + digest) ^ c; /* digest*33 ^ c */
+	return digest;
+}
+
+/* Reap any unused branches of the cache tree */
+void
+cache_reaper(void)
+{
+	/*
+	 * The Cache Reaper attempts to release all the directory entries
+	 * belonging to the root of the current task's file system context.
+	 * This will in theory release all the associated inodes.  If those
+	 * inodes are only held in the cache (not in use, and therefore not
+	 * referenced by anything else), they'll be freed and will in turn have
+	 * their directory entries released.  This recursive process will prune
+	 * most of the VFS cache, leaving only in-use inodes.
+	 */
+	if (!current)
+		return;
+	if (!current->fs)
+		return;
+	if (!current->fs->root)
+		return;
+	entry_clean(current->fs->root);
+	current->fs->root->dirEntries = NULL;
+}
+
+/* Get a DirEntry */
+DirEntry *
+entry_get(DirEntry *entry)
+{
+	entry->usage++;
+	return entry;
+}
+
+/* Put a DirEntry */
+void
+entry_put(DirEntry *entry)
+{
+	if (--entry->usage)
+		return;
+	if (entry->inode)
+		inode_put(entry->inode);
+	kfree(entry);
+}
+
+/* Search for a Directory Entry in a list by name */
+DirEntry *
+entry_find(Inode *inode, const char *name)
+{
+	if (!inode)
+		return NULL;
+	acquire(&inode->lock);
+	uint32_t search = name_hash(name);
+	DirEntry *item = inode->dirEntries;
+	/* Look for matching hash */
+	while (item) {
+		if (item->hash == search)
+			break;
+		item = item->next;
+	}
+	if (!item) {
+		release(&inode->lock);
+		return NULL;
+	}
+	/* Check all collisions */
+	while (item && item->hash == search) {
+		if (!strcmp(item->name, name))
+			break;
+		item = item->next;
+	}
+	release(&inode->lock);
+	if (!item || item->hash != search)
+		return NULL;
+	return item;
+}
+
+/* Add a Directory Entry to a list */
+void
+entry_add(Inode *inode, DirEntry *insert)
+{
+	if (!inode)
+		return;
+	acquire(&inode->lock);
+	insert->hash = name_hash(insert->name);
+	if (!inode->dirEntries) {
+		inode->dirEntries = entry_get(insert);
+		release(&inode->lock);
+		return;
+	}
+
+	/* Try to find a collision */
+	DirEntry *collision = inode->dirEntries;
+	while (collision->next) {
+		if (collision->hash == insert->hash)
+			break;
+		collision = collision->next;
+	}
+	/* Add into list */
+	insert->next = collision->next;
+	collision->next = entry_get(insert);
+	release(&inode->lock);
+}
+
+/* Remove a Directory Entry from a list */
+void
+entry_remove(Inode *inode, const char *name)
+{
+	if (!inode)
+		return;
+	acquire(&inode->lock);
+	uint32_t search = name_hash(name);
+	DirEntry *item = inode->dirEntries, *prev = NULL;
+	/* Look for matching hash */
+	while (item) {
+		if (item->hash == search)
+			break;
+		prev = item;
+		item = item->next;
+	}
+	if (!item) {
+		release(&inode->lock);
+		return;
+	}
+	/* Check all collisions */
+	while (item && item->hash == search) {
+		if (!strcmp(item->name, name))
+			break;
+		prev = item;
+		item = item->next;
+	}
+	if (!item || item->hash != search) {
+		release(&inode->lock);
+		return;
+	}
+
+	/* Link over item */
+	if (prev)
+		prev->next = item->next;
+	else
+		inode->dirEntries = item;
+	entry_put(item);
+	release(&inode->lock);
+}
+
+/* Clean a list of Directory Entries */
+void
+entry_clean(Inode *inode)
+{
+	if (!inode)
+		return;
+	if (!inode->dirEntries)
+		return;
+	acquire(&inode->lock);
+	DirEntry *de, *den;
+	for (de = inode->dirEntries; de; de = den) {
+		den = de->next;
+		entry_put(de);
+	}
+	release(&inode->lock);
+}
+
+/* Get a Page */
+Page *
+page_get(Page *page)
+{
+	page->usage++;
+	return page;
+}
+
+/* Put a Page */
+void
+page_put(Page *page)
+{
+	if (--page->usage)
+		return;
+	free_frame(PG_ADDR(page->frame));
+	kfree(page);
+}
+
+/* Find a Page by offset */
+Page *
+page_find(Inode *inode, off_t offset)
+{
+	if (!inode)
+		return NULL;
+	acquire(&inode->lock);
+	PageCache *cache;
+	for (cache = inode->pages; cache; cache = cache->next)
+		if (cache->page->offset == PG_ADDR(offset))
+			break;
+	release(&inode->lock);
+	if (!cache)
+		return NULL;
+	return cache->page;
+}
+
+/* Add a page to a list */
+void
+page_add(Inode *inode, Page *page)
+{
+	if (!inode)
+		return;
+	acquire(&inode->lock);
+	PageCache *cache;
+	if (!inode->pages) {
+		inode->pages = kmalloc(sizeof(PageCache));
+		cache = inode->pages;
+	} else {
+		for (cache = inode->pages; cache->next; cache = cache->next);
+		cache->next = kmalloc(sizeof(PageCache));
+		cache = cache->next;
+	}
+	cache->page = page_get(page);
+	release(&inode->lock);
+}
+
+/* Create and add a Page to a list */
+Page *
+page_create(Inode *inode, page_t frame, off_t offset)
+{
+	if (!inode)
+		return NULL;
+	acquire(&inode->lock);
+	Page *prev, *page = kmalloc(sizeof(Page));
+	page->frame = PG_ADDR(frame);
+	page->offset = PG_ADDR(offset);
+	page_add(inode, page);
+	release(&inode->lock);
+	return page;
+}
+
+/* Remove a Page from a list */
+void
+page_remove(Inode *inode, Page *page)
+{
+	if (!inode)
+		return;
+	if (!inode->pages)
+		return;
+	acquire(&inode->lock);
+
+	PageCache *cache, *item;
+	if (inode->pages->page == page) {
+		item = inode->pages;
+		inode->pages = item->next;
+	} else {
+		for (cache = inode->pages;
+		     cache->next; cache = cache->next)
+			if (cache->next->page == page)
+				break;
+		if (!cache->next) {
+			release(&inode->lock);
+			return;
+		}
+		item = cache->next;
+		cache->next = item->next;
+	}
+	page_put(page);
+	kfree(item);
+	release(&inode->lock);
+}
+
+/* Clean a list of Pages */
+void
+page_clean(Inode *inode)
+{
+	if (!inode)
+		return;
+	if (!inode->pages)
+		return;
+	acquire(&inode->lock);
+	PageCache *c, *cn;
+	for (c = inode->pages; c; c = cn) {
+		page_put(c->page);
+		cn = c->next;
+		kfree(c);
+	}
+	release(&inode->lock);
+}
diff --git a/vfs/cache.h b/vfs/cache.h
new file mode 100644
index 0000000..158d2ab
--- /dev/null
+++ b/vfs/cache.h
@@ -0,0 +1,22 @@
+#ifndef KERNEL_VFS_CACHE_H
+#define KERNEL_VFS_CACHE_H
+
+#include "vfs.h"
+
+void cache_reaper(void);
+
+DirEntry *entry_get(DirEntry *entry);
+void entry_put(DirEntry *entry);
+DirEntry *entry_find(Inode *inode, const char *name);
+void entry_add(Inode *inode, DirEntry *insert);
+void entry_remove(Inode *inode, const char *name);
+void entry_clean(Inode *inode);
+
+Page *page_get(Page *page);
+Page *page_find(Inode *inode, off_t offset);
+void page_add(Inode *inode, Page *page);
+Page *page_create(Inode *inode, page_t frame, off_t offset);
+void page_remove(Inode *inode, Page *page);
+void page_clean(Inode *inode);
+
+#endif
diff --git a/vfs/custody.c b/vfs/custody.c
new file mode 100644
index 0000000..1200f59
--- /dev/null
+++ b/vfs/custody.c
@@ -0,0 +1,117 @@
+/*
+ * This file handles chains of custody for files.  It implements a generic set
+ * of operations for acting on CustodyChain objects.  A CustodyChain holds a
+ * linked list of Custody objects, each of which refers to a directory entry.
+ * With this chain, the VFS is able to find what path was used to access a
+ * resources and go back up the chain (for implementing ..) and avoid going
+ * beyond the root.
+ */
+
+#include <string.h>
+#include "vfs.h"
+#include "cache.h"
+#include "../mem/heap.h"
+
+/* Initialise a custody chain */
+void
+init_custody_chain(CustodyChain *chain)
+{
+	Custody *root = kmalloc(sizeof(Custody));
+	chain->start = chain->end = root;
+	chain->size = 0;
+	init_lock(&chain->lock);
+	root->prev = root->next = NULL;
+	root->chain = chain;
+	root->entry = NULL;
+}
+
+/* Create a new custody chain */
+CustodyChain *
+create_custody_chain(void)
+{
+	CustodyChain *chain = kmalloc(sizeof(CustodyChain));
+	init_custody_chain(chain);
+	return chain;
+}
+
+/* Clean a custody chain */
+void
+clean_custody_chain(CustodyChain *chain)
+{
+	acquire(&chain->lock);
+	while (chain->size)
+		remove_custody(chain);
+	kfree(chain->start);
+	release(&chain->lock);
+}
+
+/* Destroy a custody chain */
+void
+destroy_custody_chain(CustodyChain *chain)
+{
+	clean_custody_chain(chain);
+	kfree(chain);
+}
+
+/* Copy custody chain */
+void
+copy_custody_chain(CustodyChain *chain, CustodyChain *newchain)
+{
+	acquire(&chain->lock);
+	acquire(&newchain->lock);
+	Custody *c;
+	for (c = chain->start->next; c; c = c->next)
+		add_custody(newchain, c->entry);
+	release(&newchain->lock);
+	release(&chain->lock);
+}
+
+/* Add entry */
+void
+add_custody(CustodyChain *chain, DirEntry *entry)
+{
+	acquire(&chain->lock);
+	chain->end->next = kmalloc(sizeof(Custody));
+	chain->end->next->prev= chain->end;
+	chain->end = chain->end->next;
+	chain->end->chain = chain;
+	chain->end->entry = entry_get(entry);
+	chain->size++;
+	release(&chain->lock);
+}
+
+/* Remove an entry */
+DirEntry *
+remove_custody(CustodyChain *chain)
+{
+	if (!chain->size)
+		return NULL;
+	acquire(&chain->lock);
+	entry_put(chain->end->entry);
+	chain->end = chain->end->prev;
+	kfree(chain->end->next);
+	chain->end->next = NULL;
+	chain->size--;
+	release(&chain->lock);
+	return chain->end->entry;
+}
+
+/* Convert a chain to a path string */
+char *
+custody_path(CustodyChain *chain, char *buf, size_t size)
+{
+	char *ptr = buf;
+	Custody *curr;
+	if (!chain->start->next) {
+		*ptr++ = '/';
+		*ptr = '\0';
+		return buf;
+	}
+	for (curr = chain->start->next; curr; curr = curr->next) {
+		*ptr++ = '/';
+		strcpy(ptr, curr->entry->name);
+		ptr += strlen(curr->entry->name);
+	}
+	*ptr = '\0';
+	return buf;
+}
diff --git a/vfs/devfs/file.c b/vfs/devfs/file.c
new file mode 100644
index 0000000..d2da4e4
--- /dev/null
+++ b/vfs/devfs/file.c
@@ -0,0 +1,84 @@
+/*
+ * This file controls access to DevFS Files.  It contains the functions called
+ * by the VFS for any operation on a File struct belonging to DevFS.
+ */
+
+#include <stddef.h>
+#include <string.h>
+#include <errno.h>
+#include <dirent.h>
+#include "fs.h"
+#include "../vfs.h"
+#include "../cache.h"
+#include "../../mem/paging.h"
+#include "../../mem/frame.h"
+#include "../../drivers/drivers.h"
+
+int devfs_open(File *file);
+int devfs_readdir(File *file, DirEnt *dent, off_t index);
+
+FileOps devfsFileOps = {
+	.open = devfs_open,
+	.readdir = devfs_readdir,
+};
+
+/* Open a device node */
+int
+devfs_open(File *file)
+{
+	if (S_ISREG(file->inode->mode) || S_ISDIR(file->inode->mode))
+		return 0;
+
+	/* Use major number to find relevant driver */
+	Driver *driver;
+	for (driver = drivers; driver; driver = driver->next)
+		if (driver->major == MAJOR(file->inode->dev))
+			break;
+	if (!driver)
+		return -ENXIO;
+
+	file->ops = driver->ops;
+	if (!file->ops)
+		return 0;
+	if (!file->ops->open)
+		return 0;
+	return file->ops->open(file);
+	/* For checking the minor internally */
+}
+
+/* Read a directory entry */
+int
+devfs_readdir(File *file, DirEnt *dent, off_t index)
+{
+	DirEntry *de;
+
+	if (!index--) {
+		dent->ino = file->inode->ino;
+		dent->type = DT_DIR;
+		dent->namelen = 2;
+		strncpy(dent->name, ".", dent->namelen);
+		return 0;
+	}
+
+	for (de = file->inode->dirEntries; de && index; de = de->next, index--);
+	if (!de)
+		return -ENOENT;
+	dent->ino = de->inode->ino;
+	if (S_ISBLK(de->inode->mode))
+		dent->type = DT_BLK;
+	if (S_ISCHR(de->inode->mode))
+		dent->type = DT_CHR;
+	if (S_ISDIR(de->inode->mode))
+		dent->type = DT_DIR;
+	if (S_ISFIFO(de->inode->mode))
+		dent->type = DT_FIFO;
+	if (S_ISLNK(de->inode->mode))
+		dent->type = DT_LNK;
+	if (S_ISREG(de->inode->mode))
+		dent->type = DT_REG;
+	if (S_ISSOCK(de->inode->mode))
+		dent->type = DT_SOCK;
+	dent->namelen = strnlen(de->name, NAME_MAX) + 1;
+	strncpy(dent->name, de->name, NAME_MAX);
+	return 0;
+}
diff --git a/vfs/devfs/fs.h b/vfs/devfs/fs.h
new file mode 100644
index 0000000..f972b67
--- /dev/null
+++ b/vfs/devfs/fs.h
@@ -0,0 +1,13 @@
+#ifndef KERNEL_VFS_DEVFS_H
+#define KERNEL_VFS_DEVFS_H
+
+#include "../vfs.h"
+
+/* Operations */
+extern SuperOps devfsSuperOps;
+extern InodeOps devfsInodeOps;
+extern FileOps devfsFileOps;
+
+extern FileSystemType devfsType;
+
+#endif
diff --git a/vfs/devfs/inode.c b/vfs/devfs/inode.c
new file mode 100644
index 0000000..2208263
--- /dev/null
+++ b/vfs/devfs/inode.c
@@ -0,0 +1,69 @@
+/*
+ * This file contains the functions dealing with DevFS inodes.  The VFS will
+ * call these when it performs operations on DevFS Inodes, or is dealing with
+ * the DevFS hierarchy.
+ */
+
+#include <string.h>
+#include "fs.h"
+#include "../vfs.h"
+#include "../../mem/heap.h"
+
+int devfs_create(Inode *inode, DirEntry *entry, mode_t mode);
+Inode *devfs_lookup(Inode *inode, const char *name);
+int devfs_mkdir(Inode *inode, DirEntry *entry, mode_t mode);
+int devfs_rmdir(Inode *inode, DirEntry *entry);
+int devfs_mknod(Inode *inode, DirEntry *entry, mode_t mode, dev_t dev);
+int devfs_rename(Inode *si, DirEntry *sde, Inode *di, DirEntry *dde);
+
+InodeOps devfsInodeOps = {
+	.create = devfs_create,
+	.lookup = devfs_lookup,
+	.mkdir = devfs_mkdir,
+	.rmdir = devfs_rmdir,
+	.mknod = devfs_mknod,
+	.rename = devfs_rename,
+};
+
+/* Create a file */
+int
+devfs_create(Inode *inode, DirEntry *entry, mode_t mode)
+{
+	return 0;
+}
+
+
+/* Look up a file */
+Inode *
+devfs_lookup(Inode *inode, const char *name)
+{
+	return NULL;
+}
+
+/* Make a directory */
+int
+devfs_mkdir(Inode *inode, DirEntry *entry, mode_t mode)
+{
+	return 0;
+}
+
+/* Remove a directory */
+int
+devfs_rmdir(Inode *inode, DirEntry *entry)
+{
+	return 0;
+}
+
+/* Make a node */
+int
+devfs_mknod(Inode *inode, DirEntry *entry, mode_t mode, dev_t dev)
+{
+	return 0;
+}
+
+/* Rename/mode a directory entry */
+int
+devfs_rename(Inode *si, DirEntry *sde, Inode *di, DirEntry *dde)
+{
+	return 0;
+}
diff --git a/vfs/devfs/super.c b/vfs/devfs/super.c
new file mode 100644
index 0000000..e57d60b
--- /dev/null
+++ b/vfs/devfs/super.c
@@ -0,0 +1,85 @@
+/*
+ * This file controls the superblock for DevFS.  It supports mounting new DevFS
+ * filesystems.  The VFS will use the calls here when dealing directly with the
+ * filesystem structure.
+ */
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include "fs.h"
+#include "../vfs.h"
+#include "../super.h"
+#include "../inode.h"
+#include "../../mem/heap.h"
+
+Inode *devfs_mount(FileSystemType *type, int flags,
+                   const char *dev, void *data);
+Inode *devfs_alloc_inode(SuperBlock *sb);
+
+FileSystemType devfsType = {
+	.name    = "DevFS",
+	.mount   = devfs_mount,
+//	.kill_sb = devfs_kill_sb,
+};
+
+SuperOps devfsSuperOps = {
+	.alloc_inode  = devfs_alloc_inode,
+//	.free_inode   = devfs_free_inode,
+//	.write_inode  = devfs_write_inode,
+//	.delete_inode = devfs_delete_inode,
+};
+
+Inode devfsRoot = {
+	.mode = S_IFDIR | 0755,
+	.ops = &devfsInodeOps,
+	.fileOps = &devfsFileOps,
+	.size = 4096,
+};
+
+/* Mount a DevFS instance */
+Inode *
+devfs_mount(FileSystemType *type, int flags, const char *dev, void *data)
+{
+	if (type != &devfsType)
+		return NULL;
+
+	SuperBlock *super = kmalloc(sizeof(SuperBlock));
+
+	super->type = type;
+	super->ops = &devfsSuperOps;
+	init_lock(&super->lock);
+
+	/*
+	 * DevFS is special - it has a globally unique root.  This means that no
+	 * matter where a DevFS instance is mounted, it will be mounted with
+	 * this inode, meaning the contents are the same.  It basically causes
+	 * the mountpoint to act as a hard-link.  This means you can mount a new
+	 * DevFS instance in a chroot() environment, and not have to remake all
+	 * the device nodes.
+	 */
+	Inode *inode = &devfsRoot;
+	inode->nlink++;
+	inode->super = super;
+	/* FIXME: need an inode per super, or only one super */
+	super->root = inode;
+	/* Never free */
+	if (!inode->usage)
+		inode_get(inode);
+
+	return inode;
+}
+
+/* Allocate an inode */
+Inode *
+devfs_alloc_inode(SuperBlock *sb)
+{
+	Inode *inode = kmalloc(sizeof(Inode));
+	init_lock(&inode->lock);
+	inode->ops = &devfsInodeOps;
+	inode->fileOps = &devfsFileOps;
+	inode->super = sb;
+	inode->nlink = 1;
+	return inode_get(inode); /* This ensures that the inode is never free */
+}
diff --git a/vfs/ext2fs/block.c b/vfs/ext2fs/block.c
new file mode 100644
index 0000000..28cf480
--- /dev/null
+++ b/vfs/ext2fs/block.c
@@ -0,0 +1,32 @@
+/*
+ * This file contains the Ext2 block implementation.  It reads a backing file
+ * from the super block in the intervals specified by the superblock.  It also
+ * contains functions for reading blocks from files.
+ */
+
+#include <stdint.h>
+#include "fs.h"
+
+/* Read a block of data */
+void
+ext2_read_block(SuperBlock *super, uint32_t index, char *buf)
+{
+	Ext2Super *rsuper = super->data;
+
+	super->back->pos = index * (1024 << rsuper->blockSize);
+	file_read(super->back, buf, 1024 << rsuper->blockSize);
+}
+
+/* Get the data block address from inode block index */
+uint32_t
+ext2_get_data_addr(SuperBlock *super, Ext2Inode *node, uint32_t index)
+{
+	uint32_t tmp;
+	char block[4096];
+
+	/* Main blocks */
+	if (index < 12)
+		return node->directBlock[index];
+	index -= 12;
+	return 0;
+}
diff --git a/vfs/ext2fs/file.c b/vfs/ext2fs/file.c
new file mode 100644
index 0000000..d0caa35
--- /dev/null
+++ b/vfs/ext2fs/file.c
@@ -0,0 +1,86 @@
+/*
+ * This file controls access to Ext2FS Files.  It contains the functions called
+ * by the VFS for any operation on a Ext2FS File struct.
+ */
+
+#include <stddef.h>
+#include <string.h>
+#include <dirent.h>
+#include <errno.h>
+#include "fs.h"
+#include "../vfs.h"
+
+size_t ext2fs_read(File *file, char *buf, size_t size, off_t offset);
+int ext2fs_readdir(File *file, DirEnt *dent, off_t index);
+int ext2fs_open(File *file);
+
+FileOps ext2fsFileOps = {
+	.read = ext2fs_read,
+	.readdir = ext2fs_readdir,
+	.open = ext2fs_open,
+};
+
+/* Read a file */
+size_t
+ext2fs_read(File *file, char *buf, size_t size, off_t offset)
+{
+	Ext2Inode inode;
+	uint16_t min;
+	size_t count = 0, i = offset / 4096;
+	uint32_t blk;
+	char ebuf[4096];
+	ext2_read_inode(file->inode->super, file->inode->ino, &inode);
+	if (offset > inode.lsize)
+		return 0;
+	if (size + offset > inode.lsize)
+		size = inode.lsize - offset;
+	while (size) {
+		min = (size > 0x1000) ? 0x1000 : size;
+		blk = ext2_get_data_addr(file->inode->super, &inode, i);
+		ext2_read_block(file->inode->super, blk, ebuf);
+		memcpy(buf + count, ebuf + (offset % 4096), min);
+		size -= min;
+		count += min;
+		i++;
+	}
+	if (count >= 0x1000)
+	return count;
+}
+
+/* Read a directory entry */
+int
+ext2fs_readdir(File *file, DirEnt *dent, off_t index)
+{
+	char buf[4096];
+	uint32_t block, blk = 0;
+	Ext2DirEntry *de;
+	Ext2Inode inode;
+	ext2_read_inode(file->inode->super, file->inode->ino, &inode);
+	for (blk = 0; blk < 0xFFFF; blk++) {
+		block = ext2_get_data_addr(file->inode->super, &inode, blk);
+		if (!block)
+			return -ENOENT;
+		ext2_read_block(file->inode->super, block, buf);
+		for (de = (Ext2DirEntry *) buf;
+		     index && de < (Ext2DirEntry *) (buf + 4096);
+		     de = (void *) ((char *) de + de->size), index--);
+		if (de >= (Ext2DirEntry *) (buf + 4096))
+			return -ENOENT;
+		if (!index)
+			break;
+	}
+	if (!de->ino)
+		return -ENOENT;
+	dent->ino = de->ino;
+	dent->type = de->type;
+	dent->namelen = de->nameLen + 1;
+	strncpy(dent->name, de->name, de->size);
+	return 0;
+}
+
+/* Open a file */
+int
+ext2fs_open(File *file)
+{
+	return 0;
+}
diff --git a/vfs/ext2fs/fs.h b/vfs/ext2fs/fs.h
new file mode 100644
index 0000000..0e5cadb
--- /dev/null
+++ b/vfs/ext2fs/fs.h
@@ -0,0 +1,116 @@
+#ifndef KERNEL_VFS_EXT2FS_H
+#define KERNEL_VFS_EXT2FS_H
+
+#include "../vfs.h"
+
+typedef struct Ext2Super Ext2Super;
+typedef struct Ext2BlockGroupDesc Ext2BlockGroupDesc;
+typedef struct Ext2Inode Ext2Inode;
+typedef struct Ext2DirEntry Ext2DirEntry;
+
+/* Structure of the Ext2 SuperBlock */
+struct Ext2Super {
+	uint32_t numInodes;
+	uint32_t numBlocks;
+	uint32_t reservedBlocks;
+	uint32_t unallocBlocks;
+	uint32_t unallocInodes;
+	uint32_t superBlock;
+	uint32_t blockSize;
+	uint32_t fragSize;
+	uint32_t blocksPerGroup;
+	uint32_t fragsPerGroup;
+	uint32_t inodesPerGroup;
+	uint32_t lastMountTime;
+	uint32_t lastWriteTime;
+	uint16_t lastCheck;
+	uint16_t mustCheck;
+	uint16_t signature;
+	uint16_t state;
+	uint16_t error;
+	uint16_t verMinor;
+	uint32_t lastCheckTime;
+	uint32_t checkInterval;
+	uint32_t creator;
+	uint32_t verMajor;
+	uint16_t uid, gid;
+	/* Extended fields */
+	uint32_t firstAvailInode;
+	uint16_t inodeSize;
+	uint16_t blockGroup;
+	uint32_t optionalFeatures;
+	uint32_t requiredFeatures;
+	uint32_t writableFeatures;
+	char fsId[16];
+	char volumeName[16];
+	char lastPath[64];
+	uint32_t compression;
+	uint8_t preallocFileBlocks;
+	uint8_t preallocDirBlocks;
+	uint16_t unused;
+	char journalId[16];
+	uint32_t journalInode;
+	uint32_t journalDev;
+	uint32_t orphanHead;
+} __attribute__((packed));
+
+/* Structure of the Ext2 Block Group Descriptor */
+struct Ext2BlockGroupDesc {
+	uint32_t blockUsage;
+	uint32_t inodeUsage;
+	uint32_t inodeTable;
+	uint16_t unallocBlocks;
+	uint16_t unallocInodes;
+	uint16_t numDirs;
+} __attribute__((packed));
+
+/* Structure of the Ext2 Inode */
+struct Ext2Inode {
+	uint16_t type;
+	uint16_t uid;
+	uint32_t lsize;
+	uint32_t lastAccessTime;
+	uint32_t creationTime;
+	uint32_t lastWriteTime;
+	uint32_t deletionTime;
+	uint16_t gid;
+	uint16_t numHardLinks;
+	uint32_t numSectors;
+	uint32_t flags;
+	uint32_t osA;
+	uint32_t directBlock[12];
+	uint32_t singleBlock;
+	uint32_t doubleBlock;
+	uint32_t tripleBlock;
+	uint32_t gen;
+	uint32_t attr;
+	union {
+		uint32_t usize;
+		uint32_t dirACL;
+	};
+	uint32_t fragment;
+	uint32_t osB[3];
+} __attribute__((packed));
+
+/* Structure of the Ext2 Directory Entry */
+struct Ext2DirEntry {
+	uint32_t ino;
+	uint16_t size;
+	uint8_t nameLen;
+	uint8_t type;
+	char name[];
+} __attribute__((packed));
+
+/* Operations */
+extern SuperOps ext2fsSuperOps;
+extern InodeOps ext2fsInodeOps;
+extern FileOps ext2fsFileOps;
+
+extern FileSystemType ext2fsType;
+
+void ext2_read_vnode(SuperBlock *super, uint32_t index, Inode *res);
+void ext2_read_inode(SuperBlock *super, uint32_t index, Ext2Inode *res);
+void ext2_read_block(SuperBlock *super, uint32_t index, char *buf);
+uint32_t ext2_get_data_addr(SuperBlock *super, Ext2Inode *node, uint32_t index);
+
+#endif
diff --git a/vfs/ext2fs/inode.c b/vfs/ext2fs/inode.c
new file mode 100644
index 0000000..3437220
--- /dev/null
+++ b/vfs/ext2fs/inode.c
@@ -0,0 +1,94 @@
+/*
+ * This file contains the Ext2 inode implementation.  It contains the functions
+ * for the inode operations, as well as several internal operations relating to
+ * inodes required for the full Ext2 implementation.
+ */
+
+#include <stdint.h>
+#include <string.h>
+#include <fcntl.h>
+#include "fs.h"
+#include "../vfs.h"
+#include "../super.h"
+#include "../../mem/heap.h"
+
+Inode *ext2fs_lookup(Inode *inode, const char *name);
+
+InodeOps ext2fsInodeOps = {
+	.lookup = ext2fs_lookup,
+};
+
+/* Read an Ext2 inode as a VFS inode */
+void
+ext2_read_vnode(SuperBlock *super, uint32_t index, Inode *res)
+{
+	Ext2Inode inode;
+	ext2_read_inode(super, index, &inode);
+
+	res->ino = index;
+	res->usage = 0;
+	res->uid = inode.uid;
+	res->gid = inode.gid;
+	res->mode = inode.type;
+	res->nlink = inode.numHardLinks;
+	res->size = inode.lsize;
+	res->ops = &ext2fsInodeOps;
+	res->fileOps = &ext2fsFileOps;
+	res->super = super;
+}
+
+/* Read an Ext2 inode */
+void
+ext2_read_inode(SuperBlock *super, uint32_t index, Ext2Inode *res)
+{
+	Ext2Super *rsuper = super->data;
+
+	/* Find the Block Group Descriptor */
+	Ext2BlockGroupDesc bgd;
+	uint32_t groups = rsuper->numBlocks / rsuper->blocksPerGroup;
+	uint32_t group = (index - 1) / rsuper->inodesPerGroup;
+	index = (index - 1) % rsuper->inodesPerGroup;
+	if (rsuper->numBlocks % rsuper->blocksPerGroup)
+		groups++;
+	super->back->pos = (rsuper->blockSize ? 1 : 2)
+	                 * (1024 << rsuper->blockSize);
+	super->back->pos += sizeof(Ext2BlockGroupDesc) * group;
+	file_read(super->back, (char *) &bgd, sizeof(Ext2BlockGroupDesc));
+	/* Read Inode */
+	super->back->pos = bgd.inodeTable * (1024 << rsuper->blockSize);
+	super->back->pos += rsuper->inodeSize * index;
+	file_read(super->back, (char *) res, sizeof(Ext2Inode));
+}
+
+/* Look up a file */
+Inode *
+ext2fs_lookup(Inode *vnode, const char *name)
+{
+	char buf[4096];
+	uint32_t block, blk = 0;
+	Ext2DirEntry *de;
+	Ext2Inode inode;
+	ext2_read_inode(vnode->super, vnode->ino, &inode);
+	for (blk = 0; blk < 0xFFFF; blk++) {
+		block = ext2_get_data_addr(vnode->super, &inode, blk);
+		if (!block)
+			return NULL;
+		ext2_read_block(vnode->super, block, buf);
+		for (de = (Ext2DirEntry *) buf;
+		     strncmp(de->name, name, de->nameLen)
+		     && de < (Ext2DirEntry *) (buf + 4096);
+		     de = (void *) ((char *) de + de->size));
+		if (de >= (Ext2DirEntry *) (buf + 4096))
+			return NULL;
+		if (!strncmp(de->name, name, de->nameLen))
+			break;
+	}
+	if (!de->ino)
+		return NULL;
+	Inode *res = super_find_inode(vnode->super, de->ino);
+	if (res)
+		return res;
+	res = kmalloc(sizeof(Inode));
+	ext2_read_vnode(vnode->super, de->ino, res);
+	return res;
+}
diff --git a/vfs/ext2fs/super.c b/vfs/ext2fs/super.c
new file mode 100644
index 0000000..98ca800
--- /dev/null
+++ b/vfs/ext2fs/super.c
@@ -0,0 +1,89 @@
+/*
+ * This file controls the superblock for Ext2FS.  It supports mounting new
+ * Ext2FS filesystems.  The VFS will use the calls here when dealing directly
+ * with the filesystem structure.
+ */
+
+#include <stdint.h>
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/mount.h>
+#include "fs.h"
+#include "../vfs.h"
+#include "../inode.h"
+#include "../super.h"
+#include "../../mem/heap.h"
+#include "../../task/task.h"
+#include "../../screen.h"
+
+Inode *ext2fs_mount(FileSystemType *type, int flags,
+                      const char *dev, void *data);
+Inode *ext2fs_alloc_inode(SuperBlock *sb);
+
+FileSystemType ext2fsType = {
+	.name = "Ext2FS",
+	.mount = ext2fs_mount,
+};
+
+SuperOps ext2fsSuperOps = {
+	.alloc_inode = ext2fs_alloc_inode,
+};
+
+/* Mount a Ext2FS instance */
+Inode *
+ext2fs_mount(FileSystemType *type, int flags, const char *dev, void *data)
+{
+	if (type != &ext2fsType)
+		return NULL;
+
+	int fd = open(dev, O_RDONLY);
+	if (fd < 0)
+		return (void *) fd;
+	File *back = file_get(current->files->fd[fd]);
+	close(fd);
+	if (!S_ISBLK(back->mode)) {
+		file_put(back);
+		return (void *) -ENOTBLK;
+	}
+
+	Ext2Super *rsuper = kmalloc(sizeof(Ext2Super));
+	back->pos = 1024;
+	file_read(back, (char *) rsuper, sizeof(Ext2Super));
+	if (rsuper->signature != 0xEF53) {
+		kprintf("Error while mounting %s: Not an Ext2FS drive", dev);
+		file_put(back);
+		kfree(rsuper);
+		return (void *) -EINVAL;
+	}
+	if (rsuper->requiredFeatures & 0x01) {
+		kprintf("Cannot mount %s: Compression required", dev);
+		file_put(back);
+		kfree(rsuper);
+		return (void *) -EINVAL;
+	}
+	if ((rsuper->writableFeatures & 0x02) && !(flags & MS_RDONLY)) {
+		kprintf("Cannot mount %s: 64-bit File System required", dev);
+		file_put(back);
+		kfree(rsuper);
+		return (void *) -EINVAL;
+	}
+
+	SuperBlock *super = kmalloc(sizeof(SuperBlock));
+	super->type = type;
+	super->ops = &ext2fsSuperOps;
+	init_lock(&super->lock);
+	super->back = back;
+	super->data = rsuper;
+	super->root = kmalloc(sizeof(Inode));
+	ext2_read_vnode(super, 2, super->root);
+
+	return super->root;
+}
+
+/* Allocate an inode */
+Inode *
+ext2fs_alloc_inode(SuperBlock *sb)
+{
+	return NULL;
+}
diff --git a/vfs/file.c b/vfs/file.c
new file mode 100644
index 0000000..d3dfdc6
--- /dev/null
+++ b/vfs/file.c
@@ -0,0 +1,181 @@
+/*
+ * This file handles the file operations.  Most of the functions here are just
+ * wrapper around the file operations.  They generally check if the operation
+ * exists and use it if it does.  If it doesn't they may implement a generic
+ * action, or just simply return.  They also perform the necessary validation to
+ * ensure the operation can be called on the particular file.
+ */
+
+#include <string.h>
+#include <errno.h>
+#include "vfs.h"
+#include "inode.h"
+#include "cache.h"
+#include "../mem/heap.h"
+#include "../mem/frame.h"
+#include "../screen.h"
+
+/* Get a File */
+File *
+file_get(File *file)
+{
+	file->usage++;
+	return file;
+}
+
+/* Put a File */
+void
+file_put(File *file)
+{
+	ASSERT(file->usage);
+	if (--file->usage)
+		return;
+	if (file->inode)
+		inode_put(file->inode);
+	if (file->path)
+		destroy_custody_chain(file->path);
+	kfree(file);
+}
+
+/* Read a file */
+size_t
+file_read(File *file, char *buf, size_t size)
+{
+	size_t count = 0;
+	if (!S_ISREG(file->inode->mode)) {
+		if (!file->ops)
+			return count;
+		if (!file->ops->read)
+			return count;
+		count = file->ops->read(file, buf, size, file->pos);
+		file->pos += count;
+		return count;
+	}
+	if (file->pos > file->inode->size)
+		return count;
+	if (size + file->pos > file->inode->size)
+		size = file->inode->size - file->pos;
+
+	uint16_t min, offset = file->pos & 0xFFF;
+	Page *page;
+	page_t oldPage;
+	acquire(&file->lock);
+	while (size) {
+		min = (size > 0x1000) ? 0x1000 : size;
+		page = page_find(file->inode, file->pos + (count & ~0xFFF));
+		if (!page) {
+			if (!file->ops)
+				goto end;
+			if (!file->ops->read)
+				goto end;
+			file->ops->read(file, buf + count, min,
+		                        file->pos + count);
+		} else {
+			acquire(&quickPageLock);
+			oldPage = quick_page(page->frame);
+			memcpy(buf + count, QUICK_PAGE + offset, min);
+			quick_page(oldPage);
+			release(&quickPageLock);
+		}
+		offset = 0;
+end:
+		size -= min;
+		count += min;
+	}
+	file->pos += count;
+	release(&file->lock);
+	return count;
+}
+
+/* Write a file */
+size_t
+file_write(File *file, char *buf, size_t size)
+{
+	size_t count = 0;
+	if (!S_ISREG(file->inode->mode)) {
+		if (!file->ops)
+			return count;
+		if (!file->ops->write)
+			return count;
+		count = file->ops->write(file, buf, size, file->pos);
+		file->pos += count;
+		return count;
+	}
+	if (size + file->pos > file->inode->size)
+		file->inode->size = size + file->pos;
+
+	uint16_t min, offset = file->pos & 0xFFF;
+	Page *page;
+	page_t oldPage;
+	acquire(&file->lock);
+	while (size) {
+		min = (size > 0x1000) ? 0x1000 : size;
+		page = page_find(file->inode, file->pos + (count & ~0xFFF));
+		if (!page) {
+			if (!file->ops)
+				goto end;
+			if (!file->ops->write)
+				goto end;
+			file->ops->write(file, buf + count, min,
+			                 file->pos + count);
+		} else {
+			acquire(&quickPageLock);
+			oldPage = quick_page(page->frame);
+			memcpy(QUICK_PAGE + offset, buf + count, min);
+			quick_page(oldPage);
+			release(&quickPageLock);
+		}
+		offset = 0;
+end:
+		size -= min;
+		count += min;
+	}
+	file->pos += count;
+	release(&file->lock);
+	return count;
+}
+
+/* I/O Control */
+int
+file_ioctl(File *file, unsigned long request, uintptr_t argp)
+{
+	if (!file->ops)
+		return -EINVAL;
+	if (!file->ops->ioctl)
+		return -ENOTTY;
+	return file->ops->ioctl(file, request, argp);
+}
+
+/* Read a directory entry (DirEnt) from a directory */
+int
+file_readdir(File *file, DirEnt *dent, off_t index)
+{
+	if (!file->ops)
+		return -EINVAL;
+	if (!file->ops->readdir)
+		return -EINVAL;
+	return file->ops->readdir(file, dent, index);
+}
+
+/* Open a file */
+int
+file_open(File *file)
+{
+	if (!file->ops)
+		return -EINVAL;
+	if (!file->ops->open)
+		return -EINVAL;
+	return file->ops->open(file);
+}
+
+/* Map a file into memory */
+void
+file_mmap(File *file, void *addr, size_t len, off_t offset)
+{
+	if (!file->ops)
+		return;
+	if (file->ops->mmap)
+		return file->ops->mmap(file, addr, len, offset);
+	if (S_ISREG(file->mode))
+		file->ops->read(file, addr, len, offset);
+}
diff --git a/vfs/inode.c b/vfs/inode.c
new file mode 100644
index 0000000..34b5c65
--- /dev/null
+++ b/vfs/inode.c
@@ -0,0 +1,204 @@
+/*
+ * This file deals with inode operations.  Most of the functions here are just
+ * wrappers for the inode operations.  They generally check if the operation
+ * exists and use it if it does.  If it doesn't they may implement a generic
+ * action, or just simply return.  They also perform the necessary validation to
+ * ensure the operation can be called on the particular inode.
+ */
+
+#include <string.h>
+#include <errno.h>
+#include "vfs.h"
+#include "cache.h"
+#include "inode.h"
+#include "super.h"
+#include "../mem/heap.h"
+#include "../screen.h"
+
+/* Get an Inode */
+Inode *
+inode_get(Inode *inode)
+{
+	/* Add the inode to the Inode List */
+	if (!inode->usage && inode->super) {
+		if (!inode->super->inodes) {
+			inode->super->inodes = inode;
+		} else {
+			inode->lnext = inode->super->inodes;
+			inode->super->inodes = inode;
+		}
+		inode->super->cachedInodes++;
+	}
+
+	inode->usage++;
+	return inode;
+}
+
+/* Put an Inode */
+void
+inode_put(Inode *inode)
+{
+	ASSERT(inode->usage);
+	if (--inode->usage)
+		return;
+
+	/* Remove the inode from the Inode List */
+	Inode *search, *prev = NULL;
+	if (inode->super) {
+		for (search = inode->super->inodes; search;
+		     prev = search, search = search->lnext)
+			if (search == inode)
+				break;
+		if (search) {
+			if (prev)
+				prev->lnext = search->lnext;
+			else
+				inode->super->inodes = search->lnext;
+		}
+	}
+
+	/* Clean up */
+	if (S_ISDIR(inode->mode))
+		entry_clean(inode);
+	else
+		page_clean(inode);
+
+	kfree(inode);
+}
+
+/* Create an inode */
+int
+inode_create(Inode *inode, DirEntry *entry, mode_t mode)
+{
+	if (!inode->ops)
+		return -EINVAL;
+	if (!inode->ops->create)
+		return -EINVAL;
+	acquire(&inode->lock);
+	int err = inode->ops->create(inode, entry, mode);
+	if (!err) {
+		entry_add(inode, entry);
+		entry->inode = super_alloc_inode(inode->super);
+		entry->inode->mode = mode | S_IFREG;
+	}
+	release(&inode->lock);
+	return err;
+}
+
+/* Find a child inode in a directory inode */
+DirEntry *
+inode_lookup(Inode *inode, const char *name)
+{
+	if (!S_ISDIR(inode->mode))
+		return NULL;
+
+	acquire(&inode->lock);
+
+	/* Check cache first */
+	DirEntry *de = entry_find(inode, name);
+	if (de) {
+		entry_get(de);
+		if (de->inode) {
+			release(&inode->lock);
+			return de;
+		}
+	}
+	/* Try file system lookup */
+	if (!inode->ops) {
+		release(&inode->lock);
+		return NULL;
+	}
+	if (!inode->ops->lookup) {
+		release(&inode->lock);
+		return NULL;
+	}
+	Inode *child = inode->ops->lookup(inode, name);
+
+	/* The file doesn't exist */
+	if (!child) {
+		if (de)
+			entry_remove(inode, name);
+		release(&inode->lock);
+		return NULL;
+	}
+
+	/* Fill in DirEntry */
+	if (!de) {
+		de = kmalloc(sizeof(DirEntry));
+		strncpy(de->name, name, NAME_MAX);
+		de->super = inode->super;
+		entry_add(inode, de);
+	}
+	de->inode = inode_get(child);
+	de->mnt = NULL;
+	release(&inode->lock);
+
+	return entry_get(de);
+}
+
+/* Make a directory */
+int
+inode_mkdir(Inode *inode, DirEntry *entry, mode_t mode)
+{
+	if (!inode->ops)
+		return -EINVAL;
+	if (!inode->ops->mkdir)
+		return -EINVAL;
+	acquire(&inode->lock);
+	int err = inode->ops->mkdir(inode, entry, mode);
+	if (!err) {
+		entry_add(inode, entry);
+		entry->inode = super_alloc_inode(inode->super);
+		entry->inode->mode = mode | S_IFDIR;
+	}
+	release(&inode->lock);
+	return err;
+}
+
+/* Remove a directory */
+int
+inode_rmdir(Inode *inode, DirEntry *entry)
+{
+	if (!inode->ops)
+		return -EINVAL;
+	if (!inode->ops->rmdir)
+		return -EINVAL;
+	acquire(&inode->lock);
+	int err = inode->ops->rmdir(inode, entry);
+	if (!err)
+		entry_remove(inode, entry->name);
+	release(&inode->lock);
+	return err;
+}
+
+/* Make a node */
+int
+inode_mknod(Inode *inode, DirEntry *entry, mode_t mode, dev_t dev)
+{
+	if (!inode->ops)
+		return -EINVAL;
+	if (!inode->ops->mknod)
+		return -EINVAL;
+	acquire(&inode->lock);
+
+	int err = inode->ops->mknod(inode, entry, mode, dev);
+	if (!err) {
+		entry_add(inode, entry);
+		entry->inode = super_alloc_inode(inode->super);
+		entry->inode->mode = mode; /* FIXME */
+		entry->inode->dev = dev;
+	}
+	release(&inode->lock);
+	return err;
+}
+
+/* Rename/mode a directory entry */
+int
+inode_rename(Inode *si, DirEntry *sde, Inode *di, DirEntry *dde)
+{
+	if (!si->ops)
+		return -EINVAL;
+	if (!si->ops->rename)
+		return -EINVAL;
+	return 0;
+}
diff --git a/vfs/inode.h b/vfs/inode.h
new file mode 100644
index 0000000..adf5b3b
--- /dev/null
+++ b/vfs/inode.h
@@ -0,0 +1,15 @@
+#ifndef KERNEL_VFS_INODE_H
+#define KERNEL_VFS_INODE_H
+
+#include "vfs.h"
+
+Inode *inode_get(Inode *inode);
+void inode_put(Inode *inode);
+int inode_create(Inode *inode, DirEntry *entry, mode_t mode);
+DirEntry *inode_lookup(Inode *inode, const char *name);
+int inode_mkdir(Inode *inode, DirEntry *entry, mode_t mode);
+int inode_rmdir(Inode *inode, DirEntry *entry);
+int inode_mknod(Inode *inode, DirEntry *entry, mode_t mode, dev_t dev);
+int inode_rename(Inode *si, DirEntry *sde, Inode *di, DirEntry *dde);
+
+#endif
diff --git a/vfs/procfs/file.c b/vfs/procfs/file.c
new file mode 100644
index 0000000..b292cf4
--- /dev/null
+++ b/vfs/procfs/file.c
@@ -0,0 +1,126 @@
+/*
+ * This file controls access to ProcFS Files.  It contains the functions called
+ * by the VFS for any operation on a File struct belonging to ProcFS.
+ */
+
+#include <stddef.h>
+#include <string.h>
+#include <errno.h>
+#include "fs.h"
+#include "../vfs.h"
+#include "../cache.h"
+#include "../../mem/paging.h"
+#include "../../mem/frame.h"
+
+size_t procfs_read(File *file, char *buf, size_t size, off_t offset);
+size_t procfs_write(File *file, char *buf, size_t size, off_t offset);
+int procfs_readdir(File *file, DirEnt *dent, off_t index);
+int procfs_open(File *file);
+
+FileOps procfsFileOps = {
+	.read = procfs_read,
+	.write = procfs_write,
+	.readdir = procfs_readdir,
+	.open = procfs_open,
+};
+
+/* Read a file */
+size_t
+procfs_read(File *file, char *buf, size_t size, off_t offset)
+{
+	if (offset > file->inode->size)
+		return 0;
+	if (size + offset > file->inode->size)
+		size = file->inode->size - offset;
+
+	uint16_t min;
+	size_t count = 0;
+	Page *page;
+	page_t oldPage;
+	while (size) {
+		min = (size > 0x1000) ? 0x1000 : size;
+		page = page_find(file->inode, offset);
+		if (!page)
+			break;
+		acquire(&quickPageLock);
+		oldPage = quick_page(page->frame);
+		memcpy(buf + count, QUICK_PAGE, min);
+		quick_page(oldPage);
+		release(&quickPageLock);
+		size -= min;
+		count += min;
+	}
+	return count;
+}
+
+/* Write a file */
+size_t
+procfs_write(File *file, char *buf, size_t size, off_t offset)
+{
+	if (size + offset > file->inode->size)
+		file->inode->size = size + offset;
+
+	uint16_t min;
+	size_t count = 0;
+	Page *page;
+	page_t oldPage;
+	while (size) {
+		min = (size > 0x1000) ? 0x1000 : size;
+		page = page_find(file->inode, offset);
+		if (!page)
+			page = page_create(file->inode, alloc_frames(1),
+		                           offset);
+		acquire(&quickPageLock);
+		oldPage = quick_page(page->frame);
+		memcpy(QUICK_PAGE, buf + count, min);
+		quick_page(oldPage);
+		release(&quickPageLock);
+		size -= min;
+		count += min;
+	}
+	return count;
+}
+
+/* Read a directory entry */
+int
+procfs_readdir(File *file, DirEnt *dent, off_t index)
+{
+	DirEntry *de;
+
+	if (!index--) {
+		dent->ino = file->inode->ino;
+		dent->type = DT_DIR;
+		dent->namelen = 2;
+		strncpy(dent->name, ".", dent->namelen);
+		return 0;
+	}
+
+	for (de = file->inode->dirEntries; de && index; de = de->next, index--);
+	if (!de)
+		return -ENOENT;
+	dent->ino = de->inode->ino;
+	if (S_ISBLK(de->inode->mode))
+		dent->type = DT_BLK;
+	if (S_ISCHR(de->inode->mode))
+		dent->type = DT_CHR;
+	if (S_ISDIR(de->inode->mode))
+		dent->type = DT_DIR;
+	if (S_ISFIFO(de->inode->mode))
+		dent->type = DT_FIFO;
+	if (S_ISLNK(de->inode->mode))
+		dent->type = DT_LNK;
+	if (S_ISREG(de->inode->mode))
+		dent->type = DT_REG;
+	if (S_ISSOCK(de->inode->mode))
+		dent->type = DT_SOCK;
+	dent->namelen = strnlen(de->name, NAME_MAX) + 1;
+	strncpy(dent->name, de->name, NAME_MAX);
+	return 0;
+}
+
+/* Open a file */
+int
+procfs_open(File *file)
+{
+	return 0;
+}
diff --git a/vfs/procfs/fs.h b/vfs/procfs/fs.h
new file mode 100644
index 0000000..346c1b3
--- /dev/null
+++ b/vfs/procfs/fs.h
@@ -0,0 +1,13 @@
+#ifndef KERNEL_VFS_PROCFS_H
+#define KERNEL_VFS_PROCFS_H
+
+#include "../vfs.h"
+
+/* Operations */
+extern SuperOps procfsSuperOps;
+extern InodeOps procfsInodeOps;
+extern FileOps procfsFileOps;
+
+extern FileSystemType procfsType;
+
+#endif
diff --git a/vfs/procfs/inode.c b/vfs/procfs/inode.c
new file mode 100644
index 0000000..a81c3fc
--- /dev/null
+++ b/vfs/procfs/inode.c
@@ -0,0 +1,69 @@
+/*
+ * This file contains the functions dealing with ProcFS inodes.  The VFS will
+ * call these when it performs operations on ProcFS Inodes, or is dealing with
+ * the ProcFS hierarchy.
+ */
+
+#include <string.h>
+#include "fs.h"
+#include "../vfs.h"
+#include "../../mem/heap.h"
+
+int procfs_create(Inode *inode, DirEntry *entry, mode_t mode);
+Inode *procfs_lookup(Inode *inode, const char *name);
+int procfs_mkdir(Inode *inode, DirEntry *entry, mode_t mode);
+int procfs_rmdir(Inode *inode, DirEntry *entry);
+int procfs_mknod(Inode *inode, DirEntry *entry, mode_t mode, dev_t dev);
+int procfs_rename(Inode *si, DirEntry *sde, Inode *di, DirEntry *dde);
+
+InodeOps procfsInodeOps = {
+	.create = procfs_create,
+	.lookup = procfs_lookup,
+	.mkdir = procfs_mkdir,
+	.rmdir = procfs_rmdir,
+	.mknod = procfs_mknod,
+	.rename = procfs_rename,
+};
+
+/* Create a file */
+int
+procfs_create(Inode *inode, DirEntry *entry, mode_t mode)
+{
+	return 0;
+}
+
+
+/* Look up a file */
+Inode *
+procfs_lookup(Inode *inode, const char *name)
+{
+	return NULL;
+}
+
+/* Make a directory */
+int
+procfs_mkdir(Inode *inode, DirEntry *entry, mode_t mode)
+{
+	return 0;
+}
+
+/* Remove a directory */
+int
+procfs_rmdir(Inode *inode, DirEntry *entry)
+{
+	return 0;
+}
+
+/* Make a node */
+int
+procfs_mknod(Inode *inode, DirEntry *entry, mode_t mode, dev_t dev)
+{
+	return 0;
+}
+
+/* Rename/mode a directory entry */
+int
+procfs_rename(Inode *si, DirEntry *sde, Inode *di, DirEntry *dde)
+{
+	return 0;
+}
diff --git a/vfs/procfs/super.c b/vfs/procfs/super.c
new file mode 100644
index 0000000..dcfa871
--- /dev/null
+++ b/vfs/procfs/super.c
@@ -0,0 +1,77 @@
+/*
+ * This file controls the superblock for ProcFS.  It supports mounting new ProcFS
+ * filesystems.  The VFS will use the calls here when dealing directly with the
+ * filesystem structure.
+ */
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include "fs.h"
+#include "../vfs.h"
+#include "../super.h"
+#include "../inode.h"
+#include "../../mem/heap.h"
+
+Inode *procfs_mount(FileSystemType *type, int flags,
+                   const char *dev, void *data);
+Inode *procfs_alloc_inode(SuperBlock *sb);
+
+FileSystemType procfsType = {
+	.name    = "ProcFS",
+	.mount   = procfs_mount,
+//	.kill_sb = procfs_kill_sb,
+};
+
+SuperOps procfsSuperOps = {
+	.alloc_inode  = procfs_alloc_inode,
+//	.free_inode   = procfs_free_inode,
+//	.write_inode  = procfs_write_inode,
+//	.delete_inode = procfs_delete_inode,
+};
+
+Inode procfsRoot = {
+	.mode = S_IFDIR | 0755,
+	.ops = &procfsInodeOps,
+	.fileOps = &procfsFileOps,
+	.size = 4096,
+};
+
+/* Mount a ProcFS instance */
+Inode *
+procfs_mount(FileSystemType *type, int flags, const char *dev, void *data)
+{
+	if (type != &procfsType)
+		return NULL;
+
+	SuperBlock *super = kmalloc(sizeof(SuperBlock));
+
+	super->type = type;
+	super->ops = &procfsSuperOps;
+	init_lock(&super->lock);
+
+	Inode *inode = &procfsRoot;
+	inode->nlink++;
+	inode->super = super;
+	/* FIXME: need an inode per super, or only one super */
+	super->root = inode;
+	/* Never free */
+	if (!inode->usage)
+		inode_get(inode);
+
+	return inode;
+}
+
+/* Allocate an inode */
+Inode *
+procfs_alloc_inode(SuperBlock *sb)
+{
+	Inode *inode = kmalloc(sizeof(Inode));
+	init_lock(&inode->lock);
+	inode->ops = &procfsInodeOps;
+	inode->fileOps = &procfsFileOps;
+	inode->super = sb;
+	inode->nlink = 1;
+	return inode_get(inode); /* This ensures that the inode is never free */
+}
diff --git a/vfs/super.c b/vfs/super.c
new file mode 100644
index 0000000..5201110
--- /dev/null
+++ b/vfs/super.c
@@ -0,0 +1,42 @@
+/*
+ * This file contains the wrapper functions for the Super Block Operations.  It
+ * just calls the relevant internal functions for the file system, since most of
+ * them need to know driver specific values, like where to find the file system
+ * operations.  There usually isn't a generic way to handle the functions.
+ */
+
+#include <stdint.h>
+#include "vfs.h"
+
+/* Allocate an inode */
+Inode *
+super_alloc_inode(SuperBlock *sb)
+{
+	if (!sb->ops)
+		return NULL;
+	if (!sb->ops->alloc_inode)
+		return NULL;
+	return sb->ops->alloc_inode(sb);
+}
+
+/* Search for an Inode in a Super Block's Inode list */
+Inode *
+super_find_inode(SuperBlock *sb, ino_t ino)
+{
+	Inode *inode;
+	for (inode = sb->inodes; inode; inode = inode->lnext)
+		if (inode->ino == ino)
+			break;
+	return inode;
+}
+
+/* Write an inode to disk */
+int
+super_write_inode(SuperBlock *sb, Inode *inode)
+{
+	if (!sb->ops)
+		return 0;
+	if (!sb->ops->write_inode)
+		return 0;
+	return sb->ops->write_inode(inode);
+}
diff --git a/vfs/super.h b/vfs/super.h
new file mode 100644
index 0000000..917b7c1
--- /dev/null
+++ b/vfs/super.h
@@ -0,0 +1,10 @@
+#ifndef KERNEL_VFS_SUPER_H
+#define KERNEL_VFS_SUPER_H
+
+#include "vfs.h"
+
+Inode *super_alloc_inode(SuperBlock *sb);
+Inode *super_find_inode(SuperBlock *sb, ino_t ino);
+int super_write_inode(SuperBlock *sb, Inode *inode);
+
+#endif
diff --git a/vfs/tmpfs/file.c b/vfs/tmpfs/file.c
new file mode 100644
index 0000000..252c88f
--- /dev/null
+++ b/vfs/tmpfs/file.c
@@ -0,0 +1,126 @@
+/*
+ * This file controls access to TmpFS Files.  It contains the functions called
+ * by the VFS for any operation on a File struct belonging to TmpFS.
+ */
+
+#include <stddef.h>
+#include <string.h>
+#include <errno.h>
+#include "fs.h"
+#include "../vfs.h"
+#include "../cache.h"
+#include "../../mem/paging.h"
+#include "../../mem/frame.h"
+
+size_t tmpfs_read(File *file, char *buf, size_t size, off_t offset);
+size_t tmpfs_write(File *file, char *buf, size_t size, off_t offset);
+int tmpfs_readdir(File *file, DirEnt *dent, off_t index);
+int tmpfs_open(File *file);
+
+FileOps tmpfsFileOps = {
+	.read = tmpfs_read,
+	.write = tmpfs_write,
+	.readdir = tmpfs_readdir,
+	.open = tmpfs_open,
+};
+
+/* Read a file */
+size_t
+tmpfs_read(File *file, char *buf, size_t size, off_t offset)
+{
+	if (offset > file->inode->size)
+		return 0;
+	if (size + offset > file->inode->size)
+		size = file->inode->size - offset;
+
+	uint16_t min;
+	size_t count = 0;
+	Page *page;
+	page_t oldPage;
+	while (size) {
+		min = (size > 0x1000) ? 0x1000 : size;
+		page = page_find(file->inode, offset);
+		if (!page)
+			break;
+		acquire(&quickPageLock);
+		oldPage = quick_page(page->frame);
+		memcpy(buf + count, QUICK_PAGE, min);
+		quick_page(oldPage);
+		release(&quickPageLock);
+		size -= min;
+		count += min;
+	}
+	return count;
+}
+
+/* Write a file */
+size_t
+tmpfs_write(File *file, char *buf, size_t size, off_t offset)
+{
+	if (size + offset > file->inode->size)
+		file->inode->size = size + offset;
+
+	uint16_t min;
+	size_t count = 0;
+	Page *page;
+	page_t oldPage;
+	while (size) {
+		min = (size > 0x1000) ? 0x1000 : size;
+		page = page_find(file->inode, offset);
+		if (!page)
+			page = page_create(file->inode, alloc_frames(1),
+		                           offset);
+		acquire(&quickPageLock);
+		oldPage = quick_page(page->frame);
+		memcpy(QUICK_PAGE, buf + count, min);
+		quick_page(oldPage);
+		release(&quickPageLock);
+		size -= min;
+		count += min;
+	}
+	return count;
+}
+
+/* Read a directory entry */
+int
+tmpfs_readdir(File *file, DirEnt *dent, off_t index)
+{
+	DirEntry *de;
+
+	if (!index--) {
+		dent->ino = file->inode->ino;
+		dent->type = DT_DIR;
+		dent->namelen = 2;
+		strncpy(dent->name, ".", dent->namelen);
+		return 0;
+	}
+
+	for (de = file->inode->dirEntries; de && index; de = de->next, index--);
+	if (!de)
+		return -ENOENT;
+	dent->ino = de->inode->ino;
+	if (S_ISBLK(de->inode->mode))
+		dent->type = DT_BLK;
+	if (S_ISCHR(de->inode->mode))
+		dent->type = DT_CHR;
+	if (S_ISDIR(de->inode->mode))
+		dent->type = DT_DIR;
+	if (S_ISFIFO(de->inode->mode))
+		dent->type = DT_FIFO;
+	if (S_ISLNK(de->inode->mode))
+		dent->type = DT_LNK;
+	if (S_ISREG(de->inode->mode))
+		dent->type = DT_REG;
+	if (S_ISSOCK(de->inode->mode))
+		dent->type = DT_SOCK;
+	dent->namelen = strnlen(de->name, NAME_MAX) + 1;
+	strncpy(dent->name, de->name, NAME_MAX);
+	return 0;
+}
+
+/* Open a file */
+int
+tmpfs_open(File *file)
+{
+	return 0;
+}
diff --git a/vfs/tmpfs/fs.h b/vfs/tmpfs/fs.h
new file mode 100644
index 0000000..104f64d
--- /dev/null
+++ b/vfs/tmpfs/fs.h
@@ -0,0 +1,13 @@
+#ifndef KERNEL_VFS_TMPFS_H
+#define KERNEL_VFS_TMPFS_H
+
+#include "../vfs.h"
+
+/* Operations */
+extern SuperOps tmpfsSuperOps;
+extern InodeOps tmpfsInodeOps;
+extern FileOps tmpfsFileOps;
+
+extern FileSystemType tmpfsType;
+
+#endif
diff --git a/vfs/tmpfs/inode.c b/vfs/tmpfs/inode.c
new file mode 100644
index 0000000..5da5408
--- /dev/null
+++ b/vfs/tmpfs/inode.c
@@ -0,0 +1,69 @@
+/*
+ * This file contains the functions dealing with TmpFS inodes.  The VFS will
+ * call these when it performs operations on TmpFS Inodes, or is dealing with
+ * the TmpFS hierarchy.
+ */
+
+#include <string.h>
+#include "fs.h"
+#include "../vfs.h"
+#include "../../mem/heap.h"
+
+int tmpfs_create(Inode *inode, DirEntry *entry, mode_t mode);
+Inode *tmpfs_lookup(Inode *inode, const char *name);
+int tmpfs_mkdir(Inode *inode, DirEntry *entry, mode_t mode);
+int tmpfs_rmdir(Inode *inode, DirEntry *entry);
+int tmpfs_mknod(Inode *inode, DirEntry *entry, mode_t mode, dev_t dev);
+int tmpfs_rename(Inode *si, DirEntry *sde, Inode *di, DirEntry *dde);
+
+InodeOps tmpfsInodeOps = {
+	.create = tmpfs_create,
+	.lookup = tmpfs_lookup,
+	.mkdir = tmpfs_mkdir,
+	.rmdir = tmpfs_rmdir,
+	.mknod = tmpfs_mknod,
+	.rename = tmpfs_rename,
+};
+
+/* Create a file */
+int
+tmpfs_create(Inode *inode, DirEntry *entry, mode_t mode)
+{
+	return 0;
+}
+
+
+/* Look up a file */
+Inode *
+tmpfs_lookup(Inode *inode, const char *name)
+{
+	return NULL;
+}
+
+/* Make a directory */
+int
+tmpfs_mkdir(Inode *inode, DirEntry *entry, mode_t mode)
+{
+	return 0;
+}
+
+/* Remove a directory */
+int
+tmpfs_rmdir(Inode *inode, DirEntry *entry)
+{
+	return 0;
+}
+
+/* Make a node */
+int
+tmpfs_mknod(Inode *inode, DirEntry *entry, mode_t mode, dev_t dev)
+{
+	return 0;
+}
+
+/* Rename/mode a directory entry */
+int
+tmpfs_rename(Inode *si, DirEntry *sde, Inode *di, DirEntry *dde)
+{
+	return 0;
+}
diff --git a/vfs/tmpfs/super.c b/vfs/tmpfs/super.c
new file mode 100644
index 0000000..ede1961
--- /dev/null
+++ b/vfs/tmpfs/super.c
@@ -0,0 +1,65 @@
+/*
+ * This file controls the superblock for TmpFS.  It supports mounting new TmpFS
+ * filesystems.  The VFS will use the calls here when dealing directly with the
+ * filesystem structure.
+ */
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include "fs.h"
+#include "../vfs.h"
+#include "../super.h"
+#include "../inode.h"
+#include "../../mem/heap.h"
+
+Inode *tmpfs_mount(FileSystemType *type, int flags,
+                   const char *dev, void *data);
+Inode *tmpfs_alloc_inode(SuperBlock *sb);
+
+FileSystemType tmpfsType = {
+	.name    = "TmpFS",
+	.mount   = tmpfs_mount,
+//	.kill_sb = tmpfs_kill_sb,
+};
+
+SuperOps tmpfsSuperOps = {
+	.alloc_inode  = tmpfs_alloc_inode,
+//	.free_inode   = tmpfs_free_inode,
+//	.write_inode  = tmpfs_write_inode,
+//	.delete_inode = tmpfs_delete_inode,
+};
+
+/* Mount a TmpFS instance */
+Inode *
+tmpfs_mount(FileSystemType *type, int flags, const char *dev, void *data)
+{
+	if (type != &tmpfsType)
+		return NULL;
+
+	SuperBlock *super = kmalloc(sizeof(SuperBlock));
+
+	super->type = type;
+	super->ops = &tmpfsSuperOps;
+	init_lock(&super->lock);
+
+	Inode *inode = super_alloc_inode(super);
+	inode->mode = S_IFDIR | 0755;
+	super->root = inode;
+
+	return inode;
+}
+
+/* Allocate an inode */
+Inode *
+tmpfs_alloc_inode(SuperBlock *sb)
+{
+	Inode *inode = kmalloc(sizeof(Inode));
+	init_lock(&inode->lock);
+	inode->ops = &tmpfsInodeOps;
+	inode->fileOps = &tmpfsFileOps;
+	inode->super = sb;
+	inode->nlink = 1;
+	return inode_get(inode); /* This ensures that the inode is never free */
+}
diff --git a/vfs/vfs.c b/vfs/vfs.c
new file mode 100644
index 0000000..68f4b34
--- /dev/null
+++ b/vfs/vfs.c
@@ -0,0 +1,680 @@
+/*
+ * This file controls the Virtual File System for the Kernel.  It implements a
+ * generic File System, which can hook other File Systems.  This forms a key
+ * part of the Kernel, and is used for loading programs and some higher-level
+ * IPC.  This system will dispatch messages to the actual File System tasks in
+ * user-space.
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include "tmpfs/fs.h"
+#include "devfs/fs.h"
+#include "procfs/fs.h"
+#include "ext2fs/fs.h"
+#include "vfs.h"
+#include "cache.h"
+#include "inode.h"
+#include "../mem/heap.h"
+#include "../task/task.h"
+
+FileSystemType *fsTypes;
+
+/* Register a File System Type */
+void
+register_fstype(FileSystemType *fstype)
+{
+	if (!fsTypes) {
+		fsTypes = fstype;
+		return;
+	}
+	FileSystemType *prev;
+	for (prev = fsTypes; prev->next; prev = prev->next);
+	prev->next = fstype;
+}
+
+/* Initialise the Virtual File System */
+void
+init_vfs(void)
+{
+	register_fstype(&tmpfsType);
+	register_fstype(&devfsType);
+	register_fstype(&procfsType);
+	register_fstype(&ext2fsType);
+
+	mount("tmpfs", NULL, "TmpFS", 0, NULL);
+	mkdir("dev", 0);
+	mount("devfs", "/dev", "DevFS", 0, NULL);
+	mkdir("proc", 0);
+	mount("procfs", "/proc", "ProcFS", 0, NULL);
+}
+
+/* Check if a process has permission for a file */
+int
+permission(Inode *inode, int mask)
+{
+	if (!inode)
+		return 0;
+
+	int mode = inode->mode;
+
+	if (current->euid == inode->uid)
+		mode >>= 6;
+	else if (current->egid == inode->gid)
+		mode >>= 3;
+
+	if (((mode & mask & 0007) == mask) || super_user())
+		return 1;
+	return 0;
+}
+
+/* Lookup a file path */
+Inode *
+lookup(const char *path, CustodyChain *newchain)
+{
+	if (!current->fs->root)
+		return NULL;
+
+	char buf[PATH_MAX], *p = buf;
+	strncpy(buf, path, PATH_MAX);
+	Inode *ino;
+	DirEntry *de = NULL;
+	CustodyChain *chain = create_custody_chain();
+
+	/* Resolve to absolute/relative root */
+	if (*p == '/') {
+		ino = current->fs->root;
+		while (*++p == '/');
+	} else {
+		ino = current->fs->cwd;
+		copy_custody_chain(&current->fs->cwdCustody, chain);
+	}
+
+	/* Drop through directories */
+	char *curr, *prev = p;
+	for (curr = p; *curr; curr++) {
+		if (*curr != '/')
+			continue;
+		*curr = '\0';
+		if (!strcmp(prev, ".")) {
+			de = NULL;
+		} else if (!strcmp(prev, "..")) {
+			de = NULL;
+			if (ino != current->fs->root) {
+				de = remove_custody(chain);
+				if (!de)
+					ino = current->fs->root;
+			}
+		} else {
+			de = inode_lookup(ino, prev);
+			if (!de) {
+				ino = NULL;
+				goto end;
+			}
+			add_custody(chain, de);
+			entry_put(de);
+		}
+		if (de) {
+			ino = de->inode;
+			if (de->mnt)
+				ino = de->mnt;
+		}
+		while (*++curr == '/');
+		prev = curr;
+	}
+	/* Basename */
+	if (!strcmp(prev, ".") || !strcmp(prev, "")) {
+		if (!S_ISDIR(ino->mode)) {
+			ino = NULL;
+			de = NULL;
+		}
+	} else if (!strcmp(prev, "..")) {
+		if (ino != current->fs->root) {
+			de = remove_custody(chain);
+			if (!de)
+				ino = current->fs->root;
+		}
+	} else {
+		de = inode_lookup(ino, prev);
+		if (!de) {
+			de = kmalloc(sizeof(DirEntry));
+			init_lock(&de->lock);
+			strncpy(de->name, prev, NAME_MAX);
+			de->super = ino->super;
+			de->usage = 1;
+		}
+		add_custody(chain, de);
+		entry_put(de);
+	}
+	if (de) {
+		ino = de->inode;
+		if (de->mnt)
+			ino = de->mnt;
+		if (ino)
+			inode_get(ino);
+	}
+
+	/* Copy chain */
+	if (newchain)
+		copy_custody_chain(chain, newchain);
+end:
+	destroy_custody_chain(chain);
+	return ino;
+}
+
+/* Mount a file system */
+int
+mount(const char *src, const char *target, const char *type,
+      unsigned long flags, void *data)
+{
+	if (!verify_access(src, strnlen(src, PATH_MAX), PROT_READ))
+		return -EFAULT;
+	if (!verify_access(target, strnlen(target, PATH_MAX), PROT_READ))
+		return -EFAULT;
+	if (!verify_access(type, strnlen(type, 16), PROT_READ))
+		return -EFAULT;
+
+	FileSystemType *fsType;
+	for (fsType = fsTypes; fsType; fsType = fsType->next)
+		if (!strcmp(fsType->name, type))
+			break;
+	if (!fsType)
+		return -ENODEV;
+
+	Inode *mnt = fsType->mount(fsType, flags, src, data);
+	if ((int) mnt < 0)
+		return (int) mnt;
+	if (!target && !current->fs->root) {
+		current->fs->root = inode_get(mnt);
+		current->fs->cwd = inode_get(mnt);
+		return 0;
+	}
+	/* Find target's DirEntry */
+	CustodyChain tmp;
+	init_custody_chain(&tmp);
+	Inode *root = lookup(target, &tmp);
+	if (!root) {
+		clean_custody_chain(&tmp);
+		return -ENOENT;
+	}
+	if (!S_ISDIR(root->mode)) {
+		inode_put(root);
+		clean_custody_chain(&tmp);
+		return -ENOTDIR;
+	}
+	DirEntry *de = entry_get(tmp.end->entry);
+	de->mnt = inode_get(mnt);
+	inode_put(root);
+	clean_custody_chain(&tmp);
+	return 0;
+}
+
+/* Open a file */
+int
+open(const char *name, int flags, ...)
+{
+	if (!verify_access(name, strnlen(name, PATH_MAX), PROT_READ))
+		return -EFAULT;
+
+	/* Allocate file descriptor */
+	int fd;
+	for (fd = 0; fd < NFILES; fd++)
+		if (!current->files->fd[fd])
+			break;
+	if (fd == NFILES)
+		return -EMFILE;
+
+	/* Find inode */
+	CustodyChain chain;
+	init_custody_chain(&chain);
+	Inode *ino = lookup(name, &chain);
+	va_list args;
+	va_start(args, flags);
+	if (!ino) {
+		if (!(flags & O_CREATE)) {
+			clean_custody_chain(&chain);
+			return -ENOENT;
+		}
+		/* Create file */
+		if (!chain.size) {
+			clean_custody_chain(&chain);
+			return -ENOENT;
+		}
+		if (chain.end->prev->entry) {
+			ino = chain.end->prev->entry->inode;
+			if (chain.end->prev->entry->mnt)
+				ino = chain.end->prev->entry->mnt;
+		} else {
+			ino = current->fs->root;
+		}
+		if (!permission(ino, MAY_WRITE)) {
+			clean_custody_chain(&chain);
+			return -EACCES;
+		}
+		inode_create(ino, chain.end->entry, va_arg(args, mode_t));
+		ino = lookup(name, NULL);
+	}
+	va_end(args);
+
+	/* Check permissions */
+	int perm = 0;
+	if ((flags & O_ACCMODE) == O_RDONLY)
+		perm = MAY_READ;
+	else if ((flags & O_ACCMODE) == O_WRONLY)
+		perm = MAY_WRITE;
+	else if ((flags & O_ACCMODE) == O_RDWR)
+		perm = MAY_READ | MAY_WRITE;
+	if (!permission(ino, perm)) {
+		inode_put(ino);
+		clean_custody_chain(&chain);
+		return -EACCES;
+	}
+
+	/* Open file */
+	File *file = kmalloc(sizeof(File));
+	file->inode = inode_get(ino);
+	file->uid = ino->uid;
+	file->gid = ino->gid;
+	file->flags = flags;
+	file->mode = ino->mode;
+	file->pos = 0;
+	init_lock(&file->lock);
+	file->ops = ino->fileOps;
+	file->path = create_custody_chain();
+	file->usage = 1;
+
+	copy_custody_chain(&chain, file->path);
+	clean_custody_chain(&chain);
+
+	int err = file_open(file);
+	if (err) {
+		file_put(file);
+		inode_put(ino);
+		return err;
+	}
+
+	current->files->fd[fd] = file;
+	inode_put(ino);
+	return fd;
+}
+
+/* Close a file */
+int
+close(int fd)
+{
+	if (fd < 0 || fd >= NFILES)
+		return -EBADF;
+	File *file = current->files->fd[fd];
+	current->files->fd[fd] = NULL;
+	if (!file)
+		return -EBADF;
+
+	file_put(file);
+	return 0;
+}
+
+/* Read a file */
+size_t
+read(int fd, void *buf, size_t count)
+{
+	if (!verify_access(buf, count, PROT_WRITE))
+		return -EFAULT;
+	if (fd < 0 || fd >= NFILES)
+		return -EBADF;
+	File *file = current->files->fd[fd];
+	if (!file)
+		return -EBADF;
+
+	if (S_ISDIR(file->mode))
+		return -EISDIR;
+
+	return file_read(file, buf, count);
+}
+
+/* Write a file */
+size_t
+write(int fd, void *buf, size_t count)
+{
+	if (!verify_access(buf, count, PROT_READ))
+		return -EFAULT;
+	if (fd < 0 || fd >= NFILES)
+		return -EBADF;
+	File *file = current->files->fd[fd];
+	if (!file)
+		return -EBADF;
+
+	if (S_ISDIR(file->mode))
+		return -EISDIR;
+
+	return file_write(file, buf, count);
+}
+
+/* I/O Control */
+int
+ioctl(int fd, unsigned long request, ...)
+{
+	if (fd < 0 || fd >= NFILES)
+		return -EBADF;
+	File *file = current->files->fd[fd];
+	if (!file)
+		return -EBADF;
+	va_list args;
+	va_start(args, request);
+	uintptr_t argp = va_arg(args, uintptr_t);
+	va_end(args);
+	return file_ioctl(file, request, argp);
+}
+
+/* Move a file's position */
+off_t
+lseek(int fd, off_t offset, int whence)
+{
+	if (fd < 0 || fd >= NFILES)
+		return -EBADF;
+	File *file = current->files->fd[fd];
+	if (!file)
+		return -EBADF;
+	acquire(&file->lock);
+
+	switch (whence) {
+	case SEEK_SET:
+		file->pos = offset;
+		break;
+	case SEEK_CUR:
+		file->pos += offset;
+		break;
+	case SEEK_END:
+		file->pos = file->inode->size + offset;
+		break;
+	default:
+		release(&file->lock);
+		return -EINVAL;
+	}
+	release(&file->lock);
+	return file->pos;
+}
+
+/* Get file status */
+int
+stat(const char *pathname, struct stat *statbuf)
+{
+	if (!verify_access(pathname, strnlen(pathname, PATH_MAX), PROT_READ))
+		return -EFAULT;
+	if (!verify_access(statbuf, sizeof(struct stat), PROT_WRITE))
+		return -EFAULT;
+	Inode *inode = lookup(pathname, NULL);
+	if (!inode)
+		return -ENOENT;
+	if (statbuf) {
+		statbuf->inode = inode->ino;
+		statbuf->mode = inode->mode;
+		statbuf->nlink = inode->nlink;
+		statbuf->uid = inode->uid;
+		statbuf->gid = inode->gid;
+		statbuf->size = inode->size;
+	}
+	inode_put(inode);
+	return 0;
+}
+
+/* Get file status by file descriptor */
+int
+fstat(int fd, struct stat *statbuf)
+{
+	if (!verify_access(statbuf, sizeof(struct stat), PROT_WRITE))
+		return -EFAULT;
+	if (fd < 0 || fd >= NFILES)
+		return -EBADF;
+	File *file = current->files->fd[fd];
+	if (!file)
+		return -EBADF;
+	Inode *inode = file->inode;
+	statbuf->inode = inode->ino;
+	statbuf->mode = inode->mode;
+	statbuf->size = inode->size;
+	return 0;
+}
+
+/* Get directory entries */
+size_t
+getdents(int fd, void *buf, size_t count)
+{
+	if (!verify_access(buf, count, PROT_WRITE))
+		return -EFAULT;
+	if (count < sizeof(DirEnt))
+		return -EINVAL;
+
+	int err;
+	size_t i, size = 0;
+	DirEnt *dent = buf;
+	File *file = current->files->fd[fd];
+	if (!file)
+		return -EBADF;
+	if (!S_ISDIR(file->inode->mode))
+		return -ENOTDIR;
+
+	/* Read as many entries as possible */
+	for (i = 0; i < count / sizeof(DirEnt); i++) {
+		err = file_readdir(file, dent, i);
+		if (err < 0)
+			goto out;
+		size += sizeof(DirEnt) + dent->namelen;
+		*((char *) buf + size - 1) = '\0';
+		dent = (void *) ((char *) buf + size);
+	}
+out:
+	if (!i)
+		return err;
+	return size;
+}
+
+/* Make a directory */
+int
+mkdir(const char *pathname, mode_t mode)
+{
+	if (!verify_access(pathname, strnlen(pathname, PATH_MAX), PROT_READ))
+		return -EFAULT;
+	int err;
+	CustodyChain chain;
+	init_custody_chain(&chain);
+	Inode *inode = lookup(pathname, &chain);
+	if (inode) {
+		err = -EEXIST;
+		goto end;
+	}
+	if (!chain.size) {
+		err = -ENOENT;
+		goto end;
+	}
+	/* Check write permission */
+	if (chain.end->prev->entry) {
+		inode = chain.end->prev->entry->inode;
+		if (chain.end->prev->entry->mnt)
+			inode = chain.end->prev->entry->mnt;
+	} else {
+		inode = current->fs->root;
+	}
+	if (!permission(inode, MAY_WRITE)) {
+		err = -EACCES;
+		goto end;
+	}
+
+	/* Create directory */
+	err = inode_mkdir(inode, entry_get(chain.end->entry), mode);
+end:
+	clean_custody_chain(&chain);
+//	if (inode)
+//		inode_put(inode);
+	return err;
+}
+
+/* Remove a directory */
+int
+rmdir(const char *pathname)
+{
+	return 0;
+}
+
+/* Make a special node */
+int
+mknod(const char *pathname, mode_t mode, dev_t dev)
+{
+	if (!verify_access(pathname, strnlen(pathname, PATH_MAX), PROT_READ))
+		return -EFAULT;
+	int err;
+	CustodyChain chain;
+	init_custody_chain(&chain);
+	Inode *inode = lookup(pathname, &chain);
+	if (inode) {
+		err = -EEXIST;
+		goto end;
+	}
+	if (!chain.size) {
+		err = -ENOENT;
+		goto end;
+	}
+	/* Check write permission */
+	if (chain.end->prev->entry) {
+		inode = chain.end->prev->entry->inode;
+		if (chain.end->prev->entry->mnt)
+			inode = chain.end->prev->entry->mnt;
+	} else {
+		inode = current->fs->root;
+	}
+	if (!permission(inode, MAY_WRITE)) {
+		err = -EACCES;
+		goto end;
+	}
+
+	/* Create node */
+	err = inode_mknod(inode, entry_get(chain.end->entry), mode, dev);
+end:
+	clean_custody_chain(&chain);
+//	if (inode)
+//		inode_put(inode);
+	return err;
+}
+
+/* Rename/move a file */
+int
+rename(const char *oldpath, const char *newpath)
+{
+	return 0;
+}
+
+/* Duplicate a file descriptor */
+int
+dup(int oldfd)
+{
+	/* Check file descriptor */
+	if (oldfd < 0 || oldfd >= NFILES)
+		return -EBADF;
+	if (!current->files->fd[oldfd])
+		return -EBADF;
+
+	/* Allocate file descriptor */
+	int fd;
+	for (fd = 0; fd < NFILES; fd++)
+		if (!current->files->fd[fd])
+			break;
+	if (fd == NFILES)
+		return -EMFILE;
+	dup2(oldfd, fd);
+}
+
+/* Duplicate a file descriptor to a new file descriptor */
+int
+dup2(int oldfd, int newfd)
+{
+	/* Check file descriptors */
+	if (oldfd < 0 || oldfd >= NFILES)
+		return -EBADF;
+	if (!current->files->fd[oldfd])
+		return -EBADF;
+	if (newfd < 0 || newfd >= NFILES)
+		return -EBADF;
+
+	if (newfd == oldfd)
+		return newfd;
+
+	/* Close old file in newfd */
+	if (current->files->fd[newfd])
+		close(newfd);
+	current->files->fd[newfd] = file_get(current->files->fd[oldfd]);
+	return newfd;
+}
+
+/* Check if a file descriptor refers to a terminal */
+int
+isatty(int fd)
+{
+	if (fd < 0 || fd >= NFILES)
+		return -EBADF;
+	File *file = current->files->fd[fd];
+	if (!file)
+		return -EBADF;
+	if (S_ISCHR(file->mode))
+		return 1;
+	return 0;
+}
+
+/* Change the current working directory */
+int
+chdir(const char *path)
+{
+	if (!verify_access(path, strnlen(path, PATH_MAX), PROT_READ))
+		return -EFAULT;
+	CustodyChain tmp;
+	init_custody_chain(&tmp);
+	Inode *node = lookup(path, &tmp);
+	if (!node)
+		return -ENOENT;
+	if (!S_ISDIR(node->mode))
+		return -ENOTDIR;
+	if (!permission(node, MAY_READ))
+		return -EACCES;
+	inode_put(current->fs->cwd);
+	current->fs->cwd = inode_get(node);
+	while (current->fs->cwdCustody.size)
+		remove_custody(&current->fs->cwdCustody);
+	copy_custody_chain(&tmp, &current->fs->cwdCustody);
+	clean_custody_chain(&tmp);
+//	inode_put(node);
+	return 0;
+}
+
+/* Change the current root */
+int
+chroot(const char *path)
+{
+	if (!verify_access(path, strnlen(path, PATH_MAX), PROT_READ))
+		return -EFAULT;
+	Inode *node = lookup(path, NULL);
+	if (!node)
+		return -ENOENT;
+	if (!S_ISDIR(node->mode))
+		return -ENOTDIR;
+	if (!permission(node, MAY_READ))
+		return -EACCES;
+	inode_put(current->fs->root);
+	current->fs->root = inode_get(node);
+//	inode_put(node);
+	return 0;
+}
+
+/* Get the current working directory's path */
+char *
+getcwd(char *buf, size_t size)
+{
+	if (!verify_access(buf, size, PROT_WRITE))
+		return (char *) -EFAULT;
+	return custody_path(&current->fs->cwdCustody, buf, size);
+}
diff --git a/vfs/vfs.h b/vfs/vfs.h
new file mode 100644
index 0000000..ece5679
--- /dev/null
+++ b/vfs/vfs.h
@@ -0,0 +1,202 @@
+#ifndef KERNEL_VFS_H
+#define KERNEL_VFS_H
+
+#include <stdint.h>
+#include <stddef.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include "../spinlock.h"
+#include "../mem/paging.h"
+
+#define NFILES 32
+#define NAME_MAX 255
+#define PATH_MAX 1024
+
+#define MAY_READ 4
+#define MAY_WRITE 2
+#define MAY_EXECUTE 1
+
+typedef struct FileSystemType FileSystemType;
+typedef struct SuperBlock SuperBlock;
+typedef struct SuperOps SuperOps;
+typedef struct Inode Inode;
+typedef struct InodeOps InodeOps;
+typedef struct File File;
+typedef struct FileOps FileOps;
+typedef struct DirEntry DirEntry;
+typedef struct Page Page;
+typedef struct PageCache PageCache;
+typedef struct Custody Custody;
+typedef struct CustodyChain CustodyChain;
+
+/* Structure for a File System Type */
+struct FileSystemType {
+	const char *name;
+	Inode *(*mount)(FileSystemType *, int, const char *, void *);
+	void (*kill_sb)(SuperBlock *);
+	pid_t owner;
+	FileSystemType *next;
+};
+
+/* Structure for a Super Block */
+struct SuperBlock {
+	FileSystemType *type;
+	SuperOps *ops;
+	Spinlock lock;
+	Inode *root;
+	Inode *inodes; /* List of cached inodes */
+	size_t cachedInodes;
+	File *back;
+	void *data;
+};
+struct SuperOps {
+	Inode *(*alloc_inode)(SuperBlock *);
+	int (*write_inode)(Inode *);
+	void (*delete_inode)(Inode *);
+};
+
+/* Structure for an Inode */
+struct Inode {
+	ino_t ino;
+	refcount_t usage;
+	unsigned short uid, gid;
+	mode_t mode;
+	nlink_t nlink;
+	uint32_t flags;
+	size_t size;
+	dev_t dev;
+	Spinlock lock;
+	InodeOps *ops;
+	FileOps *fileOps;
+	SuperBlock *super;
+	union {
+		DirEntry *dirEntries; /* List of Directory Entries */
+		PageCache *pages; /* List of Pages */
+	};
+	Inode *lnext; /* Next inode in super list */
+};
+struct InodeOps {
+	int (*create)(Inode *, DirEntry *, mode_t);
+	Inode *(*lookup)(Inode *, const char *);
+	int (*mkdir)(Inode *, DirEntry *, mode_t);
+	int (*rmdir)(Inode *, DirEntry *);
+	int (*mknod)(Inode *, DirEntry *, mode_t, dev_t);
+	int (*rename) (Inode *, DirEntry *, Inode *, DirEntry *);
+};
+
+/* Structure for an open File */
+struct File {
+	Inode *inode;
+	unsigned short uid, gid;
+	int flags;
+	mode_t mode;
+	off_t pos;
+	Spinlock lock;
+	FileOps *ops;
+	CustodyChain *path;
+	refcount_t usage;
+};
+struct FileOps {
+	off_t (*lseek)(File *, off_t, int);
+	size_t (*read)(File *, char *, size_t, off_t);
+	size_t (*write)(File *, char *, size_t, off_t);
+	int (*ioctl)(File *, unsigned long, uintptr_t);
+	int (*readdir)(File *, DirEnt *, off_t);
+	int (*open)(File *);
+	int (*flush)(File *);
+	int (*release)(File *);
+	void (*mmap)(File *, void *, size_t, off_t);
+};
+
+/* Structure for a Directory Entry */
+struct DirEntry {
+	DirEntry *next;
+	Inode *inode, *mnt;
+	uint32_t hash;
+	char name[NAME_MAX];
+	Spinlock lock;
+	SuperBlock *super;
+	refcount_t usage;
+	DirEntry *lnext; /* Next directory entry in global list */
+};
+
+/* Structure for a Page in a block cache */
+struct Page {
+	page_t frame;
+	off_t offset;
+	refcount_t usage;
+};
+struct PageCache {
+        Page *page;
+        PageCache *next;
+};
+
+/* Custody Chains */
+struct Custody {
+	Custody *prev, *next;
+	CustodyChain *chain;
+	DirEntry *entry;
+};
+struct CustodyChain {
+	Custody *start, *end;
+	size_t size;
+	Spinlock lock;
+};
+
+/* File System Namespace */
+typedef struct FileSystem {
+	Inode *cwd;
+	CustodyChain cwdCustody;
+	Inode *root;
+	refcount_t usage;
+} FileSystem;
+
+/* Files Namespace */
+typedef struct Files {
+	File *fd[NFILES];
+	refcount_t usage;
+} Files;
+
+void init_vfs(void);
+void register_fstype(FileSystemType *fstype);
+Inode *lookup(const char *name, CustodyChain *chain);
+
+int open(const char *name, int flags, ...); /* mode_t mode */
+int close(int fd);
+size_t read(int fd, void *buf, size_t count);
+size_t write(int fd, void *buf, size_t count);
+int ioctl(int fd, unsigned long request, ...);
+off_t lseek(int fd, off_t offset, int whence);
+int stat(const char *pathname, struct stat *statbuf);
+size_t getdents(int fd, void *buf, size_t count);
+int mkdir(const char *pathname, mode_t mode);
+int rmdir(const char *pathname);
+int mknod(const char *pathname, mode_t mode, dev_t dev);
+int rename(const char *oldpath, const char *newpath);
+int dup(int oldfd);
+int dup2(int oldfd, int newfd);
+int mount(const char *src, const char *target, const char *type,
+          unsigned long flags, void *data);
+int chdir(const char *path);
+int chroot(const char *path);
+char *getcwd(char *buf, size_t size);
+
+void init_custody_chain(CustodyChain *chain);
+CustodyChain *create_custody_chain(void);
+void clean_custody_chain(CustodyChain *chain);
+void destroy_custody_chain(CustodyChain *chain);
+void copy_custody_chain(CustodyChain *chain, CustodyChain *newchain);
+void add_custody(CustodyChain *chain, DirEntry *entry);
+DirEntry *remove_custody(CustodyChain *chain);
+char *custody_path(CustodyChain *chain, char *buf, size_t size);
+
+File *file_get(File *file);
+void file_put(File *file);
+size_t file_read(File *file, char *buf, size_t size);
+size_t file_write(File *file, char *buf, size_t size);
+int file_ioctl(File *file, unsigned long reqeust, uintptr_t argp);
+int file_readdir(File *file, DirEnt *dent, off_t index);
+int file_open(File *file);
+void file_mmap(File *file, void *buf, size_t len, off_t offset);
+
+#endif