Browse Source

Merge kernel

K. Lange 3 years ago
parent
commit
3f4293d357
100 changed files with 23586 additions and 5 deletions
  1. 76 5
      Makefile
  2. 3 0
      kernel/.gdb_history
  3. 60 0
      kernel/boot.S
  4. 102 0
      kernel/cpu/gdt.c
  5. 53 0
      kernel/cpu/idt.c
  6. 164 0
      kernel/cpu/irq.c
  7. 94 0
      kernel/cpu/isr.c
  8. 194 0
      kernel/devices/cmos.c
  9. 142 0
      kernel/devices/fpu.c
  10. 113 0
      kernel/devices/pci.c
  11. 90 0
      kernel/devices/timer.c
  12. 64 0
      kernel/ds/bitset.c
  13. 217 0
      kernel/ds/hashmap.c
  14. 248 0
      kernel/ds/list.c
  15. 170 0
      kernel/ds/ringbuffer.c
  16. 192 0
      kernel/ds/tree.c
  17. 296 0
      kernel/fs/pipe.c
  18. 110 0
      kernel/fs/ramdisk.c
  19. 435 0
      kernel/fs/tty.c
  20. 150 0
      kernel/fs/unixpipe.c
  21. 1044 0
      kernel/fs/vfs.c
  22. 20 0
      kernel/gdt.S
  23. 10 0
      kernel/idt.S
  24. 9 0
      kernel/include/args.h
  25. 141 0
      kernel/include/ata.h
  26. 19 0
      kernel/include/bitset.h
  27. 20 0
      kernel/include/boot.h
  28. 185 0
      kernel/include/elf.h
  29. 1 0
      kernel/include/errno_defs.h
  30. 174 0
      kernel/include/ext2.h
  31. 170 0
      kernel/include/fs.h
  32. 47 0
      kernel/include/hashmap.h
  33. 1 0
      kernel/include/ioctl.h
  34. 188 0
      kernel/include/ipv4.h
  35. 37 0
      kernel/include/libc.h
  36. 50 0
      kernel/include/list.h
  37. 27 0
      kernel/include/logging.h
  38. 16 0
      kernel/include/mem.h
  39. 32 0
      kernel/include/mod/net.h
  40. 4 0
      kernel/include/mod/rtl.h
  41. 34 0
      kernel/include/mod/shell.h
  42. 52 0
      kernel/include/mod/snd.h
  43. 36 0
      kernel/include/mod/sound.h
  44. 38 0
      kernel/include/mod/tmpfs.h
  45. 41 0
      kernel/include/module.h
  46. 20 0
      kernel/include/mouse.h
  47. 92 0
      kernel/include/multiboot.h
  48. 69 0
      kernel/include/pci.h
  49. 7057 0
      kernel/include/pci_list.h
  50. 27 0
      kernel/include/pipe.h
  51. 8 0
      kernel/include/printf.h
  52. 156 0
      kernel/include/process.h
  53. 26 0
      kernel/include/ringbuffer.h
  54. 42 0
      kernel/include/shm.h
  55. 11 0
      kernel/include/signal.h
  56. 1 0
      kernel/include/signal_defs.h
  57. 1 0
      kernel/include/syscall_nums.h
  58. 234 0
      kernel/include/system.h
  59. 25 0
      kernel/include/task.h
  60. 1 0
      kernel/include/termemu.h
  61. 1 0
      kernel/include/termios.h
  62. 4 0
      kernel/include/tokenize.h
  63. 37 0
      kernel/include/tree.h
  64. 37 0
      kernel/include/tss.h
  65. 18 0
      kernel/include/types.h
  66. 68 0
      kernel/include/ubsan.h
  67. 1 0
      kernel/include/utsname.h
  68. 8 0
      kernel/include/va_list.h
  69. 19 0
      kernel/include/version.h
  70. 18 0
      kernel/include/video.h
  71. 67 0
      kernel/irq.S
  72. 91 0
      kernel/isr.S
  73. 454 0
      kernel/libc.c
  74. 49 0
      kernel/link.ld
  75. 255 0
      kernel/main.c
  76. 969 0
      kernel/mem/alloc.c
  77. 564 0
      kernel/mem/mem.c
  78. 369 0
      kernel/mem/shm.c
  79. 91 0
      kernel/misc/args.c
  80. 322 0
      kernel/misc/elf.c
  81. 158 0
      kernel/misc/kprintf.c
  82. 53 0
      kernel/misc/logging.c
  83. 76 0
      kernel/misc/multiboot.c
  84. 23 0
      kernel/misc/tokenize.c
  85. 76 0
      kernel/misc/ubsan.c
  86. 57 0
      kernel/spin.c
  87. 2791 0
      kernel/symbols.S
  88. 390 0
      kernel/sys/module.c
  89. 103 0
      kernel/sys/panic.c
  90. 963 0
      kernel/sys/process.c
  91. 227 0
      kernel/sys/signal.c
  92. 975 0
      kernel/sys/syscall.c
  93. 88 0
      kernel/sys/system.c
  94. 532 0
      kernel/sys/task.c
  95. 56 0
      kernel/sys/version.c
  96. 64 0
      kernel/task.S
  97. 10 0
      kernel/tss.S
  98. 61 0
      kernel/user.S
  99. 302 0
      modules/ac97.c
  100. 0 0
      modules/ata.c

+ 76 - 5
Makefile

@@ -1,9 +1,10 @@
 APPS=init hello sh ls terminal uname compositor drawlines background session kdebug cat yutani-test sysinfo hostname yutani-query env mount date echo nyancat kill ps pstree bim terminal-vga cursor-off font-server migrate free uptime
 
-KERNEL_TARGET=i686-elf
+KERNEL_TARGET=i686-pc-toaru
 KCC = $(KERNEL_TARGET)-gcc
 KAS = $(KERNEL_TARGET)-as
 KLD = $(KERNEL_TARGET)-ld
+KNM = $(KERNEL_TARGET)-nm
 
 CC=i686-pc-toaru-gcc
 AR=i686-pc-toaru-ar
@@ -16,6 +17,54 @@ APPS_X=$(foreach app,$(APPS),base/bin/$(app))
 
 all: image.iso
 
