[gigaboot20x6] initial source code and docs

Change-Id: I95ed2d692472d7b816bc7d7045731e583da2393b
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..364dd01
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,85 @@
+
+ARCH		:= x86_64
+
+EFI_TOOLCHAIN	:=
+EFI_CC		:= $(EFI_TOOLCHAIN)gcc
+EFI_LD		:= $(EFI_TOOLCHAIN)ld
+EFI_OBJCOPY	:= $(EFI_TOOLCHAIN)objcopy
+EFI_AR		:= $(EFI_TOOLCHAIN)ar
+
+EFI_PATH	:= external/gnu-efi
+EFI_LIB_PATHS	:= $(EFI_PATH)/$(ARCH)/lib $(EFI_PATH)/$(ARCH)/gnuefi out
+EFI_INC_PATHS	:= $(EFI_PATH)/inc $(EFI_PATH)/inc/$(ARCH) $(EFI_PATH)/inc/protocol
+
+EFI_CRT0	:= $(EFI_PATH)/$(ARCH)/gnuefi/crt0-efi-$(ARCH).o
+EFI_LINKSCRIPT	:= $(EFI_PATH)/gnuefi/elf_$(ARCH)_efi.lds
+
+EFI_CFLAGS	:= -fpic -fshort-wchar -fno-stack-protector -mno-red-zone
+EFI_CFLAGS	+= -Wall
+EFI_CFLAGS	+= -std=c99
+EFI_CFLAGS	+= -ffreestanding -nostdinc -Iinclude -Isrc -Iexternal/edk2
+EFI_CFLAGS	+= $(patsubst %,-I%,$(EFI_INC_PATHS))
+EFI_CFLAGS	+= -DHAVE_USE_MS_ABI=1
+EFI_CFLAGS	+= -ggdb
+
+EFI_LDFLAGS	:= -nostdlib -znocombreloc -T $(EFI_LINKSCRIPT)
+EFI_LDFLAGS	+= -shared -Bsymbolic
+EFI_LDFLAGS	+= $(patsubst %,-L%,$(EFI_LIB_PATHS))
+
+EFI_LIBS	:= -lstuff -lefi -lgnuefi
+
+what_to_build::	all
+
+# build rules and macros
+include build/build.mk
+
+# declare applications here
+$(call efi_app, hello, hello.c)
+$(call efi_app, showmem, showmem.c)
+$(call efi_app, fileio, fileio.c)
+$(call efi_app, osboot, osboot.c netboot.c netifc.c inet6.c)
+$(call efi_app, usbtest, usbtest.c)
+
+LIB_SRCS := lib/goodies.c lib/loadfile.c lib/console-printf.c lib/printf.c lib/string.c
+
+LIB_OBJS := $(patsubst %.c,out/%.o,$(LIB_SRCS))
+DEPS += $(patsubst %.c,out/%.d,$(LIB_SRCS))
+
+out/libstuff.a: $(LIB_OBJS)
+	@mkdir -p $(dir $@)
+	@echo archiving: $@
+	$(QUIET)rm -f $@
+	$(QUIET)ar rc $@ $^
+
+# generate a small IDE disk image for qemu
+out/disk.img: $(APPS)
+	@mkdir -p $(dir $@)
+	$(QUIET)./build/mkdiskimg.sh $@
+	@echo copying: $(APPS) README.txt to disk.img
+	$(QUIET)mcopy -o -i out/disk.img@@1024K $(APPS) README.txt ::
+
+ALL += out/disk.img
+
+-include $(DEPS)
+
+# ensure gnu-efi gets built
+$(EFI_CRT0):
+	@echo building: gnu-efi
+	$(QUIET)$(MAKE) -C $(EFI_PATH)
+
+QEMU_OPTS := -cpu qemu64
+QEMU_OPTS += -bios external/ovmf/OVMF.fd
+QEMU_OPTS += -drive file=out/disk.img,format=raw,if=ide
+
+qemu:: all
+	qemu-system-x86_64 $(QEMU_OPTS)
+
+out/nbserver: src/nbserver.c
+	@mkdir -p out
+	@echo building nbserver
+	$(QUIET)gcc -o out/nbserver -Isrc -Wall src/nbserver.c
+
+all: $(ALL) out/nbserver
+
+clean::
+	rm -rf out
diff --git a/NOTES.txt b/NOTES.txt
new file mode 100644
index 0000000..465baa1
--- /dev/null
+++ b/NOTES.txt
@@ -0,0 +1,40 @@
+
+uefi_call_wrapper()
+-------------------
+gnu-efi provides this macro thing as a workaround for older compilers that
+don't support the right attributes to handle the fact that the EFI calling
+conventions are different than the standard gcc ones.
+
+If HAVE_USE_MS_ABI is defined (as the Makefile here does) and you have a
+compiler that's not a fairly ancient, everything Just Works(tm) and the
+code is much more readable.
+
+
+Intel Visual BIOS (on NUC) and netboot
+--------------------------------------
+This will netboot EFI apps, provided you have a DHCP server which is
+setup to give the BIOS the IP of a tftp server and a filename to grab
+from there.
+
+You must disable legacy boot for the EFI netboot option to appear.  If
+you check the "keep retrying forever" option, when your app exits, the
+BIOS will try to download it from the tftp server again, making for a
+quick build/download/test cycle
+
+
+Making tftpd work on Ubuntu with IPv4
+-------------------------------------
+sudo apt-get install tftpd-hpa
+
+Optionally make it easy to copy files to the server without sudo:
+sudo chown `who` /var/lib/tftpdboot 
+
+Edit /etc/default/tftpd-hpa so it looks more like:
+TFTP_USERNAME="tftp"
+TFTP_DIRECTORY="/var/lib/tftpboot"
+TFTP_ADDRESS=":69"
+TFTP_OPTIONS="--secure -4 -v -v -v"
+
+Removing the [::] and adding the -4 make it work reliably on IPv4 for me.
+The several -v's make it chattier in syslog which is handy if you're not
+sure the test machine is actually trying to grab files.
diff --git a/README.txt b/README.txt
new file mode 100644
index 0000000..c206d7b
--- /dev/null
+++ b/README.txt
@@ -0,0 +1,52 @@
+What is This?
+-------------
+
+This project contains some experiments in software that runs on UEFI
+firmware for the purpose of exploring UEFI development and bootloader
+development.
+
+
+External Software Included
+--------------------------
+
+Local Path:   external/gnu-efi/...
+Description:  headers and tooling to build UEFI binaries with gcc, etc
+Project:      https://sourceforge.net/projects/gnu-efi/
+Source:       git://git.code.sf.net/p/gnu-efi/code
+Version:      6605c16fc8b1fd3b2085364902d1fa73aa7fad76 (post-3.0.4)
+License:      BSD-ish, see gnu-efi/README.*
+
+Local Path:   external/edk2/...
+Description:  headers for UEFI from Tianocore EDK II
+Project:      http://www.tianocore.org/edk2/
+Source:       https://github.com/tianocore/edk2
+License:      BSD-ish, see headers
+
+Local Path:   external/ovmf/... 
+Description:  UEFI Firmware Suitable for use in Qemu
+Distribution: http://www.tianocore.org/ovmf/
+Version:      OVMF-X64-r15214.zip
+License:      BSD-ish, see ovmf/LICENSE
+
+
+External Dependencies
+---------------------
+
+qemu-system-x86_64 is needed to test in emulation
+gnu parted and mtools are needed to generate the disk.img for Qemu
+
+
+Useful Resources & Documentation
+--------------------------------
+
+ACPI & UEFI Specifications 
+http://www.uefi.org/specifications
+
+Intel 64 and IA-32 Architecture Manuals
+http://www.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html
+
+Tianocore UEFI Open Source Community
+(Source for OVMF, EDK II Dev Environment, etc)
+http://www.tianocore.org/
+https://github.com/tianocore
+
diff --git a/build/build.mk b/build/build.mk
new file mode 100644
index 0000000..ad51a0e
--- /dev/null
+++ b/build/build.mk
@@ -0,0 +1,54 @@
+
+ALL	:=
+APPS	:=
+DEPS	:=
+
+QUIET	?= @
+
+EFI_SECTIONS	:= .text .sdata .data .dynamic .dynsym
+EFI_SECTIONS	+= .rel .rela .reloc .eh_frame
+EFI_SECTIONS	:= $(patsubst %,-j %,$(EFI_SECTIONS))
+
+DBG_SECTIONS	:= .debug_info .debug_abbrev .debug_loc .debug_aranges
+DBG_SECTIONS	+= .debug_line .debug_macinfo .debug_str
+DBG_SECTIONS	:= $(EFI_SECTIONS) $(patsubst %,-j %,$(DBG_SECTIONS))
+
+ifneq ($(VERBOSE),)
+$(info CFLAGS   := $(EFI_CFLAGS))
+$(info LDFLAGS  := $(EFI_LDFLAGS))
+$(info SECTIONS := $(EFI_SECTIONS))
+endif
+
+out/%.o: %.c
+	@mkdir -p $(dir $@)
+	@echo compiling: $@
+	$(QUIET)$(EFI_CC) -MMD -MP -o $@ -c $(EFI_CFLAGS) $<
+
+out/%.efi: out/%.so
+	@mkdir -p $(dir $@)
+	@echo building: $@
+	$(QUIET)$(EFI_OBJCOPY) --target=efi-app-$(ARCH) $(EFI_SECTIONS) $< $@
+	$(QUIET)if [ "`nm $< | grep ' U '`" != "" ]; then echo "error: $<: undefined symbols"; nm $< | grep ' U '; rm $<; exit 1; fi
+
+out/%.dbg: out/%.so
+	@mkdir -p $(dir $@)
+	@echo building: $@
+	$(QUIET)$(EFI_OBJCOPY) --target=efi-app-$(ARCH) $(DBG_SECTIONS) $< $@
+
+
+# _efi_app <basename> <obj-files> <dep-files>
+define _efi_app
+ALL	+= out/$1.efi out/$1.dbg
+APPS	+= out/$1.efi
+DEPS	+= $3
+out/$1.so: $2 $(EFI_CRT0) out/libstuff.a
+	@mkdir -p $$(dir $$@)
+	@echo linking: $$@
+	$(QUIET)$(EFI_LD) -o $$@ $(EFI_LDFLAGS) $(EFI_CRT0) $2 $(EFI_LIBS)
+endef
+
+efi_app = $(eval $(call _efi_app,$(strip $1),\
+$(patsubst %.c,out/src/%.o,$2),\
+$(patsubst %.c,out/src/%.d,$2)))
+
+
diff --git a/build/mkdiskimg.sh b/build/mkdiskimg.sh
new file mode 100755
index 0000000..323aaa4
--- /dev/null
+++ b/build/mkdiskimg.sh
@@ -0,0 +1,18 @@
+#!/bin/bash -e
+
+if [ -z "$1" ]; then
+	echo usage: $0 "<diskimg>"
+	exit 1
+fi
+
+if [[ ! -f $1 ]]; then
+	echo creating: $1
+	dd if=/dev/zero of="$1" bs=512 count=93750
+
+	parted "$1" -s -a minimal mklabel gpt
+	parted "$1" -s -a minimal mkpart EFI FAT16 2048s 93716s
+	parted "$1" -s -a minimal toggle 1 boot
+
+	mformat -i "$1"@@1024K -h 32 -t 32 -n 64 -c 1
+fi
+
diff --git a/include/goodies.h b/include/goodies.h
new file mode 100644
index 0000000..06a7acc
--- /dev/null
+++ b/include/goodies.h
@@ -0,0 +1,45 @@
+// Copyright 2016 Google Inc. All Rights Reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files
+// (the "Software"), to deal in the Software without restriction,
+// including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software,
+// and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#pragma once
+
+void InitGoodies(EFI_HANDLE img, EFI_SYSTEM_TABLE *sys);
+
+void WaitAnyKey(void);
+void Fatal(const char *msg, EFI_STATUS status);
+CHAR16 *HandleToString(EFI_HANDLE handle);
+
+// Convenience wrappers for Open/Close protocol for use by
+// UEFI app code that's not a driver model participant
+EFI_STATUS OpenProtocol(EFI_HANDLE h, EFI_GUID *guid, void **ifc);
+EFI_STATUS CloseProtocol(EFI_HANDLE h, EFI_GUID *guid);
+
+void *LoadFile(CHAR16 *filename, UINTN *size_out);
+
+// GUIDs
+extern EFI_GUID SimpleFileSystemProtocol;
+extern EFI_GUID FileInfoGUID;
+
+// Global Context
+extern EFI_HANDLE gImg;
+extern EFI_SYSTEM_TABLE *gSys;
+extern EFI_BOOT_SERVICES *gBS;
+extern SIMPLE_TEXT_OUTPUT_INTERFACE *gConOut;
diff --git a/include/stdarg.h b/include/stdarg.h
new file mode 100644
index 0000000..25d9f5e
--- /dev/null
+++ b/include/stdarg.h
@@ -0,0 +1,9 @@
+#pragma once
+
+typedef __builtin_va_list va_list;
+
+#define va_start(v,l)  __builtin_va_start(v,l)
+#define va_end(v)      __builtin_va_end(v)
+#define va_arg(v,l)    __builtin_va_arg(v,l)
+#define va_copy(d,s)   __builtin_va_copy(d,s)
+
diff --git a/include/stdint.h b/include/stdint.h
new file mode 100644
index 0000000..880c376
--- /dev/null
+++ b/include/stdint.h
@@ -0,0 +1,30 @@
+#pragma once
+
+typedef __UINT8_TYPE__   uint8_t;
+typedef __UINT16_TYPE__  uint16_t;
+typedef __UINT32_TYPE__  uint32_t;
+typedef __UINT64_TYPE__  uint64_t;
+
+typedef __INT8_TYPE__    int8_t;
+typedef __INT16_TYPE__   int16_t;
+typedef __INT32_TYPE__   int32_t;
+typedef __INT64_TYPE__   int64_t;
+
+typedef __INTMAX_TYPE__  intmax_t;
+
+typedef __UINTMAX_TYPE__ uintmax_t;
+typedef __UINTPTR_TYPE__ uintptr_t;
+typedef __PTRDIFF_TYPE__ ptrdiff_t;
+
+typedef __SIZE_TYPE__    size_t;
+
+typedef unsigned int     uint;
+
+#define INT_MAX          (__INT_MAX__)
+
+typedef int              bool;
+typedef int              ssize_t;
+
+#define false (0)
+#define true (1)
+
diff --git a/include/stdio.h b/include/stdio.h
new file mode 100644
index 0000000..ab4accd
--- /dev/null
+++ b/include/stdio.h
@@ -0,0 +1,3 @@
+#pragma once
+
+#include <printf.h>
diff --git a/include/string.h b/include/string.h
new file mode 100644
index 0000000..2e9d71b
--- /dev/null
+++ b/include/string.h
@@ -0,0 +1,8 @@
+#pragma once
+
+#include <stdint.h>
+
+void *memset(void *dst, int c, size_t n);
+void *memcpy(void *dst, const void *src, size_t n);
+int memcmp(const void *a, const void *b, size_t n);
+size_t strlen(const char *s);
diff --git a/lib/console-printf.c b/lib/console-printf.c
new file mode 100644
index 0000000..e36c843
--- /dev/null
+++ b/lib/console-printf.c
@@ -0,0 +1,72 @@
+// Copyright 2016 Google Inc. All Rights Reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files
+// (the "Software"), to deal in the Software without restriction,
+// including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software,
+// and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#include <printf.h>
+
+#include <efi.h>
+#include <efilib.h>
+#include <goodies.h>
+
+#define PCBUFMAX 126
+// buffer is two larger to leave room for a \0 and room
+// for a \r that may be added after a \n
+
+typedef struct {
+	int off;
+	CHAR16 buf[PCBUFMAX + 2];
+} _pcstate;
+
+static int _printf_console_out(const char *str, size_t len, void *_state) {
+	_pcstate *state = _state;
+	CHAR16 *buf = state->buf;
+	int i = state->off;
+	int n = len;
+	while (n > 0) {
+		if (*str == '\n') {
+			buf[i++] = '\r';
+		}
+		buf[i++] = *str++;
+		if (i >= PCBUFMAX) {
+			buf[i] = 0;
+			gConOut->OutputString(gConOut, buf);
+			i = 0;
+		}
+		n--;
+	}
+	state->off = i;
+	return len;
+}
+
+int _printf(const char *fmt, ...) {
+	va_list ap;
+	_pcstate state;
+	int r;
+	state.off = 0;
+	va_start(ap, fmt);
+	r = _printf_engine(_printf_console_out, &state, fmt, ap);
+	va_end(ap);
+	if (state.off) {
+		state.buf[state.off] = 0;
+		gConOut->OutputString(gConOut, state.buf);
+	}
+	return r;
+}
+
diff --git a/lib/goodies.c b/lib/goodies.c
new file mode 100644
index 0000000..06601fa
--- /dev/null
+++ b/lib/goodies.c
@@ -0,0 +1,74 @@
+// Copyright 2016 Google Inc. All Rights Reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files
+// (the "Software"), to deal in the Software without restriction,
+// including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software,
+// and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#include <efi.h>
+#include <efilib.h>
+
+#include <goodies.h>
+#include <printf.h>
+
+// Useful GUID Constants Not Defined by -lefi
+EFI_GUID SimpleFileSystemProtocol = SIMPLE_FILE_SYSTEM_PROTOCOL;
+EFI_GUID FileInfoGUID = EFI_FILE_INFO_ID;
+
+// -lefi has its own globals, but this may end up not
+// depending on that, so let's not depend on those
+EFI_SYSTEM_TABLE *gSys;
+EFI_HANDLE gImg;
+EFI_BOOT_SERVICES *gBS;
+SIMPLE_TEXT_OUTPUT_INTERFACE *gConOut;
+
+void InitGoodies(EFI_HANDLE img, EFI_SYSTEM_TABLE *sys) {
+	gSys = sys;
+	gImg = img;
+	gBS = sys->BootServices;
+	gConOut = sys->ConOut;
+}
+
+void WaitAnyKey(void) {
+	SIMPLE_INPUT_INTERFACE *sii = gSys->ConIn;
+	EFI_INPUT_KEY key;
+	while (sii->ReadKeyStroke(sii, &key) != EFI_SUCCESS) ;
+}
+
+void Fatal(const char *msg, EFI_STATUS status) {
+	printf("\nERROR: %s (%ld)\n", msg, status);
+	WaitAnyKey();
+	gBS->Exit(gImg, 1, 0, NULL);
+}
+
+CHAR16 *HandleToString(EFI_HANDLE h) {
+	EFI_DEVICE_PATH *path = DevicePathFromHandle(h);
+	if (path == NULL) return L"<NoPath>";
+	CHAR16 *str = DevicePathToStr(path);
+	if (str == NULL) return L"<NoString>";
+	return str;
+}
+
+
+EFI_STATUS OpenProtocol(EFI_HANDLE h, EFI_GUID *guid, void **ifc) {
+	return gBS->OpenProtocol(h, guid, ifc, gImg, NULL,
+		EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL);
+}
+
+EFI_STATUS CloseProtocol(EFI_HANDLE h, EFI_GUID *guid) {
+	return gBS->CloseProtocol(h, guid, gImg, NULL);
+}
diff --git a/lib/loadfile.c b/lib/loadfile.c
new file mode 100644
index 0000000..691d961
--- /dev/null
+++ b/lib/loadfile.c
@@ -0,0 +1,107 @@
+// Copyright 2016 Google Inc. All Rights Reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files
+// (the "Software"), to deal in the Software without restriction,
+// including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software,
+// and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#include <efi.h>
+#include <efilib.h>
+#include <stdio.h>
+#include <goodies.h>
+
+void *LoadFile(CHAR16 *filename, UINTN *_sz) {
+	EFI_LOADED_IMAGE *loaded;
+	EFI_STATUS r;
+	void *data = NULL;
+
+	r = OpenProtocol(gImg, &LoadedImageProtocol, (void**) &loaded);
+	if (r) {
+		printf("LoadFile: Cannot open LoadedImageProtocol (%ld)\n", r);
+		goto exit0;
+	}
+
+#if 0
+	printf("Img DeviceHandle='%s'\n", HandleToString(loaded->DeviceHandle));
+	printf("Img FilePath='%s'\n", DevicePathToStr(loaded->FilePath));
+	printf("Img Base=%lx Size=%lx\n", loaded->ImageBase, loaded->ImageSize);
+#endif
+
+	EFI_FILE_IO_INTERFACE *fioi;
+	r = OpenProtocol(loaded->DeviceHandle, &SimpleFileSystemProtocol, (void **) &fioi);
+	if (r) {
+		printf("LoadFile: Cannot open SimpleFileSystemProtocol (%ld)\n", r);
+		goto exit1;
+	}
+
+	EFI_FILE_HANDLE root;
+	r = fioi->OpenVolume(fioi, &root);
+	if (r) {
+		printf("LoadFile: Cannot open root volume (%ld)\n", r);
+		goto exit2;
+	}
+
+	EFI_FILE_HANDLE file;
+	r = root->Open(root, &file, filename, EFI_FILE_MODE_READ, 0);
+	if (r) {
+		printf("LoadFile: Cannot open file (%ld)\n", r);
+		goto exit3;
+	}
+
+	char buf[512];
+	UINTN sz = sizeof(buf);
+	EFI_FILE_INFO *finfo = (void*) buf;
+	r = file->GetInfo(file, &FileInfoGUID, &sz, finfo);
+	if (r) {
+		printf("LoadFile: Cannot get FileInfo (%ld)\n", r);
+		goto exit3;
+	}
+
+	r = gBS->AllocatePool(EfiLoaderData, finfo->FileSize, (void**) &data);
+	if (r) {
+		printf("LoadFile: Cannot allocate buffer (%ld)\n", r);
+		data = NULL;
+		goto exit4;
+	}
+
+	sz = finfo->FileSize;
+	r = file->Read(file, &sz, data);
+	if (r) {
+		printf("LoadFile: Error reading file (%ld)\n", r);
+		gBS->FreePool(data);
+		data = NULL;
+		goto exit4;
+	}
+	if (sz != finfo->FileSize) {
+		printf("LoadFile: Short read\n");
+		gBS->FreePool(data);
+		data = NULL;
+		goto exit4;
+	}
+	*_sz = finfo->FileSize;
+exit4:
+	file->Close(file);
+exit3:
+	root->Close(root);
+exit2:
+	CloseProtocol(loaded->DeviceHandle, &SimpleFileSystemProtocol);
+exit1:
+	CloseProtocol(gImg, &LoadedImageProtocol);
+exit0:
+	return data;
+}
+
diff --git a/lib/string.c b/lib/string.c
new file mode 100644
index 0000000..a2c622b
--- /dev/null
+++ b/lib/string.c
@@ -0,0 +1,57 @@
+// Copyright 2016 Google Inc. All Rights Reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files
+// (the "Software"), to deal in the Software without restriction,
+// including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software,
+// and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#include <string.h>
+
+void *memset(void *_dst, int c, size_t n) {
+	uint8_t *dst = _dst;
+	while (n-- > 0) {
+		*dst++ = c;
+	}
+	return _dst;
+}
+
+void *memcpy(void *_dst, const void *_src, size_t n) {
+	uint8_t *dst = _dst;
+	const uint8_t *src = _src;
+	while (n-- > 0) {
+		*dst++ = *src++;
+	}
+	return _dst;
+}
+
+int memcmp(const void *_a, const void *_b, size_t n) {
+	const uint8_t *a = _a;
+	const uint8_t *b = _b;
+	while (n-- > 0) {
+		int x = *a++ - *b++;
+		if (x != 0) {
+			return x;
+		}
+	}
+	return 0;
+}
+
+size_t strlen(const char *s) {
+	size_t len = 0;
+	while (*s++) len++;
+	return len;
+}
diff --git a/src/fileio.c b/src/fileio.c
new file mode 100644
index 0000000..2f8f2e8
--- /dev/null
+++ b/src/fileio.c
@@ -0,0 +1,78 @@
+// Copyright 2016 Google Inc. All Rights Reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files
+// (the "Software"), to deal in the Software without restriction,
+// including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software,
+// and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#include <efi.h>
+#include <efilib.h>
+#include <goodies.h>
+
+EFI_STATUS efi_main(EFI_HANDLE img, EFI_SYSTEM_TABLE *sys) {
+	EFI_LOADED_IMAGE *loaded;
+	EFI_STATUS r;
+
+        InitializeLib(img, sys);
+	InitGoodies(img, sys);
+
+	Print(L"Hello, EFI World\n");
+
+	r = OpenProtocol(img, &LoadedImageProtocol, (void**) &loaded);
+	if (r) Fatal("LoadedImageProtocol", r);
+
+	Print(L"Img DeviceHandle='%s'\n", HandleToString(loaded->DeviceHandle));
+	Print(L"Img FilePath='%s'\n", DevicePathToStr(loaded->FilePath));
+	Print(L"Img Base=%lx Size=%lx\n", loaded->ImageBase, loaded->ImageSize);
+
+	EFI_FILE_IO_INTERFACE *fioi;
+	r = OpenProtocol(loaded->DeviceHandle, &SimpleFileSystemProtocol, (void **) &fioi);
+	if (r) Fatal("SimpleFileSystemProtocol", r);
+
+	EFI_FILE_HANDLE root;
+	r = fioi->OpenVolume(fioi, &root);
+	if (r) Fatal("OpenVolume", r);
+
+	EFI_FILE_HANDLE file;
+	r = root->Open(root, &file, L"README.txt", EFI_FILE_MODE_READ, 0);
+
+	if (r == EFI_SUCCESS) {
+		char buf[512];
+		UINTN sz = sizeof(buf);
+		EFI_FILE_INFO *finfo = (void*) buf;
+		r = file->GetInfo(file, &FileInfoGUID, &sz, finfo);
+		if (r) Fatal("GetInfo", r);
+		Print(L"FileSize %ld\n", finfo->FileSize);
+
+		sz = sizeof(buf) - 1;
+		r = file->Read(file, &sz, buf);
+		if (r) Fatal("Read", r);
+
+		char *x = buf;
+		while(sz-- > 0) Print(L"%c", (CHAR16) *x++);
+
+		file->Close(file);
+	}
+
+	root->Close(root);
+	CloseProtocol(loaded->DeviceHandle, &SimpleFileSystemProtocol);
+	CloseProtocol(img, &LoadedImageProtocol);
+
+	WaitAnyKey();
+
+        return EFI_SUCCESS;
+}
diff --git a/src/hello.c b/src/hello.c
new file mode 100644
index 0000000..86c02ed
--- /dev/null
+++ b/src/hello.c
@@ -0,0 +1,8 @@
+
+#include <efi.h>
+
+EFI_STATUS efi_main(EFI_HANDLE img, EFI_SYSTEM_TABLE *sys) {
+        SIMPLE_TEXT_OUTPUT_INTERFACE *ConOut = sys->ConOut;
+	ConOut->OutputString(ConOut, L"Hello, EFI World!\r\n");
+        return EFI_SUCCESS;
+}
diff --git a/src/inet6.c b/src/inet6.c
new file mode 100644
index 0000000..735aea0
--- /dev/null
+++ b/src/inet6.c
@@ -0,0 +1,418 @@
+// Copyright 2016 Google Inc. All Rights Reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files
+// (the "Software"), to deal in the Software without restriction,
+// including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software,
+// and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#include <string.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include <inet6.h>
+
+#if 1
+#define BAD(n) do { printf("error: %s\n", n); return; } while (0)
+#else
+#define BAD(n) do { return; } while (0)
+#endif
+
+
+// useful addresses
+const ip6_addr ip6_ll_all_nodes = {
+	.x = { 0xFF, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
+};
+const ip6_addr ip6_ll_all_routers = {
+	.x = { 0xFF, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 },
+};
+
+// Convert MAC Address to IPv6 Link Local Address
+// aa:bb:cc:dd:ee:ff => FF80::aabb:ccFF:FEdd:eeff
+// bit 2 (U/L) of the mac is inverted
+void ll6addr_from_mac(ip6_addr *_ip, const mac_addr *_mac) {
+	uint8_t *ip = _ip->x;
+	const uint8_t *mac = _mac->x;
+	memset(ip, 0, IP6_ADDR_LEN);
+	ip[0] = 0xFE;
+	ip[1] = 0x80;
+	memset(ip + 2, 0, 6);
+	ip[8] = mac[0] ^ 2;
+	ip[9] = mac[1];
+	ip[10] = mac[2];
+	ip[11] = 0xFF;
+	ip[12] = 0xFE;
+	ip[13] = mac[3];
+	ip[14] = mac[4];
+	ip[15] = mac[5];
+}
+
+// Convert MAC Address to IPv6 Solicit Neighbor Multicast Address
+// aa:bb:cc:dd:ee:ff -> FF02::1:FFdd:eeff
+void snmaddr_from_mac(ip6_addr *_ip, const mac_addr *_mac) {
+	uint8_t *ip = _ip->x;
+	const uint8_t *mac = _mac->x;
+	ip[0] = 0xFF;
+	ip[1] = 0x02;
+	memset(ip + 2, 0, 9);
+	ip[11] = 0x01;
+	ip[12] = 0xFF;
+	ip[13] = mac[3];
+	ip[14] = mac[4];
+	ip[15] = mac[5];
+}
+
+// Convert IPv6 Multicast Address to Ethernet Multicast Address
+void multicast_from_ip6(mac_addr *_mac, const ip6_addr *_ip6) {
+	const uint8_t *ip = _ip6->x;
+	uint8_t *mac = _mac->x;
+	mac[0] = 0x33;
+	mac[1] = 0x33;
+	mac[2] = ip[12];
+	mac[3] = ip[13];
+	mac[4] = ip[14];
+	mac[5] = ip[15];
+}
+
+// ip6 stack configuration
+mac_addr ll_mac_addr;
+ip6_addr ll_ip6_addr;
+mac_addr snm_mac_addr;
+ip6_addr snm_ip6_addr;
+
+// cache for the last source addresses we've seen
+static mac_addr rx_mac_addr;
+static ip6_addr rx_ip6_addr;
+
+void ip6_init(void *macaddr) {
+	char tmp[IP6TOAMAX];
+	mac_addr all;
+
+	// save our ethernet MAC and synthesize link layer addresses
+	memcpy(&ll_mac_addr, macaddr, 6);
+	ll6addr_from_mac(&ll_ip6_addr, &ll_mac_addr);
+	snmaddr_from_mac(&snm_ip6_addr, &ll_mac_addr);
+	multicast_from_ip6(&snm_mac_addr, &snm_ip6_addr);
+
+	eth_add_mcast_filter(&snm_mac_addr);
+
+	multicast_from_ip6(&all, &ip6_ll_all_nodes);
+	eth_add_mcast_filter(&all);
+
+	printf("macaddr: %02x:%02x:%02x:%02x:%02x:%02x\n",
+		ll_mac_addr.x[0], ll_mac_addr.x[1], ll_mac_addr.x[2],
+		ll_mac_addr.x[3], ll_mac_addr.x[4], ll_mac_addr.x[5]);
+	printf("ip6addr: %s\n", ip6toa(tmp, &ll_ip6_addr));
+	printf("snmaddr: %s\n", ip6toa(tmp, &snm_ip6_addr));
+}
+
+static int resolve_ip6(mac_addr *_mac, const ip6_addr *_ip) {
+	const uint8_t *ip = _ip->x;
+
+	// Multicast addresses are a simple transform
+	if (ip[0] == 0xFF) {
+		multicast_from_ip6(_mac, _ip);
+		return 0;
+	}
+
+	// Trying to send to the IP that we last received a packet from?
+	// Assume their mac address has not changed
+	if (memcmp(_ip, &rx_ip6_addr, sizeof(rx_ip6_addr)) == 0) {
+		memcpy(_mac, &rx_mac_addr, sizeof(rx_mac_addr));
+		return 0;
+	}
+
+	// We don't know how to find peers or routers yet, so give up...
+	return -1;
+}
+
+static uint16_t checksum(const void *_data, size_t len, uint16_t _sum) {
+	uint32_t sum = _sum;
+	const uint16_t *data = _data;
+	while (len > 1) {
+		sum += *data++;
+		len -= 2;
+	}
+	if (len) {
+		sum += (*data & 0xFF);
+	}
+	return sum + (sum >> 16);
+}
+
+typedef struct {
+	uint8_t eth[16];
+	ip6_hdr ip6;
+	uint8_t data[0];
+} ip6_pkt;
+
+typedef struct {
+	uint8_t eth[16];
+	ip6_hdr ip6;
+	udp_hdr udp;
+	uint8_t data[0];
+} udp_pkt;
+
+static unsigned ip6_checksum(ip6_hdr *ip, unsigned type, size_t length) {
+	uint16_t sum;
+
+	// length and protocol field for pseudo-header
+	sum = checksum(&ip->length, 2, htons(type));
+	// src/dst for pseudo-header + payload
+	sum = checksum(ip->src, 32 + length, sum);
+
+	// 0 is illegal, so 0xffff remains 0xffff
+	if (sum != 0xffff) {
+		return ~sum;
+	} else {
+		return sum;
+	}
+}
+
+static int ip6_setup(ip6_pkt *p, const ip6_addr *daddr, size_t length, uint8_t type) {
+	mac_addr dmac;
+
+	if (resolve_ip6(&dmac, daddr)) return -1;
+
+	// ethernet header
+	memcpy(p->eth + 2, &dmac, ETH_ADDR_LEN);
+	memcpy(p->eth + 8, &ll_mac_addr, ETH_ADDR_LEN);
+	p->eth[14] = (ETH_IP6 >> 8) & 0xFF;
+	p->eth[15] = ETH_IP6 & 0xFF;
+
+	// ip6 header
+	p->ip6.ver_tc_flow = 0x60; // v=6, tc=0, flow=0
+	p->ip6.length = htons(length);
+	p->ip6.next_header = type;
+	p->ip6.hop_limit = 255;
+	memcpy(p->ip6.src, &ll_ip6_addr, sizeof(ip6_addr));
+	memcpy(p->ip6.dst, daddr, sizeof(ip6_addr));
+
+	return 0;
+}
+
+#define UDP6_MAX_PAYLOAD (ETH_MTU - ETH_HDR_LEN - IP6_HDR_LEN - UDP_HDR_LEN)
+
+int udp6_send(const void *data, size_t dlen, const ip6_addr *daddr, uint16_t dport, uint16_t sport) {
+	size_t length = dlen + UDP_HDR_LEN;
+	udp_pkt *p = eth_get_buffer(ETH_MTU + 2);
+
+	if (p == 0) return -1;
+	if (dlen > UDP6_MAX_PAYLOAD) goto fail;
+	if (ip6_setup((void*) p, daddr, length, HDR_UDP)) goto fail;
+
+	// udp header
+	p->udp.src_port = htons(sport);
+	p->udp.dst_port = htons(dport);
+	p->udp.length = htons(length);
+	p->udp.checksum = 0;
+
+	memcpy(p->data, data, dlen);
+	p->udp.checksum = ip6_checksum(&p->ip6, HDR_UDP, length);
+	return eth_send(p->eth + 2, ETH_HDR_LEN + IP6_HDR_LEN + length);
+
+fail:
+	eth_put_buffer(p);
+	return -1;
+}
+
+#define ICMP6_MAX_PAYLOAD (ETH_MTU - ETH_HDR_LEN - IP6_HDR_LEN)
+
+static int icmp6_send(const void *data, size_t length, const ip6_addr *daddr) {
+	ip6_pkt *p;
+	icmp6_hdr *icmp;
+
+	p = eth_get_buffer(ETH_MTU + 2);
+	if (p == 0) return -1;
+	if (length > ICMP6_MAX_PAYLOAD) goto fail;
+	if (ip6_setup(p, daddr, length, HDR_ICMP6)) goto fail;
+
+	icmp = (void*) p->data;
+	memcpy(icmp, data, length);
+	icmp->checksum = ip6_checksum(&p->ip6, HDR_ICMP6, length);
+	return eth_send(p->eth + 2, ETH_HDR_LEN + IP6_HDR_LEN + length);
+
+fail:
+	eth_put_buffer(p);
+	return -1;
+}
+
+void _udp6_recv(ip6_hdr *ip, void *_data, size_t len) {
+	udp_hdr *udp = _data;
+	uint16_t sum, n;
+
+	if (len < UDP_HDR_LEN) BAD("Bogus Header Len");
+	if (udp->checksum == 0) BAD("Checksum Invalid");
+	if (udp->checksum == 0xFFFF) udp->checksum = 0;
+
+	sum = checksum(&ip->length, 2, htons(HDR_UDP));
+	sum = checksum(ip->src, 32 + len, sum);
+	if (sum != 0xFFFF) BAD("Checksum Incorrect");
+
+	n = ntohs(udp->length);
+	if (n < UDP_HDR_LEN) BAD("Bogus Header Len");
+	if (n > len) BAD("Packet Too Short");
+	len = n - UDP_HDR_LEN;
+
+	udp6_recv((uint8_t*) _data + UDP_HDR_LEN, len,
+		(void*) ip->dst, ntohs(udp->dst_port),
+		(void*) ip->src, ntohs(udp->src_port));
+}
+
+void icmp6_recv(ip6_hdr *ip, void *_data, size_t len) {
+	icmp6_hdr *icmp = _data;
+	uint16_t sum;
+
+	if (icmp->checksum == 0) BAD("Checksum Invalid");
+	if (icmp->checksum == 0xFFFF) icmp->checksum = 0;
+
+	sum = checksum(&ip->length, 2, htons(HDR_ICMP6));
+	sum = checksum(ip->src, 32 + len, sum);
+	if (sum != 0xFFFF) BAD("Checksum Incorrect");
+
+	if (icmp->type == ICMP6_NDP_N_SOLICIT) {
+		ndp_n_hdr *ndp = _data;
+		struct {
+			ndp_n_hdr hdr;
+			uint8_t opt[8];
+		} msg;
+
+		if (len < sizeof(ndp_n_hdr)) BAD("Bogus NDP Message");
+		if (ndp->code != 0) BAD("Bogus NDP Code");
+		if (memcmp(ndp->target, &ll_ip6_addr, IP6_ADDR_LEN)) BAD("NDP Not For Me");
+
+		msg.hdr.type = ICMP6_NDP_N_ADVERTISE;
+		msg.hdr.code = 0;
+		msg.hdr.checksum = 0;
+		msg.hdr.flags = 0x60; // (S)olicited and (O)verride flags
+		memcpy(msg.hdr.target, &ll_ip6_addr, IP6_ADDR_LEN);
+		msg.opt[0] = NDP_N_TGT_LL_ADDR;
+		msg.opt[1] = 1;
+		memcpy(msg.opt + 2, &ll_mac_addr, ETH_ADDR_LEN);
+
+		icmp6_send(&msg, sizeof(msg), (void*) ip->src);
+		return;
+	}
+
+	if (icmp->type == ICMP6_ECHO_REQUEST) {
+		icmp->checksum = 0;
+		icmp->type = ICMP6_ECHO_REPLY;
+		icmp6_send(_data, len, (void*) ip->src);
+		return;
+	}
+
+	BAD("ICMP6 Unhandled");
+}
+
+void eth_recv(void *_data, size_t len) {
+	uint8_t *data = _data;
+	ip6_hdr *ip;
+	uint32_t n;
+
+	if (len < (ETH_HDR_LEN + IP6_HDR_LEN)) BAD("Bogus Header Len");
+	if (data[12] != (ETH_IP6 >> 8)) return;
+	if (data[13] != (ETH_IP6 & 0xFF)) return;
+
+	ip = (void*) (data + ETH_HDR_LEN);
+	data += (ETH_HDR_LEN + IP6_HDR_LEN);
+	len -= (ETH_HDR_LEN + IP6_HDR_LEN);
+
+	// require v6
+	if ((ip->ver_tc_flow & 0xF0) != 0x60) BAD("Unknown IP6 Version");
+
+	// ensure length is sane
+	n = ntohs(ip->length);
+	if (n > len) BAD("IP6 Length Mismatch");
+
+	// ignore any trailing data in the ethernet frame
+	len = n;
+
+	// require that we are the destination
+	if (memcmp(&ll_ip6_addr, ip->dst, IP6_ADDR_LEN) &&
+		memcmp(&snm_ip6_addr, ip->dst, IP6_ADDR_LEN)) {
+		return;
+	}
+
+	// stash the sender's info to simplify replies
+	memcpy(&rx_mac_addr, (uint8_t*) _data + 6, ETH_ADDR_LEN);
+	memcpy(&rx_ip6_addr, ip->src, IP6_ADDR_LEN);
+
+	if (ip->next_header == HDR_ICMP6) {
+		icmp6_recv(ip, data, len);
+		return;
+	}
+
+	if (ip->next_header == HDR_UDP) {
+		_udp6_recv(ip, data, len);
+		return;
+	}
+
+	BAD("Unhandled IP6");
+}
+
+char *ip6toa(char *_out, void *ip6addr) {
+	const uint8_t *x = ip6addr;
+	const uint8_t *end = x + 16;
+	char *out = _out;
+	uint16_t n;
+
+	n = (x[0] << 8) | x[1];
+	while ((n == 0) && (x < end)) {
+		x += 2;
+		n = (x[0] << 8) | x[1];
+	}
+
+	if ((end - x) < 16) {
+		if (end == x) {
+			// all 0s - special case
+			sprintf(out, "::");
+			return _out;
+		}
+		// we consumed some number of leading 0s
+		out += sprintf(out, ":");
+		while (x < end) {
+			out += sprintf(out, ":%x", n);
+			x += 2;
+			n = (x[0] << 8) | x[1];
+		}
+		return _out;
+	}
+
+	while (x < (end - 2)) {
+		out += sprintf(out, "%x:", n);
+		x += 2;
+		n = (x[0] << 8) | x[1];
+		if (n == 0) goto middle_zeros;
+	}
+	out += sprintf(out, "%x", n);
+	return _out;
+
+middle_zeros:
+	while ((n == 0) && (x < end)) {
+		x += 2;
+		n = (x[0] << 8) | x[1];
+	}
+	if (x == end) {
+		out += sprintf(out, ":");
+		return _out;
+	}
+	while (x < end) {
+		out += sprintf(out, ":%x", n);
+		x += 2;
+		n = (x[0] << 8) | x[1];
+	}
+	return _out;
+}
+
diff --git a/src/inet6.h b/src/inet6.h
new file mode 100644
index 0000000..12d7cf6
--- /dev/null
+++ b/src/inet6.h
@@ -0,0 +1,184 @@
+// Copyright 2016 Google Inc. All Rights Reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files
+// (the "Software"), to deal in the Software without restriction,
+// including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software,
+// and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#pragma once
+
+typedef struct mac_addr_t mac_addr;
+typedef struct ip6_addr_t ip6_addr;
+typedef struct ip6_hdr_t ip6_hdr;
+typedef struct udp_hdr_t udp_hdr;
+typedef struct icmp6_hdr_t icmp6_hdr;
+typedef struct ndp_n_hdr_t ndp_n_hdr;
+
+#define ETH_ADDR_LEN		6
+#define ETH_HDR_LEN		14
+#define ETH_MTU			1514
+
+#define IP6_ADDR_LEN		16
+#define IP6_HDR_LEN		40
+
+#define IP6_MIN_MTU		1280
+
+#define UDP_HDR_LEN		8
+
+
+struct mac_addr_t {
+	uint8_t x[ETH_ADDR_LEN];
+} __attribute__((packed));
+
+struct ip6_addr_t {
+	uint8_t x[IP6_ADDR_LEN];
+} __attribute__((packed));
+
+extern const ip6_addr ip6_ll_all_nodes;
+extern const ip6_addr ip6_ll_all_routers;
+
+#define ETH_IP4			0x0800
+#define ETH_ARP			0x0806
+#define ETH_IP6			0x86DD
+
+#define HDR_HNH_OPT		0
+#define HDR_TCP			6
+#define HDR_UDP			17
+#define HDR_ROUTING		43
+#define HDR_FRAGMENT		44
+#define HDR_ICMP6		58
+#define HDR_NONE		59
+#define HDR_DST_OPT		60
+
+struct ip6_hdr_t {
+	uint32_t ver_tc_flow;
+	uint16_t length;
+	uint8_t next_header;
+	uint8_t hop_limit;
+	uint8_t src[IP6_ADDR_LEN];
+	uint8_t dst[IP6_ADDR_LEN];
+} __attribute__((packed));
+
+struct udp_hdr_t {
+	uint16_t src_port;
+	uint16_t dst_port;
+	uint16_t length;
+	uint16_t checksum;
+} __attribute__((packed));
+
+
+#define ICMP6_DEST_UNREACHABLE		1
+#define ICMP6_PACKET_TOO_BIG		2
+#define ICMP6_TIME_EXCEEDED		3
+#define ICMP6_PARAMETER_PROBLEM		4
+
+#define ICMP6_ECHO_REQUEST		128
+#define ICMP6_ECHO_REPLY		129
+
+#define ICMP6_NDP_N_SOLICIT		135
+#define ICMP6_NDP_N_ADVERTISE		136
+
+struct icmp6_hdr_t {
+	uint8_t type;
+	uint8_t code;
+	uint16_t checksum;
+} __attribute__((packed));
+
+struct ndp_n_hdr_t {
+	uint8_t type;
+	uint8_t code;
+	uint16_t checksum;
+	uint32_t flags;
+	uint8_t target[IP6_ADDR_LEN];
+	uint8_t options[0];
+} __attribute__((packed));
+
+#define NDP_N_SRC_LL_ADDR		1
+#define NDP_N_TGT_LL_ADDR		2
+#define NDP_N_PREFIX_INFO		3
+#define NDP_N_REDIRECTED_HDR		4
+#define NDP_N_MTU			5
+
+#ifndef ntohs
+#define ntohs(n) _swap16(n)
+#define htons(n) _swap16(n)
+static inline uint16_t _swap16(uint16_t n) {
+	return (n >> 8) | (n << 8);
+}
+#endif
+
+#ifndef ntohl
+#define ntohl(n) _swap32(n)
+#define htonl(n) _swap32(n)
+static inline uint32_t _swap32(uint32_t n) {
+	return (n >> 24) | ((n >> 8) & 0xFF00) |
+		((n & 0xFF00) << 8) | (n << 24);
+}
+#endif
+
+// Formats an IP6 address into the provided buffer (which must be
+// at least IP6TOAMAX bytes in size), and returns the buffer address.
+char *ip6toa(char *_out, void *ip6addr);
+#define IP6TOAMAX 40
+
+// provided by inet6.c
+void ip6_init(void *macaddr);
+void eth_recv(void *data, size_t len);
+
+// provided by interface driver
+void *eth_get_buffer(size_t len);
+void eth_put_buffer(void *ptr);
+int eth_send(void *data, size_t len);
+int eth_add_mcast_filter(const mac_addr *addr);
+
+// call to transmit a UDP packet
+int udp6_send(const void *data, size_t len,
+	const ip6_addr *daddr, uint16_t dport,
+	uint16_t sport);
+
+// implement to recive UDP packets
+void udp6_recv(void *data, size_t len,
+	const ip6_addr *daddr, uint16_t dport,
+	const ip6_addr *saddr, uint16_t sport);
+
+// NOTES
+//
+// This is an extremely minimal IPv6 stack, supporting just enough
+// functionality to talk to link local hosts over UDP.
+//
+// It responds to ICMPv6 Neighbor Solicitations for its link local
+// address, which is computed from the mac address provided by the
+// ethernet interface driver.
+//
+// It responds to PINGs.
+//
+// It can only transmit to multicast addresses or to the address it
+// last received a packet from (general usecase is to reply to a UDP
+// packet from the UDP callback, which this supports)
+//
+// It does not currently do duplicate address detection, which is
+// probably the most severe bug.
+//
+// It does not support any IPv6 options and will drop packets with
+// options.
+//
+// It expects the network stack to provide transmit buffer allocation
+// and free functionality.  It will allocate a single transmit buffer
+// from udp6_send() or icmp6_send() to fill out and either pass to the
+// network stack via eth_send() or, in the event of an error, release
+// via eth_put_buffer().
+//
diff --git a/src/nbserver.c b/src/nbserver.c
new file mode 100644
index 0000000..27bb82e
--- /dev/null
+++ b/src/nbserver.c
@@ -0,0 +1,242 @@
+// Copyright 2016 Google Inc. All Rights Reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files
+// (the "Software"), to deal in the Software without restriction,
+// including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software,
+// and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <errno.h>
+#include <stdint.h>
+#include <netboot.h>
+
+static uint32_t cookie = 1;
+
+static int io(int s, nbmsg *msg, size_t len, nbmsg *ack) {
+	int retries = 5;
+	int r;
+
+	msg->magic = NB_MAGIC;
+	msg->cookie = cookie++;
+
+	for (;;) {
+		r = write(s, msg, len);
+		if (r < 0) {
+			if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) {
+				continue;
+			}
+			fprintf(stderr, "\nnbserver: socket write error %d\n", errno);
+			return -1;
+		}
+		r = read(s, ack, 2048);
+		if (r < 0) {
+			if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) {
+				retries--;
+				if (retries > 0) {
+					fprintf(stderr, "T");
+					continue;
+				}
+				fprintf(stderr, "\nnbserver: timed out\n");
+			} else {
+				fprintf(stderr, "\nnbserver: socket read error %d\n", errno);
+			}
+			return -1;
+		}
+		if (r < sizeof(nbmsg)) {
+			fprintf(stderr, "Z");
+			continue;
+		}
+		if (ack->magic != NB_MAGIC) {
+			fprintf(stderr, "?");
+			continue;
+		}
+		if (ack->cookie != msg->cookie) {
+			fprintf(stderr, "C");
+			continue;
+		}
+		if (ack->arg != msg->arg) {
+			fprintf(stderr, "A");
+			continue;
+		}
+		if (ack->cmd == NB_ACK) return 0;
+		return -1;
+	}
+}
+
+static void xfer(struct sockaddr_in6 *addr, const char *fn) {
+	char msgbuf[2048];
+	char ackbuf[2048];
+	char tmp[INET6_ADDRSTRLEN];
+	struct timeval tv;
+	nbmsg *msg = (void*) msgbuf;
+	nbmsg *ack = (void*) ackbuf;
+	FILE *fp;
+	int s, r;
+	int count = 0;
+
+	if ((fp = fopen(fn, "rb")) == NULL) {
+		return;
+	}
+	if ((s = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
+		fprintf(stderr, "nbserver: cannot create socket %d\n", errno);
+		goto done;
+	}
+	tv.tv_sec = 0;
+	tv.tv_usec = 250 * 1000;
+	setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
+	if (connect(s, (void*) addr, sizeof(*addr)) < 0) {
+		fprintf(stderr, "nbserver: cannot connect to [%s]%d\n",
+			inet_ntop(AF_INET6, &addr->sin6_addr, tmp, sizeof(tmp)),
+			ntohs(addr->sin6_port));
+		goto done;
+	}
+
+	msg->cmd = NB_SEND_FILE;
+	msg->arg = 0;
+	strcpy((void*) msg->data, "kernel.bin");
+	if (io(s, msg, sizeof(nbmsg) + sizeof("kernel.bin"), ack)) {
+		fprintf(stderr, "nbserver: failed to start transfer\n");
+		goto done;
+	}
+
+	msg->cmd = NB_DATA;
+	msg->arg = 0;
+	do {
+		r = fread(msg->data, 1, 1024, fp);
+		if (r == 0) {
+			if (ferror(fp)) {
+				fprintf(stderr, "\nnbserver: error: reading '%s'\n", fn);
+				goto done;
+			}
+			break;
+		}
+		count += r;
+		if (count >= (32*1024)) {
+			count = 0;
+			fprintf(stderr, "#");
+		}
+		if (io(s, msg, sizeof(nbmsg) + r, ack)) {
+			fprintf(stderr, "\nnbserver: error: sending '%s'\n", fn);
+			goto done;
+		}
+		msg->arg += r;
+	} while (r != 0);
+
+	msg->cmd = NB_BOOT;
+	msg->arg = 0;
+	if (io(s, msg, sizeof(nbmsg), ack)) {
+		fprintf(stderr, "\nnbserver: failed to send boot command\n");
+	} else {
+		fprintf(stderr, "\nnbserver: sent boot command\n");
+	}
+done:
+	if (s >= 0) close(s);
+	if (fp != NULL) fclose(fp);
+}
+
+void usage(void) {
+	fprintf(stderr,
+		"usage:   nbserver [ <option> ]* <filename>\n"
+		"\n"
+		"options: -1  only boot once, then exit\n"
+		);
+	exit(1);
+}
+		
+int main(int argc, char **argv) {
+	struct sockaddr_in6 addr;
+	char tmp[INET6_ADDRSTRLEN];
+	int r, s, n = 1;
+	const char *fn = NULL;
+	int once = 0;
+
+	while (argc > 1) {
+		if (argv[1][0] != '-') {
+			if (fn != NULL) usage();
+			fn = argv[1];
+		} else if (!strcmp(argv[1], "-1")) {
+			once = 1;
+		} else {
+			usage();
+		}
+		argc--;
+		argv++;
+	}
+	if (fn == NULL) {
+		usage();
+	}
+
+	memset(&addr, 0, sizeof(addr));
+	addr.sin6_family = AF_INET6;
+	addr.sin6_port = htons(NB_ADVERT_PORT);
+
+	s = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
+	if (s < 0) {
+		fprintf(stderr, "nbserver: cannot create socket %d\n", s);
+		return -1;
+	}
+	setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &n, sizeof(n));
+	if ((r = bind(s, (void*) &addr, sizeof(addr))) < 0) {
+		fprintf(stderr, "nbserver: cannot bind to [%s]%d %d\n",
+			inet_ntop(AF_INET6, &addr.sin6_addr, tmp, sizeof(tmp)),
+			ntohs(addr.sin6_port), r);
+		return -1;
+	}
+
+	fprintf(stderr, "nbserver: listening on [%s]%d\n",
+		inet_ntop(AF_INET6, &addr.sin6_addr, tmp, sizeof(tmp)),
+		ntohs(addr.sin6_port));
+	for (;;) {
+		struct sockaddr_in6 ra;
+		socklen_t rlen;
+		char buf[4096];
+		nbmsg *msg = (void*) buf;
+		rlen = sizeof(ra);
+		r = recvfrom(s, buf, 4096, 0, (void*) &ra, &rlen);
+		if (r < 0) {
+			fprintf(stderr, "nbserver: socket read error %d\n", r);
+			break;
+		}
+		if (r < sizeof(nbmsg)) continue;
+		if ((ra.sin6_addr.s6_addr[0] != 0xFE) || (ra.sin6_addr.s6_addr[1] != 0x80)) {
+			fprintf(stderr, "ignoring non-link-local message\n");
+			continue;
+		}
+		if (msg->magic != NB_MAGIC) continue;
+		if (msg->cmd != NB_ADVERTISE) continue;
+		fprintf(stderr, "nbserver: got beacon from [%s]%d\n",
+			inet_ntop(AF_INET6, &ra.sin6_addr, tmp, sizeof(tmp)),
+			ntohs(ra.sin6_port));
+		fprintf(stderr, "nbserver: sending '%s'...\n", fn);
+		xfer(&ra, fn);
+		if (once) {
+			break;
+		}
+	}
+
+	return 0;
+}
+
diff --git a/src/netboot.c b/src/netboot.c
new file mode 100644
index 0000000..93e9832
--- /dev/null
+++ b/src/netboot.c
@@ -0,0 +1,188 @@
+// Copyright 2016 Google Inc. All Rights Reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files
+// (the "Software"), to deal in the Software without restriction,
+// including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software,
+// and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#include <stdio.h>
+#include <string.h>
+
+#include <netboot.h>
+#include <netifc.h>
+#include <inet6.h>
+
+static uint32_t last_cookie = 0;
+static uint32_t last_cmd = 0;
+static uint32_t last_arg = 0;
+static uint32_t last_ack_cmd = 0;
+static uint32_t last_ack_arg = 0;
+
+static uint8_t *nb_buffer;
+static uint32_t nb_bufsize = 0;
+static uint32_t nb_offset = 0;
+
+static int nb_boot_now = 0;
+static int nb_active = 0;
+
+void udp6_recv(void *data, size_t len,
+	const ip6_addr *daddr, uint16_t dport,
+	const ip6_addr *saddr, uint16_t sport) {
+	nbmsg *msg = data;
+	nbmsg ack;
+
+	if (dport != NB_SERVER_PORT) return;
+
+	if (len < sizeof(nbmsg)) return;
+	len -= sizeof(nbmsg);
+
+	//printf("netboot: MSG %08x %08x %08x %08x datalen %d\n",
+	//	msg->magic, msg->cookie, msg->cmd, msg->arg, len);
+
+	if ((last_cookie == msg->cookie) &&
+		(last_cmd == msg->cmd) && (last_arg = msg->arg)) {
+		// host must have missed the ack. resend
+		ack.magic = NB_MAGIC;
+		ack.cookie = last_cookie;
+		ack.cmd = last_ack_cmd;
+		ack.arg = last_ack_arg;
+		goto transmit;
+	}
+
+	ack.cmd = NB_ACK;
+	ack.arg = 0;
+
+	switch (msg->cmd) {
+	case NB_COMMAND:
+		if (len == 0) return;
+		msg->data[len - 1] = 0;
+		break;
+	case NB_SEND_FILE:
+		if (len == 0) return;
+		msg->data[len - 1] = 0;		
+		nb_offset = 0;
+		printf("netboot: Receive File...\n");
+		break;
+	case NB_DATA:
+		if (msg->arg != nb_offset) return;
+		if (nb_buffer == 0) return;
+		ack.arg = msg->arg;
+		if ((nb_offset + len) > nb_bufsize) {
+			ack.cmd = NB_ERROR_TOO_LARGE;
+		} else {
+			memcpy(nb_buffer + nb_offset, msg->data, len);
+			nb_offset += len;
+			ack.cmd = NB_ACK;
+		}
+		break;
+	case NB_BOOT:
+		nb_boot_now = 1;
+		printf("netboot: Boot Kernel...\n");
+		break;
+	default:
+		ack.cmd = NB_ERROR_BAD_CMD;
+		ack.arg = 0;
+	}
+
+	last_cookie = msg->cookie;
+	last_cmd = msg->cmd;
+	last_arg = msg->arg;
+	last_ack_cmd = ack.cmd;
+	last_ack_arg = ack.arg;
+
+	ack.cookie = msg->cookie;
+	ack.magic = NB_MAGIC;
+transmit:
+	nb_active = 1;
+	udp6_send(&ack, sizeof(ack), saddr, sport, NB_SERVER_PORT);
+}
+
+static char advertise_data[] = 
+	"version\00.1\0"
+	"serialno\0unknown\0"
+	"board\0unknown\0";
+
+static void advertise(void) {
+	uint8_t buffer[256];
+	nbmsg *msg = (void*) buffer;
+	msg->magic = NB_MAGIC;
+	msg->cookie = 0;
+	msg->cmd = NB_ADVERTISE;
+	msg->arg = 0;
+	memcpy(msg->data, advertise_data, sizeof(advertise_data));
+	udp6_send(buffer, sizeof(nbmsg) + sizeof(advertise_data),
+		&ip6_ll_all_nodes, NB_ADVERT_PORT, NB_SERVER_PORT);
+}
+
+#define FAST_TICK 100
+#define SLOW_TICK 1000
+
+int netboot_init(void *buf, size_t len) {
+	if (netifc_open()) {
+		printf("netboot: Failed to open network interface\n");
+		return -1;
+	}
+
+	nb_buffer = buf;
+	nb_bufsize = len;
+	return 0;
+}
+
+static int nb_fastcount = 0;
+static int nb_online = 0;
+
+int netboot_poll(void) {
+	if (netifc_active()) {
+		if (nb_online == 0) {
+			printf("netboot: interface online\n");
+			nb_online = 1;
+			nb_fastcount = 20;
+			netifc_set_timer(FAST_TICK);
+			advertise();
+		}
+	} else {
+		if (nb_online == 1) {
+			printf("netboot: interface offline\n");
+			nb_online = 0;
+		}
+		return 0;
+	}
+	if (netifc_timer_expired()) {
+		if (nb_fastcount) {
+			nb_fastcount--;
+			netifc_set_timer(FAST_TICK);
+		} else {
+			netifc_set_timer(SLOW_TICK);
+		}
+		if (nb_active) {
+			// don't advertise if we're in a transfer
+			nb_active = 0;
+		} else {
+			advertise();
+		}
+	}
+
+	netifc_poll();
+
+	if (nb_boot_now) {
+		nb_boot_now = 0;
+		return nb_offset;
+	} else {
+		return 0;
+	}
+}
+
diff --git a/src/netboot.h b/src/netboot.h
new file mode 100644
index 0000000..8019b78
--- /dev/null
+++ b/src/netboot.h
@@ -0,0 +1,52 @@
+// Copyright 2016 Google Inc. All Rights Reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files
+// (the "Software"), to deal in the Software without restriction,
+// including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software,
+// and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#pragma once
+
+#define NB_MAGIC	0xAA774217
+
+#define NB_SERVER_PORT	33330
+#define NB_ADVERT_PORT	33331
+
+#define NB_COMMAND		1 // arg=0, data=command
+#define NB_SEND_FILE		2 // arg=0, data=filename
+#define NB_DATA			3 // arg=blocknum, data=data
+#define NB_BOOT			4 // arg=0
+
+#define NB_ACK			0
+
+#define NB_ADVERTISE		0x77777777
+
+#define NB_ERROR		0x80000000
+#define NB_ERROR_BAD_CMD	0x80000001
+#define NB_ERROR_BAD_PARAM	0x80000002
+#define NB_ERROR_TOO_LARGE	0x80000003
+
+typedef struct nbmsg_t {
+	uint32_t magic;
+	uint32_t cookie;
+	uint32_t cmd;
+	uint32_t arg;
+	uint8_t  data[0];
+} nbmsg;
+
+int netboot_init(void *buf, size_t len);
+int netboot_poll(void);
diff --git a/src/netifc.c b/src/netifc.c
new file mode 100644
index 0000000..2760043
--- /dev/null
+++ b/src/netifc.c
@@ -0,0 +1,270 @@
+// Copyright 2016 Google Inc. All Rights Reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files
+// (the "Software"), to deal in the Software without restriction,
+// including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software,
+// and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#include <efi.h>
+#include <efilib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <goodies.h>
+
+#include <netifc.h>
+#include <inet6.h>
+
+static EFI_SIMPLE_NETWORK *snp;
+
+#define MAX_FILTER 8
+static EFI_MAC_ADDRESS mcast_filters[MAX_FILTER];
+static unsigned mcast_filter_count = 0;
+
+#define NUM_BUFFER_PAGES	8
+#define ETH_BUFFER_SIZE		1516
+#define ETH_HEADER_SIZE		16
+#define ETH_BUFFER_MAGIC	0x424201020304A7A7UL
+
+typedef struct eth_buffer_t eth_buffer;
+struct eth_buffer_t {
+	uint64_t magic;
+	eth_buffer *next;
+	uint8_t data[0];
+};
+
+static EFI_PHYSICAL_ADDRESS eth_buffers_base = 0;
+static eth_buffer *eth_buffers = NULL;
+
+void *eth_get_buffer(size_t sz) {
+	eth_buffer *buf;
+	if (sz > ETH_BUFFER_SIZE) {
+		return NULL;
+	}
+	if (eth_buffers == NULL) {
+		return NULL;
+	}
+	buf = eth_buffers;
+	eth_buffers = buf->next;
+	buf->next = NULL;
+	return buf->data;
+}
+
+void eth_put_buffer(void *data) {
+	eth_buffer *buf = (void*) (((uint64_t) data) & (~2047));
+
+	if (buf->magic != ETH_BUFFER_MAGIC) {
+		printf("fatal: eth buffer %p (from %p) bad magic %lx\n", buf, data, buf->magic);
+		for (;;) ;
+	}
+	buf->next = eth_buffers;
+	eth_buffers = buf;
+}
+
+int eth_send(void *data, size_t len) {
+	EFI_STATUS r;
+
+	if ((r = snp->Transmit(snp, 0, len, (void*) data, NULL, NULL, NULL))) {
+		eth_put_buffer(data);
+		return -1;
+	} else {
+		return 0;
+	}
+}
+
+void eth_dump_status(void) {
+	printf("State/HwAdSz/HdrSz/MaxSz %d %d %d %d\n",
+		snp->Mode->State, snp->Mode->HwAddressSize,
+		snp->Mode->MediaHeaderSize, snp->Mode->MaxPacketSize);
+	printf("RcvMask/RcvCfg/MaxMcast/NumMcast %d %d %d %d\n",
+		snp->Mode->ReceiveFilterMask, snp->Mode->ReceiveFilterSetting,
+		snp->Mode->MaxMCastFilterCount, snp->Mode->MCastFilterCount);
+	UINT8 *x = snp->Mode->CurrentAddress.Addr;
+	printf("MacAddr %02x:%02x:%02x:%02x:%02x:%02x\n",
+		x[0], x[1], x[2], x[3], x[4], x[5]);
+	printf("SetMac/MultiTx/LinkDetect/Link %d %d %d %d\n",
+		snp->Mode->MacAddressChangeable, snp->Mode->MultipleTxSupported,
+		snp->Mode->MediaPresentSupported, snp->Mode->MediaPresent);
+}
+
+int eth_add_mcast_filter(const mac_addr *addr) {
+	if (mcast_filter_count >= MAX_FILTER) return -1;
+	if (mcast_filter_count >= snp->Mode->MaxMCastFilterCount) return -1;
+	memcpy(mcast_filters + mcast_filter_count, addr, ETH_ADDR_LEN);
+	mcast_filter_count++;
+	return 0;
+}
+
+static EFI_EVENT net_timer = NULL;
+
+#define TIMER_MS(n) (((uint64_t) (n)) * 10000UL)
+
+void netifc_set_timer(uint32_t ms) {
+	if (net_timer == 0) {
+		return;
+	}
+	gBS->SetTimer(net_timer, TimerRelative, TIMER_MS(ms));
+}
+
+int netifc_timer_expired(void) {
+	if (net_timer == 0) {
+		return 0;
+	}
+	if (gBS->CheckEvent(net_timer) == EFI_SUCCESS) {
+		return 1;
+	}
+	return 0;
+}
+
+int netifc_open(void) {
+	EFI_BOOT_SERVICES *bs = gSys->BootServices;
+	EFI_HANDLE h[32];
+	EFI_STATUS r;
+	int i, j;
+	UINTN sz;
+
+	bs->CreateEvent(EVT_TIMER, TPL_CALLBACK, NULL, NULL, &net_timer);
+
+	sz = sizeof(h);
+	r = bs->LocateHandle(ByProtocol, &SimpleNetworkProtocol, NULL, &sz, h);
+	if (r != EFI_SUCCESS) {
+		printf("Failed to locate SNP handle(s) %ld\n", r);
+		return -1;
+	}
+
+	r = bs->OpenProtocol(h[0], &SimpleNetworkProtocol, (void**) &snp, gImg, NULL,
+		EFI_OPEN_PROTOCOL_EXCLUSIVE);
+	if (r) {
+		printf("Failed to open SNP exclusively %ld\n", r);
+		return -1;
+	}
+
+	if (snp->Mode->State != EfiSimpleNetworkStarted) {
+		snp->Start(snp);
+		if (snp->Mode->State != EfiSimpleNetworkStarted) {
+			printf("Failed to start SNP\n");
+			return -1;
+		}
+		r = snp->Initialize(snp, 32768, 32768);
+		if (r) {
+			printf("Failed to initialize SNP\n");
+			return -1;
+		}
+	}
+
+	if (bs->AllocatePages(AllocateAnyPages, EfiLoaderData, NUM_BUFFER_PAGES, &eth_buffers_base)) {
+		printf("Failed to allocate net buffers\n");
+		return -1;
+	}
+
+	uint8_t *ptr = (void*) eth_buffers_base;
+	for (r = 0; r < (NUM_BUFFER_PAGES * 2); r++) {
+		eth_buffer *buf = (void*) ptr;
+		buf->magic = ETH_BUFFER_MAGIC;
+		eth_put_buffer(buf);
+		ptr += 2048;
+	}
+
+	ip6_init(snp->Mode->CurrentAddress.Addr);
+
+	r = snp->ReceiveFilters(snp,
+		EFI_SIMPLE_NETWORK_RECEIVE_UNICAST |
+		EFI_SIMPLE_NETWORK_RECEIVE_MULTICAST,
+		0, 0, mcast_filter_count, (void*) mcast_filters);
+	if (r) {
+		printf("Failed to install multicast filters %lx\n", r);
+		return -1;
+	}
+
+	eth_dump_status();
+
+	if (snp->Mode->MCastFilterCount != mcast_filter_count) {
+		printf("OOPS: expected %d filters, found %d\n",
+			mcast_filter_count, snp->Mode->MCastFilterCount);
+		goto force_promisc;
+	}
+	for (i = 0; i < mcast_filter_count; i++) {
+		//uint8_t *m = (void*) &mcast_filters[i];
+		//printf("i=%d %02x %02x %02x %02x %02x %02x\n", i, m[0], m[1], m[2], m[3], m[4], m[5]);
+		for (j = 0; j < mcast_filter_count; j++) {
+			//m = (void*) &snp->Mode->MCastFilter[j];
+			//printf("j=%d %02x %02x %02x %02x %02x %02x\n", j, m[0], m[1], m[2], m[3], m[4], m[5]);
+			if (!memcmp(mcast_filters + i, &snp->Mode->MCastFilter[j], 6)) {
+				goto found_it;
+			}
+		}
+		printf("OOPS: filter #%d missing\n", i);
+		goto force_promisc;
+	found_it:
+		;
+	}
+
+	return 0;
+
+force_promisc:
+	r = snp->ReceiveFilters(snp,
+		EFI_SIMPLE_NETWORK_RECEIVE_UNICAST |
+		EFI_SIMPLE_NETWORK_RECEIVE_PROMISCUOUS |
+		EFI_SIMPLE_NETWORK_RECEIVE_PROMISCUOUS_MULTICAST,
+		0, 0, 0, NULL);
+	if (r) {
+		printf("Failed to set promiscuous mode %lx\n", r);
+		return -1;
+	}
+	return 0;
+}
+
+void netifc_close(void) {
+}
+
+int netifc_active(void) {
+	return (snp != 0);
+}
+
+void netifc_poll(void) {
+	UINT8 data[1514];
+	EFI_STATUS r;
+	UINTN hsz, bsz;
+	UINT32 irq;
+	VOID *txdone;
+
+	if ((r = snp->GetStatus(snp, &irq, &txdone))) {
+		return;
+	}
+
+	if (txdone) {
+		eth_put_buffer(txdone);
+	}
+		
+	if (irq & EFI_SIMPLE_NETWORK_RECEIVE_INTERRUPT) {
+		hsz = 0;
+		bsz = sizeof(data);
+		r = snp->Receive(snp, &hsz, &bsz, data, NULL, NULL, NULL);
+		if (r != EFI_SUCCESS) {
+			return;
+		}
+#if TRACE
+		printf("RX %02x:%02x:%02x:%02x:%02x:%02x < %02x:%02x:%02x:%02x:%02x:%02x %02x%02x %d\n",
+			data[0], data[1], data[2], data[3], data[4], data[5],
+			data[6], data[7], data[8], data[9], data[10], data[11],
+			data[12], data[13], (int) (bsz - hsz));
+#endif
+		eth_recv(data, bsz);
+	}
+}
+
+
diff --git a/src/netifc.h b/src/netifc.h
new file mode 100644
index 0000000..1d1c55b
--- /dev/null
+++ b/src/netifc.h
@@ -0,0 +1,41 @@
+// Copyright 2016 Google Inc. All Rights Reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files
+// (the "Software"), to deal in the Software without restriction,
+// including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software,
+// and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#pragma once
+
+// setup networking
+int netifc_open(void);
+
+// process inbound packet(s)
+void netifc_poll(void);
+
+// return nonzero if interface exists
+int netifc_active(void);
+
+// shut down networking
+void netifc_close(void);
+
+// set a timer to expire after ms milliseconds
+void netifc_set_timer(uint32_t ms);
+
+// returns true once the timer has expired
+int netifc_timer_expired(void);
+
diff --git a/src/osboot.c b/src/osboot.c
new file mode 100644
index 0000000..887441a
--- /dev/null
+++ b/src/osboot.c
@@ -0,0 +1,450 @@
+// Copyright 2016 Google Inc. All Rights Reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files
+// (the "Software"), to deal in the Software without restriction,
+// including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software,
+// and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#include <efi.h>
+#include <efilib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <goodies.h>
+#include <netboot.h>
+
+#define E820_IGNORE	0
+#define E820_RAM	1
+#define E820_RESERVED	2
+#define E820_ACPI	3
+#define E820_NVS	4
+#define E820_UNUSABLE	5
+
+const char *e820name[] = {
+	"IGNORE",
+	"RAM",
+	"RESERVED",
+	"ACPI",
+	"NVS",
+	"UNUSABLE",
+};
+
+struct e820entry {
+	UINT64 addr;
+	UINT64 size;
+	UINT32 type;
+} __attribute__((packed));
+
+unsigned e820type(unsigned uefi_mem_type) {
+	switch (uefi_mem_type) {
+	case EfiReservedMemoryType:
+	case EfiPalCode:
+		return E820_RESERVED;
+	case EfiRuntimeServicesCode:
+	case EfiRuntimeServicesData:
+#if WITH_RUNTIME_SERVICES
+		return E820_RESERVED;
+#else
+		return E820_RAM;
+#endif
+	case EfiACPIReclaimMemory:
+		return E820_ACPI;
+	case EfiACPIMemoryNVS:
+		return E820_NVS;
+	case EfiLoaderCode:
+	case EfiLoaderData:
+	case EfiBootServicesCode:
+	case EfiBootServicesData:
+	case EfiConventionalMemory:
+		return E820_RAM;
+	case EfiMemoryMappedIO:
+	case EfiMemoryMappedIOPortSpace:
+		return E820_IGNORE;
+	default:
+		if (uefi_mem_type >= 0x80000000) {
+			return E820_RAM;
+		}
+		return E820_UNUSABLE;
+	}
+}
+
+static unsigned char scratch[32768];
+static struct e820entry e820table[128];
+
+int process_memory_map(EFI_SYSTEM_TABLE *sys, UINTN *_key, int silent) {
+	EFI_MEMORY_DESCRIPTOR *mmap;
+	struct e820entry *entry = e820table;
+	UINTN msize, off;
+	UINTN mkey, dsize;
+	UINT32 dversion;
+	unsigned n, type;
+	EFI_STATUS r;
+
+	msize = sizeof(scratch);
+	mmap = (EFI_MEMORY_DESCRIPTOR*) scratch;
+	mkey = dsize = dversion = 0;
+	r = sys->BootServices->GetMemoryMap(&msize, mmap, &mkey, &dsize, &dversion);
+	if (!silent) printf("r=%lx msz=%lx key=%lx dsz=%lx dvn=%x\n", r, msize, mkey, dsize, dversion);	
+	if (r != EFI_SUCCESS) {
+		return -1;
+	}
+	if (msize > sizeof(scratch)) {
+		if (!silent) printf("Memory Table Too Large (%ld entries)\n", (msize / dsize));
+		return -1;
+	}
+	for (off = 0, n = 0; off < msize; off += dsize) {
+		mmap = (EFI_MEMORY_DESCRIPTOR*) (scratch + off);
+		type = e820type(mmap->Type);
+		if (type == E820_IGNORE) {
+			continue;
+		}
+		if ((n > 0) && (entry[n-1].type == type)) {
+			if ((entry[n-1].addr + entry[n-1].size) == mmap->PhysicalStart) {
+				entry[n-1].size += mmap->NumberOfPages * 4096UL;
+				continue;
+			}
+		}
+		entry[n].addr = mmap->PhysicalStart;
+		entry[n].size = mmap->NumberOfPages * 4096UL;
+		entry[n].type = type;
+		n++;
+		if (n == 128) {
+			if (!silent) printf("E820 Table Too Large (%ld raw entries)\n", (msize / dsize));
+			return -1;
+		}
+	}
+	*_key = mkey;
+	return n;
+}
+
+#define ZP_E820_COUNT		0x1E8	// byte
+#define ZP_SETUP		0x1F1	// start of setup structure
+#define ZP_SETUP_SECTS		0x1F1	// byte (setup_size/512-1)
+#define ZP_JUMP			0x200   // jump instruction
+#define ZP_HEADER		0x202	// word "HdrS"
+#define ZP_VERSION		0x206	// half 0xHHLL
+#define ZP_LOADER_TYPE		0x210	// byte
+#define ZP_RAMDISK_BASE		0x218	// word (ptr or 0)
+#define ZP_RAMDISK_SIZE		0x21C	// word (bytes)
+#define ZP_EXTRA_MAGIC		0x220	// word 
+#define ZP_CMDLINE		0x228	// word (ptr)
+#define ZP_SYSSIZE		0x1F4	// word (size/16)
+#define ZP_XLOADFLAGS		0x236	// half
+#define ZP_E820_TABLE		0x2D0	// 128 entries
+
+#define ZP_ACPI_RSD		0x080   // word phys ptr
+#define ZP_FB_BASE		0x090
+#define ZP_FB_WIDTH		0x094
+#define ZP_FB_HEIGHT		0x098
+#define ZP_FB_STRIDE		0x09C
+#define ZP_FB_FORMAT		0x0A0
+#define ZP_FB_REGBASE		0x0A4
+#define ZP_FB_SIZE		0x0A8
+
+#define ZP_MAGIC_VALUE		0xDBC64323
+
+#define ZP8(p,off)	(*((UINT8*)((p) + (off))))
+#define ZP16(p,off)	(*((UINT16*)((p) + (off))))
+#define ZP32(p,off)	(*((UINT32*)((p) + (off))))
+
+typedef struct {
+	UINT8 *zeropage;
+	UINT8 *cmdline;
+	void *image;
+	UINT32 pages;
+} kernel_t;
+
+void install_memmap(kernel_t *k, struct e820entry *memmap, unsigned count) {
+	memcpy(memmap, k->zeropage + ZP_E820_TABLE, sizeof(*memmap) * count);
+	ZP8(k->zeropage, ZP_E820_COUNT) = count;
+}
+
+void start_kernel(kernel_t *k) {
+	// 64bit entry is at offset 0x200
+	UINT64 entry = (UINT64) (k->image + 0x200);
+
+	// ebx = 0, ebp = 0, edi = 0, esi = zeropage
+	__asm__ __volatile__ (
+	"movl $0, %%ebp \n"
+	"cli \n"
+	"jmp *%[entry] \n"
+	:: [entry]"a"(entry),
+	   [zeropage] "S"(k->zeropage),
+	   "b"(0), "D"(0)
+	);
+	for (;;) ;
+}
+
+int load_kernel(EFI_BOOT_SERVICES *bs, uint8_t *image, size_t sz, kernel_t *k) {
+	UINT32 setup_sz;
+	UINT32 image_sz;
+	UINT32 setup_end;
+	EFI_PHYSICAL_ADDRESS mem;
+
+	k->zeropage = NULL;
+	k->cmdline = NULL;
+	k->image = NULL;
+	k->pages = 0;
+
+	if (sz < 1024) {
+		// way too small to be a kernel
+		goto fail;
+	}
+
+	if (ZP32(image, ZP_HEADER) != 0x53726448) {
+		printf("kernel: invalid setup magic %08x\n", ZP32(image, ZP_HEADER));
+		goto fail;
+	}
+	if (ZP16(image, ZP_VERSION) < 0x020B) {
+		printf("kernel: unsupported setup version %04x\n", ZP16(image, ZP_VERSION));
+		goto fail;
+	}
+	setup_sz = (ZP8(image, ZP_SETUP_SECTS) + 1) * 512;
+	image_sz = (ZP16(image, ZP_SYSSIZE) * 16);
+	setup_end = ZP_JUMP + ZP8(image, ZP_JUMP+1);
+
+	printf("setup %d image %d  hdr %04x-%04x\n", setup_sz, image_sz, ZP_SETUP, setup_end);
+	// image size may be rounded up, thus +15
+	if ((setup_sz < 1024) || ((setup_sz + image_sz) > (sz + 15))) {
+		printf("kernel: invalid image size\n");
+		goto fail;
+	}
+
+	mem = 0xFF000;
+	if (bs->AllocatePages(AllocateMaxAddress, EfiLoaderData, 1, &mem)) {
+		printf("kernel: cannot allocate 'zero page'\n");
+		goto fail;
+	}
+	k->zeropage = (void*) mem;
+
+	mem = 0xFF000;
+	if (bs->AllocatePages(AllocateMaxAddress, EfiLoaderData, 1, &mem)) {
+		printf("kernel: cannot allocate commandline\n");
+		goto fail;
+	}
+	k->cmdline = (void*) mem;
+
+	mem = 0x100000;
+	k->pages = (image_sz + 4095) / 4096;
+	if (bs->AllocatePages(AllocateAddress, EfiLoaderData, k->pages + 1, &mem)) {
+		printf("kernel: cannot allocate kernel\n");
+		goto fail;
+	}
+	k->image = (void*) mem;
+
+	// setup zero page, copy setup header from kernel binary
+	ZeroMem(k->zeropage, 4096);
+	CopyMem(k->zeropage + ZP_SETUP, image + ZP_SETUP, setup_end - ZP_SETUP);
+
+	CopyMem(k->image, image + setup_sz, image_sz);
+
+	// empty commandline for now
+	ZP32(k->zeropage, ZP_CMDLINE) = (uint64_t) k->cmdline;
+	k->cmdline[0] = 0;
+
+	// no ramdisk for now
+	ZP32(k->zeropage, ZP_RAMDISK_BASE) = 0;
+	ZP32(k->zeropage, ZP_RAMDISK_SIZE) = 0;
+
+	// undefined bootloader
+	ZP8(k->zeropage, ZP_LOADER_TYPE) = 0xFF;
+
+	printf("kernel @%p, zeropage @%p, cmdline @%p\n",
+		k->image, k->zeropage, k->cmdline);
+
+	return 0;
+fail:
+	if (k->image) {
+		bs->FreePages((EFI_PHYSICAL_ADDRESS) k->image, k->pages);
+	}
+	if (k->cmdline) {
+		bs->FreePages((EFI_PHYSICAL_ADDRESS) k->cmdline, 1);
+	}
+	if (k->zeropage) {
+		bs->FreePages((EFI_PHYSICAL_ADDRESS) k->zeropage, 1);
+	}
+
+	return -1;
+}
+
+static EFI_GUID GraphicsOutputProtocol = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID;
+
+void dump_graphics_modes(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop) {
+	EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *info;
+	UINTN sz;
+	UINT32 num;
+	for (num = 0; num < gop->Mode->MaxMode; num++) {
+		if (gop->QueryMode(gop, num, &sz, &info)) {
+			continue;
+		}
+		printf("Mode %d  %d x %d (stride %d) fmt %d\n",
+			num, info->HorizontalResolution, info->VerticalResolution,
+			info->PixelsPerScanLine, info->PixelFormat);
+		if (info->PixelFormat == PixelBitMask) {
+			printf("Mode %d R:%08x G:%08x B:%08x X:%08x\n", num,
+				info->PixelInformation.RedMask,
+				info->PixelInformation.GreenMask,
+				info->PixelInformation.BlueMask,
+				info->PixelInformation.ReservedMask);
+		}
+	}
+}
+
+static EFI_GUID AcpiTableGUID = ACPI_TABLE_GUID;
+static EFI_GUID Acpi2TableGUID = ACPI_20_TABLE_GUID;
+
+static UINT8 ACPI_RSD_PTR[8] = "RSD PTR ";
+
+uint32_t find_acpi_root(EFI_HANDLE img, EFI_SYSTEM_TABLE *sys) {
+	EFI_CONFIGURATION_TABLE *cfgtab = sys->ConfigurationTable;
+	int i;
+
+	for (i = 0; i < sys->NumberOfTableEntries; i++) {
+		if (!CompareGuid(&cfgtab[i].VendorGuid, &AcpiTableGUID) &&
+			!CompareGuid(&cfgtab[i].VendorGuid, &Acpi2TableGUID)) {
+			// not an ACPI table
+			continue;
+		}
+		if (CompareMem(cfgtab[i].VendorTable, ACPI_RSD_PTR, 8)) {
+			// not the Root Description Pointer
+			continue;
+		}
+		return (uint64_t) cfgtab[i].VendorTable;
+	}
+	return 0;
+}
+
+static EFI_GRAPHICS_OUTPUT_PROTOCOL *gop;
+
+int boot_kernel(EFI_HANDLE img, EFI_SYSTEM_TABLE *sys, void *image, size_t sz) {
+	kernel_t kernel;
+	EFI_STATUS r;
+	UINTN key;
+	int n;
+
+	printf("boot_kernel() from %p (%ld bytes)\n", image, sz);
+
+	if (load_kernel(sys->BootServices, image, sz, &kernel)) {
+		printf("Failed to load kernel image\n");
+		goto fail;
+	}
+
+	ZP32(kernel.zeropage, ZP_EXTRA_MAGIC) = ZP_MAGIC_VALUE;
+	ZP32(kernel.zeropage, ZP_ACPI_RSD) = find_acpi_root(img, sys);
+
+	ZP32(kernel.zeropage, ZP_FB_BASE) = (UINT32) gop->Mode->FrameBufferBase;
+	ZP32(kernel.zeropage, ZP_FB_WIDTH) = (UINT32) gop->Mode->Info->HorizontalResolution;
+	ZP32(kernel.zeropage, ZP_FB_HEIGHT) = (UINT32) gop->Mode->Info->VerticalResolution;
+	ZP32(kernel.zeropage, ZP_FB_STRIDE) = (UINT32) gop->Mode->Info->PixelsPerScanLine;
+	ZP32(kernel.zeropage, ZP_FB_FORMAT) = 4; // XRGB32
+	ZP32(kernel.zeropage, ZP_FB_REGBASE) = 0;
+	ZP32(kernel.zeropage, ZP_FB_SIZE) = 256*1024*1024;
+
+	n = process_memory_map(sys, &key, 0);
+	if (n > 0) {
+		struct e820entry *e = e820table;
+		while (n > 0) {
+			printf("%016lx %016lx %s\n", e->addr, e->size, e820name[e->type]);
+			e++;
+			n--;
+		}
+	}
+
+	r = sys->BootServices->ExitBootServices(img, key);
+	if (r == EFI_INVALID_PARAMETER) {
+		n = process_memory_map(sys, &key, 1);
+		r = sys->BootServices->ExitBootServices(img, key);
+		if (r) {
+			printf("Cannot Exit Services! %ld\n", r);
+		} else {
+			install_memmap(&kernel, e820table, n);
+			start_kernel(&kernel);
+		}
+	} else if (r) {
+		printf("Cannot Exit! %ld\n", r);
+	} else {
+		for (;;) ;
+	}
+fail:
+	return -1;
+}
+
+EFI_STATUS efi_main(EFI_HANDLE img, EFI_SYSTEM_TABLE *sys) {
+	EFI_BOOT_SERVICES *bs = sys->BootServices;
+	EFI_PHYSICAL_ADDRESS mem;
+	void *image;
+	UINTN sz;
+
+        InitializeLib(img, sys);
+	InitGoodies(img, sys);
+
+	printf("\nOSBOOT v0.2\n\n");
+
+	bs->LocateProtocol(&GraphicsOutputProtocol, NULL, (void**) &gop);
+	printf("Framebuffer base is at %lx\n\n", gop->Mode->FrameBufferBase);
+
+	image = LoadFile(L"lk.bin", &sz);
+	if (image != NULL) {
+		boot_kernel(img, sys, image, sz);
+		goto fail;
+	}
+	printf("Failed to load 'lk.bin' from boot media\n\n");
+
+	if (bs->AllocatePages(AllocateAnyPages, EfiLoaderData, 1024, &mem)) {
+		printf("Failed to allocate network io buffer\n");
+		goto fail;
+	}
+	image = (void*) mem;
+	if (netboot_init(image, 1024 * 4096)) {
+		printf("Failed to initialize NetBoot\n");
+		goto fail;
+	}
+	printf("\nNetBoot Server Started...\n\n");
+	for (;;) {
+		int n = netboot_poll();
+		if (n < 1024) continue;
+
+		uint8_t *x = image;
+		if ((x[0]=='M') && (x[1]=='Z') && (x[0x80]=='P') && (x[0x81]=='E')) {
+			UINTN exitdatasize;
+			EFI_STATUS r;
+			EFI_HANDLE h;
+			printf("Attempting to run EFI binary...\n");
+			r = bs->LoadImage(FALSE, img, NULL, image, n, &h);
+			if (r != EFI_SUCCESS) {
+				printf("LoadImage Failed %ld\n", r);
+				continue;
+			}
+			r = bs->StartImage(h, &exitdatasize, NULL);
+			if (r != EFI_SUCCESS) {
+				printf("StartImage Failed %ld\n", r);
+				continue;
+			}
+			printf("\nNetBoot Server Resuming...\n");
+			continue;
+		}
+
+		// maybe it's a kernel image?
+		boot_kernel(img, sys, image, n);
+		goto fail;
+	}
+
+fail:
+	printf("\nBoot Failure\n");
+	WaitAnyKey();
+	return EFI_SUCCESS;
+}
diff --git a/src/showmem.c b/src/showmem.c
new file mode 100644
index 0000000..b442ece
--- /dev/null
+++ b/src/showmem.c
@@ -0,0 +1,87 @@
+// Copyright 2016 Google Inc. All Rights Reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files
+// (the "Software"), to deal in the Software without restriction,
+// including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software,
+// and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#include <efi.h>
+#include <efilib.h>
+
+#include <printf.h>
+
+static const char *MemTypeName(UINT32 type, char *buf) {
+	switch (type) {
+	case EfiReservedMemoryType:	return "Reserved";
+	case EfiLoaderCode:		return "LoaderCode";
+	case EfiLoaderData:		return "LoaderData";
+	case EfiBootServicesCode:	return "BootSvcsCode";
+	case EfiBootServicesData:	return "BootSvcsData";
+	case EfiRuntimeServicesCode:	return "RunTimeCode";
+	case EfiRuntimeServicesData:	return "RunTimeData";
+	case EfiConventionalMemory:	return "Conventional";
+	case EfiUnusableMemory:		return "Unusable";
+	case EfiACPIReclaimMemory:	return "ACPIReclaim";
+	case EfiACPIMemoryNVS:		return "ACPINonVolMem";
+	case EfiMemoryMappedIO:		return "MemMappedIO";
+	case EfiMemoryMappedIOPortSpace: return "MemMappedPort";
+	case EfiPalCode:		return "PalCode";
+	default:
+		sprintf(buf, "0x%08x", type);
+		return buf;
+	}
+}
+
+static unsigned char scratch[4096];
+
+static void dump_memmap(EFI_SYSTEM_TABLE *systab) {
+	EFI_STATUS r;
+	UINTN msize, off;
+	EFI_MEMORY_DESCRIPTOR *mmap;
+	UINTN mkey, dsize;
+	UINT32 dversion;
+	char tmp[32];
+
+	msize = sizeof(scratch);
+	mmap = (EFI_MEMORY_DESCRIPTOR*) scratch;
+	mkey = dsize = dversion;
+	r = systab->BootServices->GetMemoryMap(&msize, mmap, &mkey, &dsize, &dversion);
+	printf("r=%lx msz=%lx key=%lx dsz=%lx dvn=%x\n",
+		r, msize, mkey, dsize, dversion);	
+	if (r != EFI_SUCCESS) {
+		return;
+	}
+	for (off = 0; off < msize; off += dsize) {
+		mmap = (EFI_MEMORY_DESCRIPTOR*) (scratch + off);
+		printf("%016lx %016lx %08lx %c %04lx %s\n",
+			mmap->PhysicalStart, mmap->VirtualStart,
+			mmap->NumberOfPages, 
+			mmap->Attribute & EFI_MEMORY_RUNTIME ? 'R' : '-',
+			mmap->Attribute & 0xFFFF,
+			MemTypeName(mmap->Type, tmp));
+	}
+}
+
+#include <goodies.h>
+
+EFI_STATUS efi_main(EFI_HANDLE img, EFI_SYSTEM_TABLE *sys) {
+        InitializeLib(img, sys);
+	InitGoodies(img, sys);
+	dump_memmap(sys);
+	WaitAnyKey();
+        return EFI_SUCCESS;
+}
diff --git a/src/usbtest.c b/src/usbtest.c
new file mode 100644
index 0000000..4026024
--- /dev/null
+++ b/src/usbtest.c
@@ -0,0 +1,161 @@
+// Copyright 2016 Google Inc. All Rights Reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files
+// (the "Software"), to deal in the Software without restriction,
+// including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software,
+// and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#include <efi.h>
+#include <efilib.h>
+
+#include <stdio.h>
+#include <goodies.h>
+
+#include <Protocol/UsbIo.h>
+
+EFI_GUID UsbIoProtocol = EFI_USB_IO_PROTOCOL_GUID;
+
+EFIAPI EFI_STATUS MyDriverSupported(
+	EFI_DRIVER_BINDING *self, EFI_HANDLE ctlr,
+	EFI_DEVICE_PATH *path) {
+
+	EFI_USB_DEVICE_DESCRIPTOR dev;
+	EFI_USB_IO_PROTOCOL *usbio;
+	EFI_STATUS r;
+
+	r = gBS->OpenProtocol(ctlr, &UsbIoProtocol,
+		(void**) &usbio, self->DriverBindingHandle,
+		ctlr, EFI_OPEN_PROTOCOL_BY_DRIVER);
+
+	if (r == 0) {
+		if (usbio->UsbGetDeviceDescriptor(usbio, &dev)) {
+			return EFI_UNSUPPORTED;
+		}
+		printf("Supported? ctlr=%p vid=%04x pid=%04x\n",
+			ctlr, dev.IdVendor, dev.IdProduct);
+		gBS->CloseProtocol(ctlr, &UsbIoProtocol,
+			self->DriverBindingHandle, ctlr);
+		return EFI_SUCCESS;
+	}
+	return EFI_UNSUPPORTED;
+}
+
+EFIAPI EFI_STATUS MyDriverStart(
+	EFI_DRIVER_BINDING *self, EFI_HANDLE ctlr,
+	EFI_DEVICE_PATH *path) {
+	EFI_STATUS r;
+
+	EFI_USB_IO_PROTOCOL *usbio;
+
+	printf("Start! ctlr=%p\n", ctlr);
+
+	r = gBS->OpenProtocol(ctlr, &UsbIoProtocol,
+		(void**) &usbio, self->DriverBindingHandle,
+		ctlr, EFI_OPEN_PROTOCOL_BY_DRIVER);
+
+	// alloc device state, stash usbio with it
+	// probably attached to a protocol installed on a child handle
+
+	if (r) {
+		printf("OpenProtocol Failed %lx\n", r);
+		return EFI_DEVICE_ERROR;
+	}
+	return EFI_SUCCESS;
+}
+
+EFIAPI EFI_STATUS MyDriverStop(
+	EFI_DRIVER_BINDING *self, EFI_HANDLE ctlr,
+	UINTN count, EFI_HANDLE *children) {
+
+	printf("Stop! ctlr=%p\n", ctlr);
+
+	// recover device state, tear down
+
+	gBS->CloseProtocol(ctlr, &UsbIoProtocol,
+		self->DriverBindingHandle, ctlr);
+	return EFI_SUCCESS;
+}
+
+static EFI_DRIVER_BINDING MyDriver = {
+	.Supported = MyDriverSupported,
+	.Start = MyDriverStart,
+	.Stop = MyDriverStop,
+	.Version = 32,
+	.ImageHandle = NULL,
+	.DriverBindingHandle = NULL,
+};
+
+void InstallMyDriver(EFI_HANDLE img, EFI_SYSTEM_TABLE *sys) {
+	EFI_BOOT_SERVICES *bs = sys->BootServices;
+	EFI_HANDLE *list;
+	UINTN count, i;
+	EFI_STATUS r;
+
+	MyDriver.ImageHandle = img;
+	MyDriver.DriverBindingHandle = img;
+	r = bs->InstallProtocolInterface(&img, &DriverBindingProtocol,
+		EFI_NATIVE_INTERFACE, &MyDriver);
+	if (r) {
+		Print(L"DriverBinding failed %lx\n", r);
+		return;
+	}
+
+	// For every Handle that supports UsbIoProtocol, try to connect the driver
+	r = bs->LocateHandleBuffer(ByProtocol, &UsbIoProtocol, NULL, &count, &list);
+	if (r == 0) {
+		for (i = 0; i < count; i++) {
+			r = bs->ConnectController(list[i], NULL, NULL, FALSE);
+		}
+		bs->FreePool(list);
+	}
+
+}
+
+void RemoveMyDriver(EFI_HANDLE img, EFI_SYSTEM_TABLE *sys) {
+	EFI_BOOT_SERVICES *bs = sys->BootServices;
+	EFI_HANDLE *list;
+	UINTN count, i;
+	EFI_STATUS r;
+
+	// Disconnect the driver
+	r = bs->LocateHandleBuffer(ByProtocol, &UsbIoProtocol, NULL, &count, &list);
+	if (r == 0) {
+		for (i = 0; i < count; i++) {
+			r = bs->DisconnectController(list[i], img, NULL);
+		}
+		bs->FreePool(list);
+	}
+
+	// Unregister so we can safely exit
+	r = bs->UninstallProtocolInterface(img, &DriverBindingProtocol, &MyDriver);
+	if (r) printf("UninstallProtocol failed %lx\n", r);
+}
+
+EFI_STATUS efi_main(EFI_HANDLE img, EFI_SYSTEM_TABLE *sys) {
+        InitializeLib(img, sys);
+	InitGoodies(img, sys);
+
+	Print(L"Hello, EFI World\n");
+
+	InstallMyDriver(img, sys);
+
+	// do stuff
+
+	RemoveMyDriver(img, sys);
+
+	return EFI_SUCCESS;
+}