BarryServer : Git

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

// Related

Orion

Barry Importing existing Orion kernel d41a53c (2 years, 4 months ago)
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;
+	}
+}