+# Kernel
+
+KCFLAGS  = -O2 -std=c99
+KCFLAGS += -finline-functions -ffreestanding
+KCFLAGS += -Wall -Wextra -Wno-unused-function -Wno-unused-parameter -Wno-format
+KCFLAGS += -pedantic -fno-omit-frame-pointer
+KCFLAGS += -D_KERNEL_
+KCFLAGS += -DKERNEL_GIT_TAG=$(shell util/make-version)
+KASFLAGS = --32
+
+KERNEL_OBJS = $(patsubst %.c,%.o,$(wildcard kernel/*.c))
+KERNEL_OBJS += $(patsubst %.c,%.o,$(wildcard kernel/*/*.c))
+KERNEL_OBJS += $(patsubst %.c,%.o,$(wildcard kernel/*/*/*.c))
+
+KERNEL_ASMOBJS = $(filter-out kernel/symbols.o,$(patsubst %.S,%.o,$(wildcard kernel/*.S)))
+
+cdrom/kernel: ${KERNEL_ASMOBJS} ${KERNEL_OBJS} kernel/symbols.o
+	${KCC} -T kernel/link.ld ${KCFLAGS} -nostdlib -o $@ ${KERNEL_ASMOBJS} ${KERNEL_OBJS} kernel/symbols.o -lgcc
+
+kernel/symbols.o: ${KERNEL_ASMOBJS} ${KERNEL_OBJS} util/generate_symbols.py
+	-rm -f kernel/symbols.o
+	${KCC} -T kernel/link.ld ${KCFLAGS} -nostdlib -o .toaruos-kernel ${KERNEL_ASMOBJS} ${KERNEL_OBJS} -lgcc
+	${KNM} .toaruos-kernel -g | util/generate_symbols.py > kernel/symbols.S
+	${KAS} ${KASFLAGS} kernel/symbols.S -o $@
+	-rm -f .toaruos-kernel
+
+kernel/sys/version.o: kernel/*/*.c kernel/*.c
+
+cdrom/mod:
+	@mkdir -p $@
+
+MODULES = $(patsubst modules/%.c,cdrom/mod/%.ko,$(wildcard modules/*.c))
+
+HEADERS = $(shell find kernel/include/ -type f -name '*.h')
+
+cdrom/mod/%.ko: modules/%.c ${HEADERS} | cdrom/mod
+	${KCC} -T modules/link.ld -I./kernel/include -nostdlib ${KCFLAGS} -c -o $@ $<
+
+modules: ${MODULES}
+
+kernel/%.o: kernel/%.S
+	${KAS} ${ASFLAGS} $< -o $@
+
+kernel/%.o: kernel/%.c ${HEADERS}
+	${KCC} ${KCFLAGS} -nostdlib -g -I./kernel/include -c -o $@ $<
+
+# Root Filesystem
+
 base/dev:
 	mkdir -p base/dev
 base/tmp:
@@ -30,18 +79,24 @@ cdrom/boot:
 	mkdir -p cdrom/boot
 dirs: base/dev base/tmp base/proc base/bin base/lib cdrom/boot
 
+# C Library
+
 libc/%.o: libc/%.c
 	$(CC) -fPIC -c -m32 -Wa,--32 -O3 -isystem include -o $@ $<
 
-base/lib/ld.so: linker/linker.c base/lib/libnihc.a | dirs
-	$(CC) -static -Wl,-static $(CFLAGS) -o $@ -Os -T linker/link.ld $< $(LIBS)
-
 base/lib/libnihc.a: ${LIBC_OBJS} | dirs
 	$(AR) cr $@ $^
 
 base/lib/libnihc.so: ${LIBC_OBJS} | dirs
 	$(CC) -o $@ $(CFLAGS) -shared -fPIC $^
 
+# Userspace Linker/Loader
+
+base/lib/ld.so: linker/linker.c base/lib/libnihc.a | dirs
+	$(CC) -static -Wl,-static $(CFLAGS) -o $@ -Os -T linker/link.ld $< $(LIBS)
+
+# Shared Libraries
+
 base/lib/libtoaru_graphics.so: lib/graphics.c lib/graphics.h
 	$(CC) -o $@ $(CFLAGS) -shared -fPIC $<
 
@@ -81,12 +136,18 @@ base/lib/libtoaru_drawstring.so: lib/drawstring.c lib/drawstring.h base/lib/libt
 base/lib/libtoaru_decorations.so: lib/decorations.c lib/decorations.h base/lib/libtoaru_graphics.so
 	$(CC) -o $@ $(CFLAGS) -shared -fPIC $< -ltoaru_graphics
 
+# Decoration Themes
+
 base/lib/libtoaru-decor-fancy.so: decors/decor-fancy.c lib/decorations.h base/lib/libtoaru_graphics.so base/lib/libtoaru_decorations.so base/lib/libtoaru_drawstring.so
 	$(CC) -o $@ $(CFLAGS) -shared -fPIC $< -ltoaru_decorations -ltoaru_drawstring -ltoaru_graphics
 
+# Init
+
 base/bin/init: apps/init.c base/lib/libnihc.a | dirs
 	$(CC) -static -Wl,-static $(CFLAGS) -o $@ $< $(LIBS)
 
+# Userspace
+
 base/bin/sh: apps/sh.c base/lib/libnihc.so base/lib/libtoaru_list.so base/lib/libtoaru_rline.so
 	$(CC) $(CFLAGS) -o $@ $< -ltoaru_rline -ltoaru_list -ltoaru_kbd $(LIBS)
 
@@ -132,12 +193,18 @@ base/bin/pstree: apps/pstree.c base/lib/libnihc.so base/lib/libtoaru_tree.so bas
 base/bin/%: apps/%.c base/lib/libnihc.so | dirs
 	$(CC) $(CFLAGS) -o $@ $< $(LIBS)
 
+# Ramdisk
+
 cdrom/ramdisk.img: ${APPS_X} base/lib/ld.so base/lib/libtoaru-decor-fancy.so Makefile | dirs
 	genext2fs -B 4096 -d base -U -b 4096 -N 2048 cdrom/ramdisk.img
 
-image.iso: cdrom/ramdisk.img cdrom/boot/boot.sys cdrom/kernel
+# CD image
+
+image.iso: cdrom/ramdisk.img cdrom/boot/boot.sys cdrom/kernel ${MODULES}
 	xorriso -as mkisofs -R -J -c boot/bootcat -b boot/boot.sys -no-emul-boot -boot-load-size 20 -o image.iso cdrom
 
+# Boot loader
+
 cdrom/boot/boot.sys: boot/boot.o boot/cstuff.o boot/link.ld | cdrom/boot
 	${KLD} -T boot/link.ld -o $@ boot/boot.o boot/cstuff.o
 
@@ -157,3 +224,7 @@ clean:
 	rm -f cdrom/ramdisk.img
 	rm -f cdrom/boot/boot.sys
 	rm -f boot/*.o
+	rm -f cdrom/kernel
+	rm -f ${KERNEL_OBJS} ${KERNEL_ASMOBJS}
+	rm -f ${MODULES}
+

+ 3 - 0
kernel/.gdb_history

@@ -0,0 +1,3 @@
+info line *0x00112449
+info line *0x00100ba7
+info line *0x00100ba7

+ 60 - 0
kernel/boot.S

@@ -0,0 +1,60 @@
+.set MB_MAGIC,              0x1BADB002
+.set MB_FLAG_PAGE_ALIGN,    1 << 0
+.set MB_FLAG_MEMORY_INFO,   1 << 1
+.set MB_FLAG_GRAPHICS,      1 << 2
+.set MB_FLAGS,              MB_FLAG_PAGE_ALIGN | MB_FLAG_MEMORY_INFO | MB_FLAG_GRAPHICS
+.set MB_CHECKSUM,           -(MB_MAGIC + MB_FLAGS)
+
+.section .multiboot
+.align 4
+
+/* Multiboot section */
+.long MB_MAGIC
+.long MB_FLAGS
+.long MB_CHECKSUM
+.long 0x00000000 /* header_addr */
+.long 0x00000000 /* load_addr */
+.long 0x00000000 /* load_end_addr */
+.long 0x00000000 /* bss_end_addr */
+.long 0x00000000 /* entry_addr */
+
+/* Request linear graphics mode */
+.long 0x00000000
+.long 0
+.long 0
+.long 32
+
+/* .stack resides in .bss */
+.section .stack, "aw", @nobits
+stack_bottom:
+.skip 32768 /* 32KiB */
+stack_top:
+
+.section .text
+
+.global start
+.type start, @function
+
+.extern kmain
+.type kmain, @function
+
+start:
+    /* Setup our stack */
+    mov $stack_top, %esp
+
+    /* Make sure our stack is 16-byte aligned */
+    and $-16, %esp
+
+    pushl %esp
+    pushl %eax /* Multiboot header magic */
+    pushl %ebx /* Multiboot header pointer */
+
+    /* Disable interrupts and call kernel proper */
+    cli
+    call kmain
+
+    /* Clear interrupts and hang if we return from kmain */
+    cli
+hang:
+    hlt
+    jmp hang

+ 102 - 0
kernel/cpu/gdt.c

@@ -0,0 +1,102 @@
+/* vim: tabstop=4 shiftwidth=4 noexpandtab
+ * This file is part of ToaruOS and is released under the terms
+ * of the NCSA / University of Illinois License - see LICENSE.md
+ * Copyright (C) 2011-2013 Kevin Lange
+ * Copyright (C) 2015 Dale Weiler
+ *
+ * Global Descriptor Tables module
+ *
+ */
+#include <system.h>
+#include <logging.h>
+#include <tss.h>
+
+typedef struct {
+	/* Limits */
+	uint16_t limit_low;
+	/* Segment address */
+	uint16_t base_low;
+	uint8_t base_middle;
+	/* Access modes */
+	uint8_t access;
+	uint8_t granularity;
+	uint8_t base_high;
+} __attribute__((packed)) gdt_entry_t;
+
+typedef struct {
+	uint16_t limit;
+	uintptr_t base;
+} __attribute__((packed)) gdt_pointer_t;
+
+/* In the future we may need to put a lock on the access of this */
+static struct {
+    gdt_entry_t entries[6];
+    gdt_pointer_t pointer;
+    tss_entry_t tss;
+} gdt __attribute__((used));
+
+extern void gdt_flush(uintptr_t);
+
+#define ENTRY(X) (gdt.entries[(X)])
+
+void gdt_set_gate(uint8_t num, uint64_t base, uint64_t limit, uint8_t access, uint8_t gran) {
+	/* Base Address */
+	ENTRY(num).base_low = (base & 0xFFFF);
+	ENTRY(num).base_middle = (base >> 16) & 0xFF;
+	ENTRY(num).base_high = (base >> 24) & 0xFF;
+	/* Limits */
+	ENTRY(num).limit_low = (limit & 0xFFFF);
+	ENTRY(num).granularity = (limit >> 16) & 0X0F;
+	/* Granularity */
+	ENTRY(num).granularity |= (gran & 0xF0);
+	/* Access flags */
+	ENTRY(num).access = access;
+}
+
+static void write_tss(int32_t num, uint16_t ss0, uint32_t esp0);
+
+void gdt_install(void) {
+	gdt_pointer_t *gdtp = &gdt.pointer;
+	gdtp->limit = sizeof gdt.entries - 1;
+	gdtp->base = (uintptr_t)&ENTRY(0);
+
+	gdt_set_gate(0, 0, 0, 0, 0);                /* NULL segment */
+	gdt_set_gate(1, 0, 0xFFFFFFFF, 0x9A, 0xCF); /* Code segment */
+	gdt_set_gate(2, 0, 0xFFFFFFFF, 0x92, 0xCF); /* Data segment */
+	gdt_set_gate(3, 0, 0xFFFFFFFF, 0xFA, 0xCF); /* User code */
+	gdt_set_gate(4, 0, 0xFFFFFFFF, 0xF2, 0xCF); /* User data */
+
+	write_tss(5, 0x10, 0x0);
+
+	/* Go go go */
+	gdt_flush((uintptr_t)gdtp);
+	tss_flush();
+}
+
+static void write_tss(int32_t num, uint16_t ss0, uint32_t esp0) {
+	tss_entry_t * tss = &gdt.tss;
+	uintptr_t base = (uintptr_t)tss;
+	uintptr_t limit = base + sizeof *tss;
+
+	/* Add the TSS descriptor to the GDT */
+	gdt_set_gate(num, base, limit, 0xE9, 0x00);
+
+	memset(tss, 0x0, sizeof *tss);
+
+	tss->ss0 = ss0;
+	tss->esp0 = esp0;
+	tss->cs = 0x0b;
+	tss->ss = 0x13;
+	tss->ds = 0x13;
+	tss->es = 0x13;
+	tss->fs = 0x13;
+	tss->gs = 0x13;
+
+	tss->iomap_base = sizeof *tss;
+}
+
+void set_kernel_stack(uintptr_t stack) {
+	/* Set the kernel stack */
+	gdt.tss.esp0 = stack;
+}
+

+ 53 - 0
kernel/cpu/idt.c

@@ -0,0 +1,53 @@
+/* vim: tabstop=4 shiftwidth=4 noexpandtab
+ * This file is part of ToaruOS and is released under the terms
+ * of the NCSA / University of Illinois License - see LICENSE.md
+ * Copyright (C) 2011-2013 Kevin Lange
+ * Copyright (C) 2015 Dale Weiler
+ *
+ * Interrupt Descriptor Tables
+ *
+ */
+#include <system.h>
+#include <logging.h>
+
+typedef struct {
+	uint16_t base_low;
+	uint16_t sel;
+	uint8_t zero;
+	uint8_t flags;
+	uint16_t base_high;
+} __attribute__((packed)) idt_entry_t;
+
+typedef struct {
+	uint16_t limit;
+	uintptr_t base;
+} __attribute__((packed)) idt_pointer_t;
+
+/* In the future we may need to put a lock on the access of this */
+static struct {
+	idt_entry_t entries[256];
+	idt_pointer_t pointer;
+} idt __attribute__((used));
+
+#define ENTRY(X) (idt.entries[(X)])
+
+typedef void (*idt_gate_t)(void);
+
+extern void idt_load(uintptr_t);
+
+void idt_set_gate(uint8_t num, idt_gate_t base, uint16_t sel, uint8_t flags) {
+	ENTRY(num).base_low = ((uintptr_t)base & 0xFFFF);
+	ENTRY(num).base_high = ((uintptr_t)base >> 16) & 0xFFFF;
+	ENTRY(num).sel = sel;
+	ENTRY(num).zero = 0;
+	ENTRY(num).flags = flags | 0x60;
+}
+
+void idt_install(void) {
+	idt_pointer_t * idtp = &idt.pointer;
+	idtp->limit = sizeof idt.entries - 1;
+	idtp->base = (uintptr_t)&ENTRY(0);
+	memset(&ENTRY(0), 0, sizeof idt.entries);
+
+	idt_load((uintptr_t)idtp);
+}

+ 164 - 0
kernel/cpu/irq.c

@@ -0,0 +1,164 @@
+/* vim: tabstop=4 shiftwidth=4 noexpandtab
+ * This file is part of ToaruOS and is released under the terms
+ * of the NCSA / University of Illinois License - see LICENSE.md
+ * Copyright (C) 2011-2014 Kevin Lange
+ * Copyright (C) 2015 Dale Weiler
+ *
+ * Interrupt Requests
+ *
+ */
+#include <system.h>
+#include <logging.h>
+#include <module.h>
+#include <printf.h>
+
+/* Programmable interrupt controller */
+#define PIC1           0x20
+#define PIC1_COMMAND   PIC1
+#define PIC1_OFFSET    0x20
+#define PIC1_DATA      (PIC1+1)
+
+#define PIC2           0xA0
+#define PIC2_COMMAND   PIC2
+#define PIC2_OFFSET    0x28
+#define PIC2_DATA      (PIC2+1)
+
+#define PIC_EOI        0x20
+
+#define ICW1_ICW4      0x01
+#define ICW1_INIT      0x10
+
+#define PIC_WAIT() \
+	do { \
+		/* May be fragile */ \
+		asm volatile("jmp 1f\n\t" \
+		             "1:\n\t" \
+		             "    jmp 2f\n\t" \
+		             "2:"); \
+	} while (0)
+
+/* Interrupts */
+static volatile int sync_depth = 0;
+
+#define SYNC_CLI() asm volatile("cli")
+#define SYNC_STI() asm volatile("sti")
+
+void int_disable(void) {
+	/* Check if interrupts are enabled */
+	uint32_t flags;
+	asm volatile("pushf\n\t"
+	             "pop %%eax\n\t"
+	             "movl %%eax, %0\n\t"
+	             : "=r"(flags)
+	             :
+	             : "%eax");
+
+	/* Disable interrupts */
+	SYNC_CLI();
+
+	/* If interrupts were enabled, then this is the first call depth */
+	if (flags & (1 << 9)) {
+		sync_depth = 1;
+	} else {
+		/* Otherwise there is now an additional call depth */
+		sync_depth++;
+	}
+}
+
+void int_resume(void) {
+	/* If there is one or no call depths, reenable interrupts */
+	if (sync_depth == 0 || sync_depth == 1) {
+		SYNC_STI();
+	} else {
+		sync_depth--;
+	}
+}
+
+void int_enable(void) {
+	sync_depth = 0;
+	SYNC_STI();
+}
+
+/* Interrupt Requests */
+#define IRQ_CHAIN_SIZE  16
+#define IRQ_CHAIN_DEPTH 4
+
+static void (*irqs[IRQ_CHAIN_SIZE])(void);
+static irq_handler_chain_t irq_routines[IRQ_CHAIN_SIZE * IRQ_CHAIN_DEPTH] = { NULL };
+
+void irq_install_handler(size_t irq, irq_handler_chain_t handler) {
+	/* Disable interrupts when changing handlers */
+	SYNC_CLI();
+	for (size_t i = 0; i < IRQ_CHAIN_DEPTH; i++) {
+		if (irq_routines[i * IRQ_CHAIN_SIZE + irq])
+			continue;
+		irq_routines[i * IRQ_CHAIN_SIZE + irq] = handler;
+		break;
+	}
+	SYNC_STI();
+}
+
+void irq_uninstall_handler(size_t irq) {
+	/* Disable interrupts when changing handlers */
+	SYNC_CLI();
+	for (size_t i = 0; i < IRQ_CHAIN_DEPTH; i++)
+		irq_routines[i * IRQ_CHAIN_SIZE + irq] = NULL;
+	SYNC_STI();
+}
+
+static void irq_remap(void) {
+	/* Cascade initialization */
+	outportb(PIC1_COMMAND, ICW1_INIT|ICW1_ICW4); PIC_WAIT();
+	outportb(PIC2_COMMAND, ICW1_INIT|ICW1_ICW4); PIC_WAIT();
+
+	/* Remap */
+	outportb(PIC1_DATA, PIC1_OFFSET); PIC_WAIT();
+	outportb(PIC2_DATA, PIC2_OFFSET); PIC_WAIT();
+
+	/* Cascade identity with slave PIC at IRQ2 */
+	outportb(PIC1_DATA, 0x04); PIC_WAIT();
+	outportb(PIC2_DATA, 0x02); PIC_WAIT();
+
+	/* Request 8086 mode on each PIC */
+	outportb(PIC1_DATA, 0x01); PIC_WAIT();
+	outportb(PIC2_DATA, 0x01); PIC_WAIT();
+}
+
+static void irq_setup_gates(void) {
+	for (size_t i = 0; i < IRQ_CHAIN_SIZE; i++) {
+		idt_set_gate(32 + i, irqs[i], 0x08, 0x8E);
+	}
+}
+
+void irq_install(void) {
+	char buffer[16];
+	for (int i = 0; i < IRQ_CHAIN_SIZE; i++) {
+		sprintf(buffer, "_irq%d", i);
+		irqs[i] = symbol_find(buffer);
+	}
+	irq_remap();
+	irq_setup_gates();
+}
+
+void irq_ack(size_t irq_no) {
+	if (irq_no >= 8) {
+		outportb(PIC2_COMMAND, PIC_EOI);
+	}
+	outportb(PIC1_COMMAND, PIC_EOI);
+}
+
+void irq_handler(struct regs *r) {
+	/* Disable interrupts when handling */
+	int_disable();
+	if (r->int_no <= 47 && r->int_no >= 32) {
+		for (size_t i = 0; i < IRQ_CHAIN_DEPTH; i++) {
+			irq_handler_chain_t handler = irq_routines[i * IRQ_CHAIN_SIZE + (r->int_no - 32)];
+			if (handler && handler(r)) {
+				goto done;
+			}
+		}
+		irq_ack(r->int_no - 32);
+	}
+done:
+	int_resume();
+}

+ 94 - 0
kernel/cpu/isr.c

@@ -0,0 +1,94 @@
+/* vim: tabstop=4 shiftwidth=4 noexpandtab
+ * This file is part of ToaruOS and is released under the terms
+ * of the NCSA / University of Illinois License - see LICENSE.md
+ * Copyright (C) 2011-2014 Kevin Lange
+ * Copyright (C) 2015 Dale Weiler
+ *
+ * Interrupt Service Requests
+ */
+#include <system.h>
+#include <logging.h>
+#include <module.h>
+#include <printf.h>
+
+/* The count is treated as is when setting up IDT gates. However there is an
+ * additional ISR for the system call vector which is handled explicitly since
+ * it's mapped at a different address.
+ */
+#define ISR_COUNT 32
+
+static struct {
+	size_t index;
+	void (*stub)(void);
+} isrs[32 + 1] __attribute__((used));
+
+static irq_handler_t isr_routines[256] = { 0 };
+
+void isrs_install_handler(size_t isrs, irq_handler_t handler) {
+	isr_routines[isrs] = handler;
+}
+
+void isrs_uninstall_handler(size_t isrs) {
+	isr_routines[isrs] = 0;
+}
+
+void isrs_install(void) {
+	char buffer[16];
+	for (int i = 0; i < ISR_COUNT; i++) {
+		sprintf(buffer, "_isr%d", i);
+		isrs[i].index = i;
+		isrs[i].stub = symbol_find(buffer);
+	}
+	isrs[ISR_COUNT].index = SYSCALL_VECTOR;
+	isrs[ISR_COUNT].stub = symbol_find("_isr127");
+
+	for (int i = 0; i < ISR_COUNT + 1; i++) {
+		idt_set_gate(isrs[i].index, isrs[i].stub, 0x08, 0x8E);
+	}
+}
+
+static const char *exception_messages[32] = {
+	"Division by zero",
+	"Debug",
+	"Non-maskable interrupt",
+	"Breakpoint",
+	"Detected overflow",
+	"Out-of-bounds",
+	"Invalid opcode",
+	"No coprocessor",
+	"Double fault",
+	"Coprocessor segment overrun",
+	"Bad TSS",
+	"Segment not present",
+	"Stack fault",
+	"General protection fault",
+	"Page fault",
+	"Unknown interrupt",
+	"Coprocessor fault",
+	"Alignment check",
+	"Machine check",
+	"Reserved",
+	"Reserved",
+	"Reserved",
+	"Reserved",
+	"Reserved",
+	"Reserved",
+	"Reserved",
+	"Reserved",
+	"Reserved",
+	"Reserved",
+	"Reserved",
+	"Reserved",
+	"Reserved"
+};
+
+void fault_handler(struct regs * r) {
+	irq_handler_t handler = isr_routines[r->int_no];
+	if (handler) {
+		handler(r);
+	} else {
+		debug_print(CRITICAL, "Unhandled exception: [%d] %s", r->int_no, exception_messages[r->int_no]);
+		HALT_AND_CATCH_FIRE("Process caused an unhandled exception", r);
+		STOP;
+	}
+}

+ 194 - 0
kernel/devices/cmos.c

@@ -0,0 +1,194 @@
+/* vim: tabstop=4 shiftwidth=4 noexpandtab
+ * This file is part of ToaruOS and is released under the terms
+ * of the NCSA / University of Illinois License - see LICENSE.md
+ * Copyright (C) 2011-2014 Kevin Lange
+ *
+ * CMOS Driver
+ *
+ */
+
+#include <system.h>
+
+/* CMOS values are stored like so:
+ * Say it's 8:42 AM, then the values are stored as:
+ * 0x08, 0x42... why this was a good idea, I have no
+ * clue, but that's how it usually is.
+ *
+ * This function will convert between this "BCD" format
+ * and regular decimal integers. */
+#define from_bcd(val)  ((val / 16) * 10 + (val & 0xf))
+
+#define	CMOS_ADDRESS     0x70
+#define	CMOS_DATA        0x71
+
+enum
+{
+	CMOS_SECOND = 0,
+	CMOS_MINUTE = 2,
+	CMOS_HOUR = 4,
+	CMOS_DAY = 7,
+	CMOS_MONTH = 8,
+	CMOS_YEAR = 9
+};
+
+void
+cmos_dump(
+		uint16_t * values
+		) {
+	uint16_t index;
+	for (index = 0; index < 128; ++index) {
+		outportb(CMOS_ADDRESS, index);
+		values[index] = inportb(CMOS_DATA);
+	}
+}
+
+int is_update_in_progress(void)
+{
+	outportb(CMOS_ADDRESS, 0x0a);
+	return inportb(CMOS_DATA) & 0x80;
+}
+
+/**
+ * Get the current month and day.
+ *
+ * @param month Pointer to a short to store the month
+ * @param day   Pointer to a short to store the day
+ */
+void
+get_date(
+		uint16_t * month,
+		uint16_t * day
+		) {
+	uint16_t values[128]; /* CMOS dump */
+	cmos_dump(values);
+
+	*month = from_bcd(values[CMOS_MONTH]);
+	*day   = from_bcd(values[CMOS_DAY]);
+}
+
+/**
+ * Get the current time.
+ *
+ * @param hours   Pointer to a short to store the current hour (/24)
+ * @param minutes Pointer to a short to store the current minute
+ * @param seconds Pointer to a short to store the current second
+ */
+void
+get_time(
+		uint16_t * hours,
+		uint16_t * minutes,
+		uint16_t * seconds
+		) {
+	uint16_t values[128]; /* CMOS dump */
+	cmos_dump(values);
+
+	*hours   = from_bcd(values[CMOS_HOUR]);
+	*minutes = from_bcd(values[CMOS_MINUTE]);
+	*seconds = from_bcd(values[CMOS_SECOND]);
+}
+
+uint32_t secs_of_years(int years) {
+	uint32_t days = 0;
+	years += 2000;
+	while (years > 1969) {
+		days += 365;
+		if (years % 4 == 0) {
+			if (years % 100 == 0) {
+				if (years % 400 == 0) {
+					days++;
+				}
+			} else {
+				days++;
+			}
+		}
+		years--;
+	}
+	return days * 86400;
+}
+
+uint32_t secs_of_month(int months, int year) {
+	year += 2000;
+
+	uint32_t days = 0;
+	switch(months) {
+		case 11:
+			days += 30;
+		case 10:
+			days += 31;
+		case 9:
+			days += 30;
+		case 8:
+			days += 31;
+		case 7:
+			days += 31;
+		case 6:
+			days += 30;
+		case 5:
+			days += 31;
+		case 4:
+			days += 30;
+		case 3:
+			days += 31;
+		case 2:
+			days += 28;
+			if ((year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0))) {
+				days++;
+			}
+		case 1:
+			days += 31;
+		default:
+			break;
+	}
+	return days * 86400;
+}
+
+uint32_t boot_time = 0;
+
+uint32_t read_cmos(void) {
+	uint16_t values[128];
+	uint16_t old_values[128];
+
+	while (is_update_in_progress())
+		;
+
+	cmos_dump(values);
+
+	do
+	{
+		memcpy(old_values, values, 128);
+		while (is_update_in_progress())
+			;
+
+		cmos_dump(values);
+	} while ((old_values[CMOS_SECOND] != values[CMOS_SECOND]) ||
+		 (old_values[CMOS_MINUTE] != values[CMOS_MINUTE]) ||
+		 (old_values[CMOS_HOUR] != values[CMOS_HOUR])     ||
+		 (old_values[CMOS_DAY] != values[CMOS_DAY])       ||
+		 (old_values[CMOS_MONTH] != values[CMOS_MONTH])   ||
+		 (old_values[CMOS_YEAR] != values[CMOS_YEAR]));
+
+	/* Math Time */
+	uint32_t time =
+	  secs_of_years(from_bcd(values[CMOS_YEAR]) - 1) +
+	  secs_of_month(from_bcd(values[CMOS_MONTH]) - 1,
+			from_bcd(values[CMOS_YEAR])) +
+	  (from_bcd(values[CMOS_DAY]) - 1) * 86400 +
+	  (from_bcd(values[CMOS_HOUR])) * 3600 +
+	  (from_bcd(values[CMOS_MINUTE])) * 60 +
+	  from_bcd(values[CMOS_SECOND]) + 0;
+
+	return time;
+}
+
+int gettimeofday(struct timeval * t, void *z) {
+	t->tv_sec = boot_time + timer_ticks + timer_drift;
+	t->tv_usec = timer_subticks * 1000;
+	return 0;
+}
+
+uint32_t now(void) {
+	struct timeval t;
+	gettimeofday(&t, NULL);
+	return t.tv_sec;
+}
+

+ 142 - 0
kernel/devices/fpu.c

@@ -0,0 +1,142 @@
+/* vim: tabstop=4 shiftwidth=4 noexpandtab
+ * This file is part of ToaruOS and is released under the terms
+ * of the NCSA / University of Illinois License - see LICENSE.md
+ * Copyright (C) 2011-2013 Kevin Lange
+ *
+ * FPU and SSE context handling.
+ *
+ * FPU context is kept through context switches,
+ * but the FPU is disabled. When an FPU instruction
+ * is executed, it will trap here and the context
+ * will be saved to its original owner and the context
+ * for the current process will be loaded or the FPU
+ * will be reset for the new process.
+ *
+ * FPU states are per kernel thread.
+ *
+ */
+#include <system.h>
+#include <logging.h>
+
+#define NO_LAZY_FPU
+
+process_t * fpu_thread = NULL;
+
+/**
+ * Set the FPU control word
+ *
+ * @param cw What to set the control word to.
+ */
+void
+set_fpu_cw(const uint16_t cw) {
+	asm volatile("fldcw %0" :: "m"(cw));
+}
+
+/**
+ * Enable the FPU and SSE
+ */
+void enable_fpu(void) {
+	asm volatile ("clts");
+	size_t t;
+	asm volatile ("mov %%cr0, %0" : "=r"(t));
+	t &= ~(1 << 2);
+	t |= (1 << 1);
+	asm volatile ("mov %0, %%cr0" :: "r"(t));
+
+	asm volatile ("mov %%cr4, %0" : "=r"(t));
+	t |= 3 << 9;
+	asm volatile ("mov %0, %%cr4" :: "r"(t));
+
+}
+
+/**
+ * Disable FPU and SSE so it traps to the kernel
+ */
+void disable_fpu(void) {
+	size_t t;
+	asm volatile ("mov %%cr0, %0" : "=r"(t));
+	t |= 1 << 3;
+	asm volatile ("mov %0, %%cr0" :: "r"(t));
+}
+
+/* Temporary aligned buffer for copying around FPU contexts */
+uint8_t saves[512] __attribute__((aligned(16)));
+
+/**
+ * Restore the FPU for a process
+ */
+void restore_fpu(process_t * proc) {
+	memcpy(&saves,(uint8_t *)&proc->thread.fp_regs,512);
+	asm volatile ("fxrstor (%0)" :: "r"(saves));
+}
+
+/**
+ * Save the FPU for a process
+ */
+void save_fpu(process_t * proc) {
+	asm volatile ("fxsave (%0)" :: "r"(saves));
+	memcpy((uint8_t *)&proc->thread.fp_regs,&saves,512);
+}
+
+/**
+ * Initialize the FPU
+ */
+void init_fpu(void) {
+	asm volatile ("fninit");
+}
+
+/**
+ * Kernel trap for FPU usage when FPU is disabled
+ */
+void invalid_op(struct regs * r) {
+	/* First, turn the FPU on */
+	enable_fpu();
+	if (fpu_thread == current_process) {
+		/* If this is the thread that last used the FPU, do nothing */
+		return;
+	}
+	if (fpu_thread) {
+		/* If there is a thread that was using the FPU, save its state */
+		save_fpu(fpu_thread);
+	}
+	fpu_thread = (process_t *)current_process;
+	if (!fpu_thread->thread.fpu_enabled) {
+		/*
+		 * If the FPU has not been used in this thread previously,
+		 * we need to initialize it.
+		 */
+		init_fpu();
+		fpu_thread->thread.fpu_enabled = 1;
+		return;
+	}
+	/* Otherwise we restore the context for this thread. */
+	restore_fpu(fpu_thread);
+}
+
+/* Called during a context switch; disable the FPU */
+void switch_fpu(void) {
+#ifdef NO_LAZY_FPU
+	save_fpu((process_t *)current_process);
+#else
+	disable_fpu();
+#endif
+}
+
+void unswitch_fpu(void) {
+#ifdef NO_LAZY_FPU
+	restore_fpu((process_t *)current_process);
+#endif
+}
+
+/* Enable the FPU context handling */
+void fpu_install(void) {
+#ifdef NO_LAZY_FPU
+	enable_fpu();
+	init_fpu();
+	save_fpu((void*)current_process);
+#else
+	enable_fpu();
+	disable_fpu();
+	isrs_install_handler(7, &invalid_op);
+#endif
+}

+ 113 - 0
kernel/devices/pci.c

@@ -0,0 +1,113 @@
+/* vim: tabstop=4 shiftwidth=4 noexpandtab
+ * This file is part of ToaruOS and is released under the terms
+ * of the NCSA / University of Illinois License - see LICENSE.md
+ * Copyright (C) 2011-2014 Kevin Lange
+ *
+ * ToAruOS PCI Initialization
+ */
+
+#include <system.h>
+#include <pci.h>
+#include <pci_list.h>
+
+
+void pci_write_field(uint32_t device, int field, int size, uint32_t value) {
+	outportl(PCI_ADDRESS_PORT, pci_get_addr(device, field));
+	outportl(PCI_VALUE_PORT, value);
+}
+
+uint32_t pci_read_field(uint32_t device, int field, int size) {
+	outportl(PCI_ADDRESS_PORT, pci_get_addr(device, field));
+
+	if (size == 4) {
+		uint32_t t = inportl(PCI_VALUE_PORT);
+		return t;
+	} else if (size == 2) {
+		uint16_t t = inports(PCI_VALUE_PORT + (field & 2));
+		return t;
+	} else if (size == 1) {
+		uint8_t t = inportb(PCI_VALUE_PORT + (field & 3));
+		return t;
+	}
+	return 0xFFFF;
+}
+
+uint16_t pci_find_type(uint32_t dev) {
+	return (pci_read_field(dev, PCI_CLASS, 1) << 8) | pci_read_field(dev, PCI_SUBCLASS, 1);
+}
+
+const char * pci_vendor_lookup(unsigned short vendor_id) {
+	for (unsigned int i = 0; i < PCI_VENTABLE_LEN; ++i) {
+		if (PciVenTable[i].VenId == vendor_id) {
+			return PciVenTable[i].VenFull;
+		}
+	}
+	return "";
+}
+
+const char * pci_device_lookup(unsigned short vendor_id, unsigned short device_id) {
+	for (unsigned int i = 0; i < PCI_DEVTABLE_LEN; ++i) {
+		if (PciDevTable[i].VenId == vendor_id && PciDevTable[i].DevId == device_id) {
+			return PciDevTable[i].ChipDesc;
+		}
+	}
+	return "";
+}
+
+void pci_scan_hit(pci_func_t f, uint32_t dev, void * extra) {
+	int dev_vend = (int)pci_read_field(dev, PCI_VENDOR_ID, 2);
+	int dev_dvid = (int)pci_read_field(dev, PCI_DEVICE_ID, 2);
+
+	f(dev, dev_vend, dev_dvid, extra);
+}
+
+void pci_scan_func(pci_func_t f, int type, int bus, int slot, int func, void * extra) {
+	uint32_t dev = pci_box_device(bus, slot, func);
+	if (type == -1 || type == pci_find_type(dev)) {
+		pci_scan_hit(f, dev, extra);
+	}
+	if (pci_find_type(dev) == PCI_TYPE_BRIDGE) {
+		pci_scan_bus(f, type, pci_read_field(dev, PCI_SECONDARY_BUS, 1), extra);
+	}
+}
+
+void pci_scan_slot(pci_func_t f, int type, int bus, int slot, void * extra) {
+	uint32_t dev = pci_box_device(bus, slot, 0);
+	if (pci_read_field(dev, PCI_VENDOR_ID, 2) == PCI_NONE) {
+		return;
+	}
+	pci_scan_func(f, type, bus, slot, 0, extra);
+	if (!pci_read_field(dev, PCI_HEADER_TYPE, 1)) {
+		return;
+	}
+	for (int func = 1; func < 8; func++) {
+		uint32_t dev = pci_box_device(bus, slot, func);
+		if (pci_read_field(dev, PCI_VENDOR_ID, 2) != PCI_NONE) {
+			pci_scan_func(f, type, bus, slot, func, extra);
+		}
+	}
+}
+
+void pci_scan_bus(pci_func_t f, int type, int bus, void * extra) {
+	for (int slot = 0; slot < 32; ++slot) {
+		pci_scan_slot(f, type, bus, slot, extra);
+	}
+}
+
+void pci_scan(pci_func_t f, int type, void * extra) {
+
+	if ((pci_read_field(0, PCI_HEADER_TYPE, 1) & 0x80) == 0) {
+		pci_scan_bus(f,type,0,extra);
+		return;
+	}
+
+	for (int func = 0; func < 8; ++func) {
+		uint32_t dev = pci_box_device(0, 0, func);
+		if (pci_read_field(dev, PCI_VENDOR_ID, 2) != PCI_NONE) {
+			pci_scan_bus(f, type, func, extra);
+		} else {
+			break;
+		}
+	}
+}
+

+ 90 - 0
kernel/devices/timer.c

@@ -0,0 +1,90 @@
+/* vim: tabstop=4 shiftwidth=4 noexpandtab
+ * This file is part of ToaruOS and is released under the terms
+ * of the NCSA / University of Illinois License - see LICENSE.md
+ * Copyright (C) 2011-2013 Kevin Lange
+ *
+ * Programmable Interrupt Timer
+ */
+#include <system.h>
+#include <logging.h>
+#include <process.h>
+
+#define PIT_A 0x40
+#define PIT_B 0x41
+#define PIT_C 0x42
+#define PIT_CONTROL 0x43
+
+#define PIT_MASK 0xFF
+#define PIT_SCALE 1193180
+#define PIT_SET 0x34
+
+#define TIMER_IRQ 0
+
+#define SUBTICKS_PER_TICK 1000
+#define RESYNC_TIME 1
+
+/*
+ * Set the phase (in hertz) for the Programmable
+ * Interrupt Timer (PIT).
+ */
+void
+timer_phase(
+		int hz
+		) {
+	int divisor = PIT_SCALE / hz;
+	outportb(PIT_CONTROL, PIT_SET);
+	outportb(PIT_A, divisor & PIT_MASK);
+	outportb(PIT_A, (divisor >> 8) & PIT_MASK);
+}
+
+/*
+ * Internal timer counters
+ */
+unsigned long timer_ticks = 0;
+unsigned long timer_subticks = 0;
+signed long timer_drift = 0;
+signed long _timer_drift = 0;
+
+static int behind = 0;
+
+/*
+ * IRQ handler for when the timer fires
+ */
+int timer_handler(struct regs *r) {
+	if (++timer_subticks == SUBTICKS_PER_TICK || (behind && ++timer_subticks == SUBTICKS_PER_TICK)) {
+		timer_ticks++;
+		timer_subticks = 0;
+		if (timer_ticks % RESYNC_TIME == 0) {
+			uint32_t new_time = read_cmos();
+			_timer_drift = new_time - boot_time - timer_ticks;
+			if (_timer_drift > 0) behind = 1;
+			else behind = 0;
+		}
+	}
+	irq_ack(TIMER_IRQ);
+
+	wakeup_sleepers(timer_ticks, timer_subticks);
+	switch_task(1);
+	return 1;
+}
+
+void relative_time(unsigned long seconds, unsigned long subseconds, unsigned long * out_seconds, unsigned long * out_subseconds) {
+	if (subseconds + timer_subticks > SUBTICKS_PER_TICK) {
+		*out_seconds    = timer_ticks + seconds + 1;
+		*out_subseconds = (subseconds + timer_subticks) - SUBTICKS_PER_TICK;
+	} else {
+		*out_seconds    = timer_ticks + seconds;
+		*out_subseconds = timer_subticks + subseconds;
+	}
+}
+
+/*
+ * Device installer for the PIT
+ */
+void timer_install(void) {
+	debug_print(NOTICE,"Initializing interval timer");
+	boot_time = read_cmos();
+	irq_install_handler(TIMER_IRQ, timer_handler);
+	timer_phase(SUBTICKS_PER_TICK); /* 100Hz */
+}
+

+ 64 - 0
kernel/ds/bitset.c

@@ -0,0 +1,64 @@
+/* vim: tabstop=4 shiftwidth=4 noexpandtab
+ * This file is part of ToaruOS and is released under the terms
+ * of the NCSA / University of Illinois License - see LICENSE.md
+ * Copyright (C) 2015 Dale Weiler
+ *               2015 Kevin Lange
+ */
+#include "bitset.h"
+
+#define CEIL(NUMBER, BASE) \
+	(((NUMBER) + (BASE) - 1) & ~((BASE) - 1))
+
+#define iom \
+	size_t index  = bit >> 3; \
+	bit = bit - index * 8; \
+	size_t offset = bit & 7; \
+	size_t mask   = 1 << offset;
+
+void bitset_init(bitset_t *set, size_t size) {
+	set->size = CEIL(size, 8);
+	set->data = calloc(set->size, 1);
+}
+
+void bitset_free(bitset_t *set) {
+	free(set->data);
+}
+
+static void bitset_resize(bitset_t *set, size_t size) {
+	if (set->size >= size) {
+		return;
+	}
+
+	set->data = realloc(set->data, size);
+	memset(set->data + set->size, 0, size - set->size);
+	set->size = size;
+}
+
+void bitset_set(bitset_t *set, size_t bit) {
+	iom;
+	if (set->size <= index) {
+		bitset_resize(set, set->size << 1);
+	}
+	set->data[index] |= mask;
+}
+
+int bitset_ffub(bitset_t *set) {
+	for (size_t i = 0; i < set->size * 8; i++) {
+		if (bitset_test(set, i)) {
+			continue;
+		}
+		return (int)i;
+	}
+	return -1;
+}
+
+void bitset_clear(bitset_t *set, size_t bit) {
+	iom;
+	set->data[index] &= ~mask;
+}
+
+int bitset_test(bitset_t *set, size_t bit) {
+	iom;
+	return !!(mask & set->data[index]);
+}
+

+ 217 - 0
kernel/ds/hashmap.c

@@ -0,0 +1,217 @@
+/* vim: tabstop=4 shiftwidth=4 noexpandtab
+ * This file is part of ToaruOS and is released under the terms
+ * of the NCSA / University of Illinois License - see LICENSE.md
+ * Copyright (C) 2013-2014 Kevin Lange
+ */
+#include "list.h"
+#include "hashmap.h"
+
+unsigned int hashmap_string_hash(void * _key) {
+	unsigned int hash = 0;
+	char * key = (char *)_key;
+	int c;
+	/* This is the so-called "sdbm" hash. It comes from a piece of
+	 * public domain code from a clone of ndbm. */
+	while ((c = *key++)) {
+		hash = c + (hash << 6) + (hash << 16) - hash;
+	}
+	return hash;
+}
+
+int hashmap_string_comp(void * a, void * b) {
+	return !strcmp(a,b);
+}
+
+void * hashmap_string_dupe(void * key) {
+	return strdup(key);
+}
+
+unsigned int hashmap_int_hash(void * key) {
+	return (unsigned int)key;
+}
+
+int hashmap_int_comp(void * a, void * b) {
+	return (int)a == (int)b;
+}
+
+void * hashmap_int_dupe(void * key) {
+	return key;
+}
+
+static void hashmap_int_free(void * ptr) {
+	return;
+}
+
+
+hashmap_t * hashmap_create(int size) {
+	hashmap_t * map = malloc(sizeof(hashmap_t));
+
+	map->hash_func     = &hashmap_string_hash;
+	map->hash_comp     = &hashmap_string_comp;
+	map->hash_key_dup  = &hashmap_string_dupe;
+	map->hash_key_free = &free;
+	map->hash_val_free = &free;
+
+	map->size = size;
+	map->entries = malloc(sizeof(hashmap_entry_t *) * size);
+	memset(map->entries, 0x00, sizeof(hashmap_entry_t *) * size);
+
+	return map;
+}
+
+hashmap_t * hashmap_create_int(int size) {
+	hashmap_t * map = malloc(sizeof(hashmap_t));
+
+	map->hash_func     = &hashmap_int_hash;
+	map->hash_comp     = &hashmap_int_comp;
+	map->hash_key_dup  = &hashmap_int_dupe;
+	map->hash_key_free = &hashmap_int_free;
+	map->hash_val_free = &free;
+
+	map->size = size;
+	map->entries = malloc(sizeof(hashmap_entry_t *) * size);
+	memset(map->entries, 0x00, sizeof(hashmap_entry_t *) * size);
+
+	return map;
+}
+
+void * hashmap_set(hashmap_t * map, void * key, void * value) {
+	unsigned int hash = map->hash_func(key) % map->size;
+
+	hashmap_entry_t * x = map->entries[hash];
+	if (!x) {
+		hashmap_entry_t * e = malloc(sizeof(hashmap_entry_t));
+		e->key   = map->hash_key_dup(key);
+		e->value = value;
+		e->next = NULL;
+		map->entries[hash] = e;
+		return NULL;
+	} else {
+		hashmap_entry_t * p = NULL;
+		do {
+			if (map->hash_comp(x->key, key)) {
+				void * out = x->value;
+				x->value = value;
+				return out;
+			} else {
+				p = x;
+				x = x->next;
+			}
+		} while (x);
+		hashmap_entry_t * e = malloc(sizeof(hashmap_entry_t));
+		e->key   = map->hash_key_dup(key);
+		e->value = value;
+		e->next = NULL;
+
+		p->next = e;
+		return NULL;
+	}
+}
+
+void * hashmap_get(hashmap_t * map, void * key) {
+	unsigned int hash = map->hash_func(key) % map->size;
+
+	hashmap_entry_t * x = map->entries[hash];
+	if (!x) {
+		return NULL;
+	} else {
+		do {
+			if (map->hash_comp(x->key, key)) {
+				return x->value;
+			}
+			x = x->next;
+		} while (x);
+		return NULL;
+	}
+}
+
+void * hashmap_remove(hashmap_t * map, void * key) {
+	unsigned int hash = map->hash_func(key) % map->size;
+
+	hashmap_entry_t * x = map->entries[hash];
+	if (!x) {
+		return NULL;
+	} else {
+		if (map->hash_comp(x->key, key)) {
+			void * out = x->value;
+			map->entries[hash] = x->next;
+			map->hash_key_free(x->key);
+			map->hash_val_free(x);
+			return out;
+		} else {
+			hashmap_entry_t * p = x;
+			x = x->next;
+			do {
+				if (map->hash_comp(x->key, key)) {
+					void * out = x->value;
+					p->next = x->next;
+					map->hash_key_free(x->key);
+					map->hash_val_free(x);
+					return out;
+				}
+				p = x;
+				x = x->next;
+			} while (x);
+		}
+		return NULL;
+	}
+}
+
+int hashmap_has(hashmap_t * map, void * key) {
+	unsigned int hash = map->hash_func(key) % map->size;
+
+	hashmap_entry_t * x = map->entries[hash];
+	if (!x) {
+		return 0;
+	} else {
+		do {
+			if (map->hash_comp(x->key, key)) {
+				return 1;
+			}
+			x = x->next;
+		} while (x);
+		return 0;
+	}
+
+}
+
+list_t * hashmap_keys(hashmap_t * map) {
+	list_t * l = list_create();
+
+	for (unsigned int i = 0; i < map->size; ++i) {
+		hashmap_entry_t * x = map->entries[i];
+		while (x) {
+			list_insert(l, x->key);
+			x = x->next;
+		}
+	}
+
+	return l;
+}
+
+list_t * hashmap_values(hashmap_t * map) {
+	list_t * l = list_create();
+
+	for (unsigned int i = 0; i < map->size; ++i) {
+		hashmap_entry_t * x = map->entries[i];
+		while (x) {
+			list_insert(l, x->value);
+			x = x->next;
+		}
+	}
+
+	return l;
+}
+
+void hashmap_free(hashmap_t * map) {
+	for (unsigned int i = 0; i < map->size; ++i) {
+		hashmap_entry_t * x = map->entries[i], * p;
+		while (x) {
+			p = x;
+			x = x->next;
+			map->hash_key_free(p->key);
+			map->hash_val_free(p);
+		}
+	}
+	free(map->entries);
+}

+ 248 - 0
kernel/ds/list.c

@@ -0,0 +1,248 @@
+/* vim: tabstop=4 shiftwidth=4 noexpandtab
+ * This file is part of ToaruOS and is released under the terms
+ * of the NCSA / University of Illinois License - see LICENSE.md
+ * Copyright (C) 2011-2014 Kevin Lange
+ *
+ * General-purpose list implementations.
+ */
+
+#include "list.h"
+
+#ifdef _KERNEL_
+#	include <system.h>
+#else
+#	include <stddef.h>
+#	include <stdlib.h>
+#endif
+
+void list_destroy(list_t * list) {
+	/* Free all of the contents of a list */
+	node_t * n = list->head;
+	while (n) {
+		free(n->value);
+		n = n->next;
+	}
+}
+
+void list_free(list_t * list) {
+	/* Free the actual structure of a list */
+	node_t * n = list->head;
+	while (n) {
+		node_t * s = n->next;
+		free(n);
+		n = s;
+	}
+}
+
+void list_append(list_t * list, node_t * node) {
+	assert(!(node->next || node->prev) && "Node is already in a list.");
+	node->next = NULL;
+	/* Insert a node onto the end of a list */
+	node->owner = list;
+	if (!list->length) {
+		list->head = node;
+		list->tail = node;
+		node->prev = NULL;
+		node->next = NULL;
+		list->length++;
+		return;
+	}
+	list->tail->next = node;
+	node->prev = list->tail;
+	list->tail = node;
+	list->length++;
+}
+
+node_t * list_insert(list_t * list, void * item) {
+	/* Insert an item into a list */
+	node_t * node = malloc(sizeof(node_t));
+	node->value = item;
+	node->next  = NULL;
+	node->prev  = NULL;
+	node->owner = NULL;
+	list_append(list, node);
+
+	return node;
+}
+
+void list_append_after(list_t * list, node_t * before, node_t * node) {
+	assert(!(node->next || node->prev) && "Node is already in a list.");
+	node->owner = list;
+	if (!list->length) {
+		list_append(list, node);
+		return;
+	}
+	if (before == NULL) {
+		node->next = list->head;
+		node->prev = NULL;
+		list->head->prev = node;
+		list->head = node;
+		list->length++;
+		return;
+	}
+	if (before == list->tail) {
+		list->tail = node;
+	} else {
+		before->next->prev = node;
+		node->next = before->next;
+	}
+	node->prev = before;
+	before->next = node;
+	list->length++;
+}
+
+node_t * list_insert_after(list_t * list, node_t * before, void * item) {
+	node_t * node = malloc(sizeof(node_t));
+	node->value = item;
+	node->next  = NULL;
+	node->prev  = NULL;
+	node->owner = NULL;
+	list_append_after(list, before, node);
+	return node;
+}
+
+void list_append_before(list_t * list, node_t * after, node_t * node) {
+	assert(!(node->next || node->prev) && "Node is already in a list.");
+	node->owner = list;
+	if (!list->length) {
+		list_append(list, node);
+		return;
+	}
+	if (after == NULL) {
+		node->next = NULL;
+		node->prev = list->tail;
+		list->tail->next = node;
+		list->tail = node;
+		list->length++;
+		return;
+	}
+	if (after == list->head) {
+		list->head = node;
+	} else {
+		after->prev->next = node;
+		node->prev = after->prev;
+	}
+	node->next = after;
+	after->prev = node;
+	list->length++;
+}
+
+node_t * list_insert_before(list_t * list, node_t * after, void * item) {
+	node_t * node = malloc(sizeof(node_t));
+	node->value = item;
+	node->next  = NULL;
+	node->prev  = NULL;
+	node->owner = NULL;
+	list_append_before(list, after, node);
+	return node;
+}
+
+list_t * list_create(void) {
+	/* Create a fresh list */
+	list_t * out = malloc(sizeof(list_t));
+	out->head = NULL;
+	out->tail = NULL;
+	out->length = 0;
+	return out;
+}
+
+node_t * list_find(list_t * list, void * value) {
+	foreach(item, list) {
+		if (item->value == value) {
+			return item;
+		}
+	}
+	return NULL;
+}
+
+int list_index_of(list_t * list, void * value) {
+	int i = 0;
+	foreach(item, list) {
+		if (item->value == value) {
+			return i;
+		}
+		i++;
+	}
+	return -1; /* not find */
+}
+
+void list_remove(list_t * list, size_t index) {
+	/* remove index from the list */
+	if (index > list->length) return;
+	size_t i = 0;
+	node_t * n = list->head;
+	while (i < index) {
+		n = n->next;
+		i++;
+	}
+	list_delete(list, n);
+}
+
+void list_delete(list_t * list, node_t * node) {
+	/* remove node from the list */
+	assert(node->owner == list && "Tried to remove a list node from a list it does not belong to.");
+	if (node == list->head) {
+		list->head = node->next;
+	}
+	if (node == list->tail) {
+		list->tail = node->prev;
+	}
+	if (node->prev) {
+		node->prev->next = node->next;
+	}
+	if (node->next) {
+		node->next->prev = node->prev;
+	}
+	node->prev = NULL;
+	node->next = NULL;
+	node->owner = NULL;
+	list->length--;
+}
+
+node_t * list_pop(list_t * list) {
+	/* Remove and return the last value in the list
+	 * If you don't need it, you still probably want to free it!
+	 * Try free(list_pop(list)); !
+	 * */
+	if (!list->tail) return NULL;
+	node_t * out = list->tail;
+	list_delete(list, out);
+	return out;
+}
+
+node_t * list_dequeue(list_t * list) {
+	if (!list->head) return NULL;
+	node_t * out = list->head;
+	list_delete(list, out);
+	return out;
+}
+
+list_t * list_copy(list_t * original) {
+	/* Create a new copy of original */
+	list_t * out = list_create();
+	node_t * node = original->head;
+	while (node) {
+		list_insert(out, node->value);
+	}
+	return out;
+}
+
+void list_merge(list_t * target, list_t * source) {
+	/* Destructively merges source into target */
+	foreach(node, source) {
+		node->owner = target;
+	}
+	if (source->head) {
+		source->head->prev = target->tail;
+	}
+	if (target->tail) {
+		target->tail->next = source->head;
+	} else {
+		target->head = source->head;
+	}
+	if (source->tail) {
+		target->tail = source->tail;
+	}
+	target->length += source->length;
+	free(source);
+}

+ 170 - 0
kernel/ds/ringbuffer.c

@@ -0,0 +1,170 @@
+/* vim: tabstop=4 shiftwidth=4 noexpandtab
+ * This file is part of ToaruOS and is released under the terms
+ * of the NCSA / University of Illinois License - see LICENSE.md
+ * Copyright (C) 2013-2014 Kevin Lange
+ */
+#include <system.h>
+#include <ringbuffer.h>
+#include <process.h>
+
+size_t ring_buffer_unread(ring_buffer_t * ring_buffer) {
+	if (ring_buffer->read_ptr == ring_buffer->write_ptr) {
+		return 0;
+	}
+	if (ring_buffer->read_ptr > ring_buffer->write_ptr) {
+		return (ring_buffer->size - ring_buffer->read_ptr) + ring_buffer->write_ptr;
+	} else {
+		return (ring_buffer->write_ptr - ring_buffer->read_ptr);
+	}
+}
+
+size_t ring_buffer_size(fs_node_t * node) {
+	ring_buffer_t * ring_buffer = (ring_buffer_t *)node->device;
+	return ring_buffer_unread(ring_buffer);
+}
+
+size_t ring_buffer_available(ring_buffer_t * ring_buffer) {
+	if (ring_buffer->read_ptr == ring_buffer->write_ptr) {
+		return ring_buffer->size - 1;
+	}
+
+	if (ring_buffer->read_ptr > ring_buffer->write_ptr) {
+		return ring_buffer->read_ptr - ring_buffer->write_ptr - 1;
+	} else {
+		return (ring_buffer->size - ring_buffer->write_ptr) + ring_buffer->read_ptr - 1;
+	}
+}
+
+static inline void ring_buffer_increment_read(ring_buffer_t * ring_buffer) {
+	ring_buffer->read_ptr++;
+	if (ring_buffer->read_ptr == ring_buffer->size) {
+		ring_buffer->read_ptr = 0;
+	}
+}
+
+static inline void ring_buffer_increment_write(ring_buffer_t * ring_buffer) {
+	ring_buffer->write_ptr++;
+	if (ring_buffer->write_ptr == ring_buffer->size) {
+		ring_buffer->write_ptr = 0;
+	}
+}
+
+static void ring_buffer_alert_waiters(ring_buffer_t * ring_buffer) {
+	if (ring_buffer->alert_waiters) {
+		while (ring_buffer->alert_waiters->head) {
+			node_t * node = list_dequeue(ring_buffer->alert_waiters);
+			process_t * p = node->value;
+			process_alert_node(p, ring_buffer);
+			free(node);
+		}
+	}
+}
+
+void ring_buffer_select_wait(ring_buffer_t * ring_buffer, void * process) {
+	if (!ring_buffer->alert_waiters) {
+		ring_buffer->alert_waiters = list_create();
+	}
+
+	if (!list_find(ring_buffer->alert_waiters, process)) {
+		list_insert(ring_buffer->alert_waiters, process);
+	}
+	list_insert(((process_t *)process)->node_waits, ring_buffer);
+}
+
+size_t ring_buffer_read(ring_buffer_t * ring_buffer, size_t size, uint8_t * buffer) {
+	size_t collected = 0;
+	while (collected == 0) {
+		spin_lock(ring_buffer->lock);
+		while (ring_buffer_unread(ring_buffer) > 0 && collected < size) {
+			buffer[collected] = ring_buffer->buffer[ring_buffer->read_ptr];
+			ring_buffer_increment_read(ring_buffer);
+			collected++;
+		}
+		spin_unlock(ring_buffer->lock);
+		wakeup_queue(ring_buffer->wait_queue_writers);
+		if (collected == 0) {
+			if (sleep_on(ring_buffer->wait_queue_readers) && ring_buffer->internal_stop) {
+				ring_buffer->internal_stop = 0;
+				break;
+			}
+		}
+	}
+	wakeup_queue(ring_buffer->wait_queue_writers);
+	return collected;
+}
+
+size_t ring_buffer_write(ring_buffer_t * ring_buffer, size_t size, uint8_t * buffer) {
+	size_t written = 0;
+	while (written < size) {
+		spin_lock(ring_buffer->lock);
+
+		while (ring_buffer_available(ring_buffer) > 0 && written < size) {
+			ring_buffer->buffer[ring_buffer->write_ptr] = buffer[written];
+			ring_buffer_increment_write(ring_buffer);
+			written++;
+		}
+
+		spin_unlock(ring_buffer->lock);
+		wakeup_queue(ring_buffer->wait_queue_readers);
+		ring_buffer_alert_waiters(ring_buffer);
+		if (written < size) {
+			if (ring_buffer->discard) {
+				break;
+			}
+			if (sleep_on(ring_buffer->wait_queue_writers) && ring_buffer->internal_stop) {
+				ring_buffer->internal_stop = 0;
+				break;
+			}
+		}
+	}
+
+	wakeup_queue(ring_buffer->wait_queue_readers);
+	ring_buffer_alert_waiters(ring_buffer);
+	return written;
+}
+
+ring_buffer_t * ring_buffer_create(size_t size) {
+	ring_buffer_t * out = malloc(sizeof(ring_buffer_t));
+
+	out->buffer     = malloc(size);
+	out->write_ptr  = 0;
+	out->read_ptr   = 0;
+	out->size       = size;
+	out->alert_waiters = NULL;
+
+	spin_init(out->lock);
+
+	out->internal_stop = 0;
+	out->discard = 0;
+
+	out->wait_queue_readers = list_create();
+	out->wait_queue_writers = list_create();
+
+	return out;
+}
+
+void ring_buffer_destroy(ring_buffer_t * ring_buffer) {
+	free(ring_buffer->buffer);
+
+	wakeup_queue(ring_buffer->wait_queue_writers);
+	wakeup_queue(ring_buffer->wait_queue_readers);
+	ring_buffer_alert_waiters(ring_buffer);
+
+	list_free(ring_buffer->wait_queue_writers);
+	list_free(ring_buffer->wait_queue_readers);
+
+	free(ring_buffer->wait_queue_writers);
+	free(ring_buffer->wait_queue_readers);
+
+	if (ring_buffer->alert_waiters) {
+		list_free(ring_buffer->alert_waiters);
+		free(ring_buffer->alert_waiters);
+	}
+}
+
+void ring_buffer_interrupt(ring_buffer_t * ring_buffer) {
+	ring_buffer->internal_stop = 1;
+	wakeup_queue_interrupted(ring_buffer->wait_queue_readers);
+	wakeup_queue_interrupted(ring_buffer->wait_queue_writers);
+}
+

+ 192 - 0
kernel/ds/tree.c

@@ -0,0 +1,192 @@
+/* vim: tabstop=4 shiftwidth=4 noexpandtab
+ * This file is part of ToaruOS and is released under the terms
+ * of the NCSA / University of Illinois License - see LICENSE.md
+ * Copyright (C) 2011-2014 Kevin Lange
+ *
+ * General-purpose tree implementation
+ */
+
+#include "tree.h"
+
+#ifdef _KERNEL_
+#	include <system.h>
+#else
+#	include <stddef.h>
+#	include <stdlib.h>
+#endif
+
+tree_t * tree_create(void) {
+	/* Create a new tree */
+	tree_t * out = malloc(sizeof(tree_t));
+	out->nodes  = 0;
+	out->root   = NULL;
+	return out;
+}
+
+void tree_set_root(tree_t * tree, void * value) {
+	/* Set the root node for a new tree. */
+	tree_node_t * root = tree_node_create(value);
+	tree->root = root;
+	tree->nodes = 1;
+}
+
+void tree_node_destroy(tree_node_t * node) {
+	/* Free the contents of a node and its children, but not the nodes themselves */
+	foreach(child, node->children) {
+		tree_node_destroy((tree_node_t *)child->value);
+	}
+	free(node->value);
+}
+
+void tree_destroy(tree_t * tree) {
+	/* Free the contents of a tree, but not the nodes */
+	if (tree->root) {
+		tree_node_destroy(tree->root);
+	}
+}
+
+void tree_node_free(tree_node_t * node) {
+	/* Free a node and its children, but not their contents */
+	if (!node) return;
+	foreach(child, node->children) {
+		tree_node_free(child->value);
+	}
+	free(node);
+}
+
+void tree_free(tree_t * tree) {
+	/* Free all of the nodes in a tree, but not their contents */
+	tree_node_free(tree->root);
+}
+
+tree_node_t * tree_node_create(void * value) {
+	/* Create a new tree node pointing to the given value */
+	tree_node_t * out = malloc(sizeof(tree_node_t));
+	out->value = value;
+	out->children = list_create();
+	out->parent = NULL;
+	return out;
+}
+
+void tree_node_insert_child_node(tree_t * tree, tree_node_t * parent, tree_node_t * node) {
+	/* Insert a node as a child of parent */
+	list_insert(parent->children, node);
+	node->parent = parent;
+	tree->nodes++;
+}
+
+tree_node_t * tree_node_insert_child(tree_t * tree, tree_node_t * parent, void * value) {
+	/* Insert a (fresh) node as a child of parent */
+	tree_node_t * out = tree_node_create(value);
+	tree_node_insert_child_node(tree, parent, out);
+	return out;
+}
+
+tree_node_t * tree_node_find_parent(tree_node_t * haystack, tree_node_t * needle) {
+	/* Recursive node part of tree_find_parent */
+	tree_node_t * found = NULL;
+	foreach(child, haystack->children) {
+		if (child->value == needle) {
+			return haystack;
+		}
+		found = tree_node_find_parent((tree_node_t *)child->value, needle);
+		if (found) {
+			break;
+		}
+	}
+	return found;
+}
+
+tree_node_t * tree_find_parent(tree_t * tree, tree_node_t * node) {
+	/* Return the parent of a node, inefficiently. */
+	if (!tree->root) return NULL;
+	return tree_node_find_parent(tree->root, node);
+}
+
+size_t tree_count_children(tree_node_t * node) {
+	/* return the number of children this node has */
+	if (!node) return 0;
+	if (!node->children) return 0;
+	size_t out = node->children->length;
+	foreach(child, node->children) {
+		out += tree_count_children((tree_node_t *)child->value);
+	}
+	return out;
+}
+
+void tree_node_parent_remove(tree_t * tree, tree_node_t * parent, tree_node_t * node) {
+	/* remove a node when we know its parent; update node counts for the tree */
+	tree->nodes -= tree_count_children(node) + 1;
+	list_delete(parent->children, list_find(parent->children, node));
+	tree_node_free(node);
+}
+
+void tree_node_remove(tree_t * tree, tree_node_t * node) {
+	/* remove an entire branch given its root */
+	tree_node_t * parent = node->parent;
+	if (!parent) {
+		if (node == tree->root) {
+			tree->nodes = 0;
+			tree->root  = NULL;
+			tree_node_free(node);
+		}
+	}
+	tree_node_parent_remove(tree, parent, node);
+}
+
+void tree_remove(tree_t * tree, tree_node_t * node) {
+	/* Remove this node and move its children into its parent's list of children */
+	tree_node_t * parent = node->parent;
+	/* This is something we just can't do. We don't know how to merge our
+	 * children into our "parent" because then we'd have more than one root node.
+	 * A good way to think about this is actually what this tree struct
+	 * primarily exists for: processes. Trying to remove the root is equivalent
+	 * to trying to kill init! Which is bad. We immediately fault on such
+	 * a case anyway ("Tried to kill init, shutting down!").
+	 */
+	if (!parent) return;
+	tree->nodes--;
+	list_delete(parent->children, list_find(parent->children, node));
+	foreach(child, node->children) {
+		/* Reassign the parents */
+		((tree_node_t *)child->value)->parent = parent;
+	}
+	list_merge(parent->children, node->children);
+	free(node);
+}
+
+void tree_remove_reparent_root(tree_t * tree, tree_node_t * node) {
+	/* Remove this node and move its children into the root children */
+	tree_node_t * parent = node->parent;
+	if (!parent) return;
+	tree->nodes--;
+	list_delete(parent->children, list_find(parent->children, node));
+	foreach(child, node->children) {
+		/* Reassign the parents */
+		((tree_node_t *)child->value)->parent = tree->root;
+	}
+	list_merge(tree->root->children, node->children);
+	free(node);
+}
+
+void tree_break_off(tree_t * tree, tree_node_t * node) {
+	tree_node_t * parent = node->parent;
+	if (!parent) return;
+	list_delete(parent->children, list_find(parent->children, node));
+}
+
+tree_node_t * tree_node_find(tree_node_t * node, void * search, tree_comparator_t comparator) {
+	if (comparator(node->value,search)) {
+		return node;
+	}
+	tree_node_t * found;
+	foreach(child, node->children) {
+		found = tree_node_find((tree_node_t *)child->value, search, comparator);
+		if (found) return found;
+	}
+	return NULL;
+}
+
+tree_node_t * tree_find(tree_t * tree, void * value, tree_comparator_t comparator) {
+	return tree_node_find(tree->root, value, comparator);
+}

+ 296 - 0
kernel/fs/pipe.c

@@ -0,0 +1,296 @@
+/* vim: tabstop=4 shiftwidth=4 noexpandtab
+ * This file is part of ToaruOS and is released under the terms
+ * of the NCSA / University of Illinois License - see LICENSE.md
+ * Copyright (C) 2012-2014 Kevin Lange
+ *
+ * Buffered Pipe
+ *
+ */
+
+#include <system.h>
+#include <fs.h>
+#include <printf.h>
+#include <pipe.h>
+#include <logging.h>
+
+#define DEBUG_PIPES 0
+
+uint32_t read_pipe(fs_node_t *node, uint32_t offset, uint32_t size, uint8_t *buffer);
+uint32_t write_pipe(fs_node_t *node, uint32_t offset, uint32_t size, uint8_t *buffer);
+void open_pipe(fs_node_t *node, unsigned int flags);
+void close_pipe(fs_node_t *node);
+
+static inline size_t pipe_unread(pipe_device_t * pipe) {
+	if (pipe->read_ptr == pipe->write_ptr) {
+		return 0;
+	}
+	if (pipe->read_ptr > pipe->write_ptr) {
+		return (pipe->size - pipe->read_ptr) + pipe->write_ptr;
+	} else {
+		return (pipe->write_ptr - pipe->read_ptr);
+	}
+}
+
+int pipe_size(fs_node_t * node) {
+	pipe_device_t * pipe = (pipe_device_t *)node->device;
+	return pipe_unread(pipe);
+}
+
+static inline size_t pipe_available(pipe_device_t * pipe) {
+	if (pipe->read_ptr == pipe->write_ptr) {
+		return pipe->size - 1;
+	}
+
+	if (pipe->read_ptr > pipe->write_ptr) {
+		return pipe->read_ptr - pipe->write_ptr - 1;
+	} else {
+		return (pipe->size - pipe->write_ptr) + pipe->read_ptr - 1;
+	}
+}
+
+int pipe_unsize(fs_node_t * node) {
+	pipe_device_t * pipe = (pipe_device_t *)node->device;
+	return pipe_available(pipe);
+}
+
+static inline void pipe_increment_read(pipe_device_t * pipe) {
+	pipe->read_ptr++;
+	if (pipe->read_ptr == pipe->size) {
+		pipe->read_ptr = 0;
+	}
+}
+
+static inline void pipe_increment_write(pipe_device_t * pipe) {
+	pipe->write_ptr++;
+	if (pipe->write_ptr == pipe->size) {
+		pipe->write_ptr = 0;
+	}
+}
+
+static inline void pipe_increment_write_by(pipe_device_t * pipe, size_t amount) {
+	pipe->write_ptr = (pipe->write_ptr + amount) % pipe->size;
+}
+
+static void pipe_alert_waiters(pipe_device_t * pipe) {
+	if (pipe->alert_waiters) {
+		while (pipe->alert_waiters->head) {
+			node_t * node = list_dequeue(pipe->alert_waiters);
+			process_t * p = node->value;
+			process_alert_node(p, pipe);
+			free(node);
+		}
+	}
+}
+
+uint32_t read_pipe(fs_node_t *node, uint32_t offset, uint32_t size, uint8_t *buffer) {
+	assert(node->device != 0 && "Attempted to read from a fully-closed pipe.");
+
+	/* Retreive the pipe object associated with this file node */
+	pipe_device_t * pipe = (pipe_device_t *)node->device;
+
+#if DEBUG_PIPES
+	if (pipe->size > 300) { /* Ignore small pipes (ie, keyboard) */
+		debug_print(INFO, "[debug] Call to read from pipe 0x%x", node->device);
+		debug_print(INFO, "        Unread bytes:    %d", pipe_unread(pipe));
+		debug_print(INFO, "        Total size:      %d", pipe->size);
+		debug_print(INFO, "        Request size:    %d", size);
+		debug_print(INFO, "        Write pointer:   %d", pipe->write_ptr);
+		debug_print(INFO, "        Read  pointer:   %d", pipe->read_ptr);
+		debug_print(INFO, "        Buffer address:  0x%x", pipe->buffer);
+	}
+#endif
+
+	if (pipe->dead) {
+		debug_print(WARNING, "Pipe is dead?");
+		send_signal(getpid(), SIGPIPE);
+		return 0;
+	}
+
+	size_t collected = 0;
+	while (collected == 0) {
+		spin_lock(pipe->lock_read);
+		while (pipe_unread(pipe) > 0 && collected < size) {
+			buffer[collected] = pipe->buffer[pipe->read_ptr];
+			pipe_increment_read(pipe);
+			collected++;
+		}
+		spin_unlock(pipe->lock_read);
+		wakeup_queue(pipe->wait_queue_writers);
+		/* Deschedule and switch */
+		if (collected == 0) {
+			sleep_on(pipe->wait_queue_readers);
+		}
+	}
+
+	return collected;
+}
+
+uint32_t write_pipe(fs_node_t *node, uint32_t offset, uint32_t size, uint8_t *buffer) {
+	assert(node->device != 0 && "Attempted to write to a fully-closed pipe.");
+
+	/* Retreive the pipe object associated with this file node */
+	pipe_device_t * pipe = (pipe_device_t *)node->device;
+
+#if DEBUG_PIPES
+	if (pipe->size > 300) { /* Ignore small pipes (ie, keyboard) */
+		debug_print(INFO, "[debug] Call to write to pipe 0x%x", node->device);
+		debug_print(INFO, "        Available space: %d", pipe_available(pipe));
+		debug_print(INFO, "        Total size:      %d", pipe->size);
+		debug_print(INFO, "        Request size:    %d", size);
+		debug_print(INFO, "        Write pointer:   %d", pipe->write_ptr);
+		debug_print(INFO, "        Read  pointer:   %d", pipe->read_ptr);
+		debug_print(INFO, "        Buffer address:  0x%x", pipe->buffer);
+		debug_print(INFO, " Write: %s", buffer);
+	}
+#endif
+
+	if (pipe->dead) {
+		debug_print(WARNING, "Pipe is dead?");
+		send_signal(getpid(), SIGPIPE);
+		return 0;
+	}
+
+	size_t written = 0;
+	while (written < size) {
+		spin_lock(pipe->lock_write);
+
+#if 0
+		size_t available = 0;
+		if (pipe->read_ptr <= pipe->write_ptr) {
+			available = pipe->size - pipe->write_ptr;
+		} else {
+			available = pipe->read_ptr - pipe->write_ptr - 1;
+		}
+		if (available) {
+			available = min(available, size - written);
+			memcpy(&pipe->buffer[pipe->write_ptr], buffer, available);
+			pipe_increment_write_by(pipe, available);
+			written += available;
+		}
+#else
+		while (pipe_available(pipe) > 0 && written < size) {
+			pipe->buffer[pipe->write_ptr] = buffer[written];
+			pipe_increment_write(pipe);
+			written++;
+		}
+#endif
+
+		spin_unlock(pipe->lock_write);
+		wakeup_queue(pipe->wait_queue_readers);
+		pipe_alert_waiters(pipe);
+		if (written < size) {
+			sleep_on(pipe->wait_queue_writers);
+		}
+	}
+
+	return written;
+}
+
+void open_pipe(fs_node_t * node, unsigned int flags) {
+	assert(node->device != 0 && "Attempted to open a fully-closed pipe.");
+
+	/* Retreive the pipe object associated with this file node */
+	pipe_device_t * pipe = (pipe_device_t *)node->device;
+
+	/* Add a reference */
+	pipe->refcount++;
+
+	return;
+}
+
+void close_pipe(fs_node_t * node) {
+	assert(node->device != 0 && "Attempted to close an already fully-closed pipe.");
+
+	/* Retreive the pipe object associated with this file node */
+	pipe_device_t * pipe = (pipe_device_t *)node->device;
+
+	/* Drop one reference */
+	pipe->refcount--;
+
+	/* Check the reference count number */
+	if (pipe->refcount == 0) {
+#if 0
+		/* No other references exist, free the pipe (but not its buffer) */
+		free(pipe->buffer);
+		list_free(pipe->wait_queue);
+		free(pipe->wait_queue);
+		free(pipe);
+		/* And let the creator know there are no more references */
+		node->device = 0;
+#endif
+	}
+
+	return;
+}
+
+static int pipe_check(fs_node_t * node) {
+	pipe_device_t * pipe = (pipe_device_t *)node->device;
+
+	if (pipe_unread(pipe) > 0) {
+		return 0;
+	}
+
+	return 1;
+}
+
+static int pipe_wait(fs_node_t * node, void * process) {
+	pipe_device_t * pipe = (pipe_device_t *)node->device;
+
+	if (!pipe->alert_waiters) {
+		pipe->alert_waiters = list_create();
+	}
+
+	if (!list_find(pipe->alert_waiters, process)) {
+		list_insert(pipe->alert_waiters, process);
+	}
+	list_insert(((process_t *)process)->node_waits, pipe);
+
+	return 0;
+}
+
+fs_node_t * make_pipe(size_t size) {
+	fs_node_t * fnode = malloc(sizeof(fs_node_t));
+	pipe_device_t * pipe = malloc(sizeof(pipe_device_t));
+	memset(fnode, 0, sizeof(fs_node_t));
+	memset(pipe, 0, sizeof(pipe_device_t));
+
+	fnode->device = 0;
+	fnode->name[0] = '\0';
+	sprintf(fnode->name, "[pipe]");
+	fnode->uid   = 0;
+	fnode->gid   = 0;
+	fnode->mask  = 0666;
+	fnode->flags = FS_PIPE;
+	fnode->read  = read_pipe;
+	fnode->write = write_pipe;
+	fnode->open  = open_pipe;
+	fnode->close = close_pipe;
+	fnode->readdir = NULL;
+	fnode->finddir = NULL;
+	fnode->ioctl   = NULL; /* TODO ioctls for pipes? maybe */
+	fnode->get_size = pipe_size;
+
+	fnode->selectcheck = pipe_check;
+	fnode->selectwait  = pipe_wait;
+
+	fnode->atime = now();
+	fnode->mtime = fnode->atime;
+	fnode->ctime = fnode->atime;
+
+	fnode->device = pipe;
+
+	pipe->buffer    = malloc(size);
+	pipe->write_ptr = 0;
+	pipe->read_ptr  = 0;
+	pipe->size      = size;
+	pipe->refcount  = 0;
+	pipe->dead      = 0;
+
+	spin_init(pipe->lock_read);
+	spin_init(pipe->lock_write);
+
+	pipe->wait_queue_writers = list_create();
+	pipe->wait_queue_readers = list_create();
+
+	return fnode;
+}

+ 110 - 0
kernel/fs/ramdisk.c

@@ -0,0 +1,110 @@
+/* vim: tabstop=4 shiftwidth=4 noexpandtab
+ * This file is part of ToaruOS and is released under the terms
+ * of the NCSA / University of Illinois License - see LICENSE.md
+ * Copyright (C) 2014 Kevin Lange
+  *
+ * Ramdisk driver.
+ *
+ * Provide raw block access to files loaded into kernel memory.
+ */
+
+#include <system.h>
+#include <logging.h>
+#include <module.h>
+#include <fs.h>
+#include <printf.h>
+#include <mem.h>
+
+static uint32_t read_ramdisk(fs_node_t *node, uint32_t offset, uint32_t size, uint8_t *buffer);
+static uint32_t write_ramdisk(fs_node_t *node, uint32_t offset, uint32_t size, uint8_t *buffer);
+static void     open_ramdisk(fs_node_t *node, unsigned int flags);
+static void     close_ramdisk(fs_node_t *node);
+
+static uint32_t read_ramdisk(fs_node_t *node, uint32_t offset, uint32_t size, uint8_t *buffer) {
+
+	if (offset > node->length) {
+		return 0;
+	}
+
+	if (offset + size > node->length) {
+		unsigned int i = node->length - offset;
+		size = i;
+	}
+
+	memcpy(buffer, (void *)(node->inode + offset), size);
+
+	return size;
+}
+
+static uint32_t write_ramdisk(fs_node_t *node, uint32_t offset, uint32_t size, uint8_t *buffer) {
+	if (offset > node->length) {
+		return 0;
+	}
+
+	if (offset + size > node->length) {
+		unsigned int i = node->length - offset;
+		size = i;
+	}
+
+	memcpy((void *)(node->inode + offset), buffer, size);
+	return size;
+}
+
+static void open_ramdisk(fs_node_t * node, unsigned int flags) {
+	return;
+}
+
+static void close_ramdisk(fs_node_t * node) {
+	return;
+}
+
+static int ioctl_ramdisk(fs_node_t * node, int request, void * argp) {
+	switch (request) {
+		case 0x4001:
+			if (current_process->user != 0) {
+				return -EPERM;
+			} else {
+				/* Clear all of the memory used by this ramdisk */
+				for (uintptr_t i = node->inode; i < node->inode + node->length; i += 0x1000) {
+					clear_frame(i);
+				}
+				/* Mark the file length as 0 */
+				node->length = 0;
+				return 0;
+			}
+		default:
+			return -EINVAL;
+	}
+}
+
+static fs_node_t * ramdisk_device_create(int device_number, uintptr_t location, size_t size) {
+	fs_node_t * fnode = malloc(sizeof(fs_node_t));
+	memset(fnode, 0x00, sizeof(fs_node_t));
+	fnode->inode = location;
+	sprintf(fnode->name, "ram%d", device_number);
+	fnode->uid = 0;
+	fnode->gid = 0;
+	fnode->mask    = 0660;
+	fnode->length  = size;
+	fnode->flags   = FS_BLOCKDEVICE;
+	fnode->read    = read_ramdisk;
+	fnode->write   = write_ramdisk;
+	fnode->open    = open_ramdisk;
+	fnode->close   = close_ramdisk;
+	fnode->ioctl   = ioctl_ramdisk;
+	return fnode;
+}
+
+static int last_device_number = 0;
+fs_node_t * ramdisk_mount(uintptr_t location, size_t size) {
+	fs_node_t * ramdisk = ramdisk_device_create(last_device_number, location, size);
+	if (ramdisk) {
+		char tmp[64];
+		sprintf(tmp, "/dev/%s", ramdisk->name);
+		vfs_mount(tmp, ramdisk);
+		last_device_number += 1;
+		return ramdisk;
+	}
+
+	return NULL;
+}

+ 435 - 0
kernel/fs/tty.c

@@ -0,0 +1,435 @@
+/* vim: tabstop=4 shiftwidth=4 noexpandtab
+ * This file is part of ToaruOS and is released under the terms
+ * of the NCSA / University of Illinois License - see LICENSE.md
+ * Copyright (C) 2013-2014 Kevin Lange
+ */
+#include <system.h>
+#include <fs.h>
+#include <pipe.h>
+#include <logging.h>
+#include <printf.h>
+
+#include <ioctl.h>
+#include <termios.h>
+#include <ringbuffer.h>
+
+#define TTY_BUFFER_SIZE 4096
+//4096
+
+typedef struct pty {
+	/* the PTY number */
+	int            name;
+
+	/* Master and slave endpoints */
+	fs_node_t *    master;
+	fs_node_t *    slave;
+
+	/* term io "window size" struct (width/height) */
+	struct winsize size;
+
+	/* termios data structure */
+	struct termios tios;
+
+	/* directional pipes */
+	ring_buffer_t * in;
+	ring_buffer_t * out;
+
+	char * canon_buffer;
+	size_t canon_bufsize;
+	size_t canon_buflen;
+
+	pid_t ct_proc; /* Controlling process (shell) */
+	pid_t fg_proc; /* Foreground process (might also be shell) */
+
+} pty_t;
+
+list_t * pty_list = NULL;
+
+#define IN(character)   ring_buffer_write(pty->in, 1, (uint8_t *)&(character))
+#define OUT(character)  ring_buffer_write(pty->out, 1, (uint8_t *)&(character))
+
+static void dump_input_buffer(pty_t * pty) {
+	char * c = pty->canon_buffer;
+	while (pty->canon_buflen > 0) {
+		IN(*c);
+		pty->canon_buflen--;
+		c++;
+	}
+}
+
+static void clear_input_buffer(pty_t * pty) {
+	pty->canon_buflen = 0;
+	pty->canon_buffer[0] = '\0';
+}
+
+static void output_process(pty_t * pty, uint8_t c) {
+	if (ring_buffer_available(pty->out) < 2) return; /* uh oh */
+	if (c == '\n' && (pty->tios.c_oflag & ONLCR)) {
+		uint8_t d = '\r';
+		OUT(d);
+	}
+	OUT(c);
+}
+
+static void output_process_slave(pty_t * pty, uint8_t c) {
+	if (c == '\n' && (pty->tios.c_oflag & ONLCR)) {
+		uint8_t d = '\r';
+		OUT(d);
+	}
+	OUT(c);
+}
+
+static void input_process(pty_t * pty, uint8_t c) {
+	if (pty->tios.c_lflag & ICANON) {
+		if (c == pty->tios.c_cc[VKILL]) {
+			while (pty->canon_buflen > 0) {
+				pty->canon_buflen--;
+				pty->canon_buffer[pty->canon_buflen] = '\0';
+				if (pty->tios.c_lflag & ECHO) {
+					output_process(pty, '\010');
+					output_process(pty, ' ');
+					output_process(pty, '\010');
+				}
+			}
+			return;
+		}
+		if (c == pty->tios.c_cc[VERASE]) {
+			/* Backspace */
+			if (pty->canon_buflen > 0) {
+				pty->canon_buflen--;
+				pty->canon_buffer[pty->canon_buflen] = '\0';
+				if (pty->tios.c_lflag & ECHO) {
+					output_process(pty, '\010');
+					output_process(pty, ' ');
+					output_process(pty, '\010');
+				}
+			}
+			return;
+		}
+		if (c == pty->tios.c_cc[VINTR]) {
+			if (pty->tios.c_lflag & ECHO) {
+				output_process(pty, '^');
+				output_process(pty, '@' + c);
+				output_process(pty, '\n');
+			}
+			clear_input_buffer(pty);
+			if (pty->fg_proc) {
+				send_signal(pty->fg_proc, SIGINT);
+			}
+			return;
+		}
+		if (c == pty->tios.c_cc[VQUIT]) {
+			if (pty->tios.c_lflag & ECHO) {
+				output_process(pty, '^');
+				output_process(pty, '@' + c);
+				output_process(pty, '\n');
+			}
+			clear_input_buffer(pty);
+			if (pty->fg_proc) {
+				send_signal(pty->fg_proc, SIGQUIT);
+			}
+			return;
+		}
+		if (c == pty->tios.c_cc[VEOF]) {
+			if (pty->canon_buflen) {
+				dump_input_buffer(pty);
+			} else {
+				ring_buffer_interrupt(pty->in);
+			}
+			return;
+		}
+		if (pty->canon_buflen < pty->canon_bufsize) {
+			pty->canon_buffer[pty->canon_buflen] = c;
+			pty->canon_buflen++;
+		}
+		if (pty->tios.c_lflag & ECHO) {
+			output_process(pty, c);
+		}
+		if (c == '\n') {
+			pty->canon_buffer[pty->canon_buflen-1] = c;
+			dump_input_buffer(pty);
+			return;
+		}
+		return;
+	} else if (pty->tios.c_lflag & ECHO) {
+		output_process(pty, c);
+	}
+	IN(c);
+}
+
+int pty_ioctl(pty_t * pty, int request, void * argp) {
+	switch (request) {
+		case IOCTLDTYPE:
+			/*
+			 * This is a special toaru-specific call to get a simple
+			 * integer that describes the kind of device this is.
+			 * It's more specific than just "character device" or "file",
+			 * but for here we just need to say we're a TTY.
+			 */
+			return IOCTL_DTYPE_TTY;
+		case TIOCSWINSZ:
+			if (!argp) return -1;
+			validate(argp);
+			memcpy(&pty->size, argp, sizeof(struct winsize));
+			/* TODO send sigwinch to fg_prog */
+			return 0;
+		case TIOCGWINSZ:
+			if (!argp) return -1;
+			validate(argp);
+			memcpy(argp, &pty->size, sizeof(struct winsize));
+			return 0;
+		case TCGETS:
+			if (!argp) return -1;
+			validate(argp);
+			memcpy(argp, &pty->tios, sizeof(struct termios));
+			return 0;
+		case TIOCSPGRP:
+			if (!argp) return -1;
+			validate(argp);
+			pty->fg_proc = *(pid_t *)argp;
+			debug_print(NOTICE, "Setting PTY group to %d", pty->fg_proc);
+			return 0;
+		case TIOCGPGRP:
+			if (!argp) return -1;
+			validate(argp);
+			*(pid_t *)argp = pty->fg_proc;
+			return 0;
+		case TCSETS:
+		case TCSETSW:
+		case TCSETSF:
+			if (!argp) return -1;
+			validate(argp);
+			if (!(((struct termios *)argp)->c_lflag & ICANON) && (pty->tios.c_lflag & ICANON)) {
+				/* Switch out of canonical mode, the dump the input buffer */
+				dump_input_buffer(pty);
+			}
+			memcpy(&pty->tios, argp, sizeof(struct termios));
+			return 0;
+		default:
+			return -EINVAL;
+	}
+}
+
+uint32_t  read_pty_master(fs_node_t * node, uint32_t offset, uint32_t size, uint8_t *buffer) {
+	pty_t * pty = (pty_t *)node->device;
+
+	/* Standard pipe read */
+	return ring_buffer_read(pty->out, size, buffer);
+}
+uint32_t write_pty_master(fs_node_t * node, uint32_t offset, uint32_t size, uint8_t *buffer) {
+	pty_t * pty = (pty_t *)node->device;
+
+	size_t l = 0;
+	for (uint8_t * c = buffer; l < size; ++c, ++l) {
+		input_process(pty, *c);
+	}
+
+	return l;
+}
+void      open_pty_master(fs_node_t * node, unsigned int flags) {
+	return;
+}
+void     close_pty_master(fs_node_t * node) {
+	return;
+}
+
+uint32_t  read_pty_slave(fs_node_t * node, uint32_t offset, uint32_t size, uint8_t *buffer) {
+	pty_t * pty = (pty_t *)node->device;
+
+	if (pty->tios.c_lflag & ICANON) {
+		return ring_buffer_read(pty->in, size, buffer);
+	} else {
+		if (pty->tios.c_cc[VMIN] == 0) {
+			return ring_buffer_read(pty->in, MIN(size, ring_buffer_unread(pty->in)), buffer);
+		} else {
+			return ring_buffer_read(pty->in, MIN(pty->tios.c_cc[VMIN], size), buffer);
+		}
+	}
+}
+
+uint32_t write_pty_slave(fs_node_t * node, uint32_t offset, uint32_t size, uint8_t *buffer) {
+	pty_t * pty = (pty_t *)node->device;
+
+	size_t l = 0;
+	for (uint8_t * c = buffer; l < size; ++c, ++l) {
+		output_process_slave(pty, *c);
+	}
+
+	return l;
+}
+void      open_pty_slave(fs_node_t * node, unsigned int flags) {
+	return;
+}
+void     close_pty_slave(fs_node_t * node) {
+	return;
+}
+
+/*
+ * These are separate functions just in case I ever feel the need to do
+ * things differently in the slave or master.
+ */
+int ioctl_pty_master(fs_node_t * node, int request, void * argp) {
+	pty_t * pty = (pty_t *)node->device;
+	return pty_ioctl(pty, request, argp);
+}
+
+int ioctl_pty_slave(fs_node_t * node, int request, void * argp) {
+	pty_t * pty = (pty_t *)node->device;
+	return pty_ioctl(pty, request, argp);
+}
+
+int pty_available_input(fs_node_t * node) {
+	pty_t * pty = (pty_t *)node->device;
+	return ring_buffer_unread(pty->in);
+}
+
+int pty_available_output(fs_node_t * node) {
+	pty_t * pty = (pty_t *)node->device;
+	return ring_buffer_unread(pty->out);
+}
+
+static int check_pty_master(fs_node_t * node) {
+	pty_t * pty = (pty_t *)node->device;
+	if (ring_buffer_unread(pty->out) > 0) {
+		return 0;
+	}
+	return 1;
+}
+
+static int check_pty_slave(fs_node_t * node) {
+	pty_t * pty = (pty_t *)node->device;
+	if (ring_buffer_unread(pty->in) > 0) {
+		return 0;
+	}
+	return 1;
+}
+
+static int wait_pty_master(fs_node_t * node, void * process) {
+	pty_t * pty = (pty_t *)node->device;
+	ring_buffer_select_wait(pty->out, process);
+	return 0;
+}
+
+static int wait_pty_slave(fs_node_t * node, void * process) {
+	pty_t * pty = (pty_t *)node->device;
+	ring_buffer_select_wait(pty->in, process);
+	return 0;
+}
+
+fs_node_t * pty_master_create(pty_t * pty) {
+	fs_node_t * fnode = malloc(sizeof(fs_node_t));
+	memset(fnode, 0x00, sizeof(fs_node_t));
+
+	fnode->name[0] = '\0';
+	sprintf(fnode->name, "pty master");
+	fnode->uid   = 0;
+	fnode->gid   = 0;
+	fnode->mask  = 0666;
+	fnode->flags = FS_PIPE;
+	fnode->read  =  read_pty_master;
+	fnode->write = write_pty_master;
+	fnode->open  =  open_pty_master;
+	fnode->close = close_pty_master;
+	fnode->selectcheck = check_pty_master;
+	fnode->selectwait  = wait_pty_master;
+	fnode->readdir = NULL;
+	fnode->finddir = NULL;
+	fnode->ioctl = ioctl_pty_master;
+	fnode->get_size = pty_available_output;
+
+	fnode->device = pty;
+
+	return fnode;
+}
+
+fs_node_t * pty_slave_create(pty_t * pty) {
+	fs_node_t * fnode = malloc(sizeof(fs_node_t));
+	memset(fnode, 0x00, sizeof(fs_node_t));
+
+	fnode->name[0] = '\0';
+	sprintf(fnode->name, "pty slave");
+	fnode->uid   = 0;
+	fnode->gid   = 0;
+	fnode->mask  = 0666;
+	fnode->flags = FS_PIPE;
+	fnode->read  =  read_pty_slave;
+	fnode->write = write_pty_slave;
+	fnode->open  =  open_pty_slave;
+	fnode->close = close_pty_slave;
+	fnode->selectcheck = check_pty_slave;
+	fnode->selectwait  = wait_pty_slave;
+	fnode->readdir = NULL;
+	fnode->finddir = NULL;
+	fnode->ioctl = ioctl_pty_slave;
+	fnode->get_size = pty_available_input;
+
+	fnode->device = pty;
+
+	return fnode;
+}
+
+void pty_install(void) {
+	pty_list = list_create();
+}
+
+pty_t * pty_new(struct winsize * size) {
+	pty_t * pty = malloc(sizeof(pty_t));
+
+	/* stdin linkage; characters from terminal → PTY slave */
+	pty->in  = ring_buffer_create(TTY_BUFFER_SIZE);
+	pty->out = ring_buffer_create(TTY_BUFFER_SIZE);
+
+	pty->in->discard = 1;
+
+	/* Master endpoint - writes go to stdin, reads come from stdout */
+	pty->master = pty_master_create(pty);
+
+	/* Slave endpoint, reads come from stdin, writes go to stdout */
+	pty->slave  = pty_slave_create(pty);
+
+	/* TODO PTY name */
+	pty->name   = 0;
+
+	if (size) {
+		memcpy(&pty->size, size, sizeof(struct winsize));
+	} else {
+		/* Sane defaults */
+		pty->size.ws_row = 25;
+		pty->size.ws_col = 80;
+	}
+
+	/* Controlling and foreground processes are set to 0 by default */
+	pty->ct_proc = 0;
+	pty->fg_proc = 0;
+
+	pty->tios.c_iflag = ICRNL | BRKINT;