[Feature]add MT2731_MP2_MR2_SVN388 baseline version

Change-Id: Ief04314834b31e27effab435d3ca8ba33b499059
diff --git a/src/bsp/lk/dev/cache/pl310/include/dev/cache/pl310.h b/src/bsp/lk/dev/cache/pl310/include/dev/cache/pl310.h
new file mode 100644
index 0000000..c012f78
--- /dev/null
+++ b/src/bsp/lk/dev/cache/pl310/include/dev/cache/pl310.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2014 Travis Geiselbrecht
+ *
+ * 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
+
+#include <sys/types.h>
+#include <stdbool.h>
+
+status_t pl310_set_enable(bool enable);
+
+/* operations */
+void pl310_invalidate(void);
+void pl310_flush_invalidate(void);
+void pl310_sync_range(void);
+void pl310_clean_range(addr_t start, size_t len);
+void pl310_clean_invalidate_range(addr_t start, size_t len);
+void pl310_invalidate_range(addr_t start, size_t len);
+
+void pl310_pin_cache_range(addr_t start, size_t len);
diff --git a/src/bsp/lk/dev/cache/pl310/pl310.c b/src/bsp/lk/dev/cache/pl310/pl310.c
new file mode 100644
index 0000000..13c5916
--- /dev/null
+++ b/src/bsp/lk/dev/cache/pl310/pl310.c
@@ -0,0 +1,234 @@
+/*
+ * Copyright (c) 2014 Travis Geiselbrecht
+ *
+ * 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 <dev/cache/pl310.h>
+
+#include <assert.h>
+#include <trace.h>
+#include <err.h>
+#include <reg.h>
+#include <stdlib.h>
+#include <arch.h>
+#include <arch/arm/mmu.h>
+#include <dev/cache/pl310_config.h>
+#include <lk/init.h>
+
+/* configuration of the pl310 comes from #define space */
+#ifndef PL310_BASE
+#error need to define PL310_BASE
+#endif
+
+#define LOCAL_TRACE 0
+
+#define PL310_REG(reg) (*REG32(PL310_BASE + (reg)))
+
+/* registers */
+#define REG0_CACHE_ID              0x000
+#define REG0_CACHE_TYPE            0x004
+#define REG1_CONTROL               0x100
+#define REG1_AUX_CONTROL           0x104
+#define REG1_TAG_RAM_CONTROL       0x108
+#define REG1_DATA_RAM_CONTROL      0x10c
+#define REG2_EV_COUNTER_CTRL       0x200
+#define REG2_EV_COUNTER1_CFG       0x204
+#define REG2_EV_COUNTER0_CFG       0x208
+#define REG2_EV_COUNTER1           0x20c
+#define REG2_EV_COUNTER0           0x210
+#define REG2_INT_MASK              0x214
+#define REG2_INT_MASK_STATUS       0x218
+#define REG2_INT_RAW_STATUS        0x21c
+#define REG2_INT_CLEAR             0x220
+#define REG7_CACHE_SYNC            0x730
+#define REG7_INV_PA                0x770
+#define REG7_INV_WAY               0x77c
+#define REG7_CLEAN_PA              0x7b0
+#define REG7_CLEAN_INDEX           0x7b8
+#define REG7_CLEAN_WAY             0x7bc
+#define REG7_CLEAN_INV_PA          0x7f0
+#define REG7_CLEAN_INV_INDEX       0x7f8
+#define REG7_CLEAN_INV_WAY         0x7fc
+#define REG9_D_LOCKDOWN0           0x900
+#define REG9_I_LOCKDOWN0           0x904
+#define REG9_D_LOCKDOWN1           0x908
+#define REG9_I_LOCKDOWN1           0x90c
+#define REG9_D_LOCKDOWN2           0x910
+#define REG9_I_LOCKDOWN2           0x914
+#define REG9_D_LOCKDOWN3           0x918
+#define REG9_I_LOCKDOWN3           0x91c
+#define REG9_D_LOCKDOWN4           0x920
+#define REG9_I_LOCKDOWN4           0x924
+#define REG9_D_LOCKDOWN5           0x928
+#define REG9_I_LOCKDOWN5           0x92c
+#define REG9_D_LOCKDOWN6           0x930
+#define REG9_I_LOCKDOWN6           0x934
+#define REG9_D_LOCKDOWN7           0x938
+#define REG9_I_LOCKDOWN7           0x93c
+#define REG9_LOCK_LINE_EN          0x950
+#define REG9_UNLOCK_WAY            0x954
+#define REG12_ADDR_FILTERING_START 0xc00
+#define REG12_ADDR_FILTERING_END   0xc04
+#define REG15_DEBUG_CTRL           0xf40
+#define REG15_PREFETCH_CTRL        0xf60
+#define REG15_POWER_CTRL           0xf80
+
+static inline bool pl310_enabled(void)
+{
+    return !!(PL310_REG(REG1_CONTROL) & 1);
+}
+
+static void pl310_init(uint level)
+{
+    /* make sure it's already disabled */
+    DEBUG_ASSERT(!pl310_enabled());
+
+    /* set tag and data ram latency */
+    PL310_REG(REG1_TAG_RAM_CONTROL) = PL310_TAG_RAM_LATENCY;
+    PL310_REG(REG1_DATA_RAM_CONTROL) = PL310_DATA_RAM_LATENCY;
+
+    /* configure */
+    /* early BRESP enable, instruction/data prefetch, exclusive cache, full line of zero */
+    PL310_REG(REG1_AUX_CONTROL) |= (1<<30)|(1<<29)|(1<<28)|(1<<12)|(1<<0);
+
+    /* flush all the ways */
+    PL310_REG(REG7_INV_WAY) = 0xffff;
+}
+
+/* run just before arch_early_init so the L2 is ready to go when
+ * the arch code starts up the caching system.
+ */
+LK_INIT_HOOK(pl310_init, pl310_init, LK_INIT_LEVEL_ARCH_EARLY - 1);
+
+status_t pl310_set_enable(bool enable)
+{
+    LTRACEF("enable %d\n", enable);
+
+    if (enable) {
+        if ((PL310_REG(REG1_CONTROL) & 1) == 0) {
+            /* if disabled */
+            pl310_invalidate();
+            PL310_REG(REG1_CONTROL) = 1;
+        }
+    } else {
+        if ((PL310_REG(REG1_CONTROL) & 1) == 1) {
+            /* if enabled */
+            pl310_flush_invalidate();
+            PL310_REG(REG1_CONTROL) = 0;
+            /* this seems to not always latch on the first try */
+            while (PL310_REG(REG1_CONTROL) & 1) {
+                PL310_REG(REG1_CONTROL) = 0;
+            }
+        }
+    }
+
+    return NO_ERROR;
+}
+
+void pl310_invalidate(void)
+{
+    if (unlikely(!pl310_enabled()))
+        return;
+    PL310_REG(REG7_INV_WAY) = 0xffff;
+    while (PL310_REG(REG7_INV_WAY) != 0)
+        ;
+}
+
+void pl310_flush_invalidate(void)
+{
+    if (unlikely(!pl310_enabled()))
+        return;
+    PL310_REG(REG7_CLEAN_INV_WAY) = 0xffff;
+    while (PL310_REG(REG7_CLEAN_INV_WAY) != 0)
+        ;
+}
+
+void pl310_sync_range(void)
+{
+    if (unlikely(!pl310_enabled()))
+        return;
+
+    PL310_REG(REG7_CACHE_SYNC) = 1;
+}
+
+#define PL310_LOOP_BODY(reg) \
+    if (unlikely(!pl310_enabled())) \
+        return; \
+ \
+    addr_t pa = 0; \
+    uint32_t last_pa_page = 1; \
+    addr_t last_va = start + len; \
+    start &= ~(CACHE_LINE - 1); \
+    while (start < last_va) { \
+        if (unlikely(pa / PAGE_SIZE != last_pa_page)) { \
+            /* get the physical address */ \
+            if (unlikely(arm_vtop(start, &pa))) \
+                return; \
+            last_pa_page = pa / PAGE_SIZE; \
+        } \
+        PL310_REG(reg) = pa; \
+ \
+        pa += CACHE_LINE; \
+        start += CACHE_LINE; \
+    } \
+\
+    PL310_REG(REG7_CACHE_SYNC) = 1;
+
+void pl310_clean_range(addr_t start, size_t len)
+{
+    LTRACEF("start 0x%lx, len %zd\n", start, len);
+    PL310_LOOP_BODY(REG7_CLEAN_PA);
+}
+
+void pl310_clean_invalidate_range(addr_t start, size_t len)
+{
+    LTRACEF("start 0x%lx, len %zd\n", start, len);
+    PL310_LOOP_BODY(REG7_CLEAN_INV_PA);
+}
+
+void pl310_invalidate_range(addr_t start, size_t len)
+{
+    LTRACEF("start 0x%lx, len %zd\n", start, len);
+    PL310_LOOP_BODY(REG7_INV_PA);
+}
+
+void pl310_pin_cache_range(addr_t start, size_t len)
+{
+    len = ROUNDUP(len, CACHE_LINE);
+
+    arch_disable_ints();
+
+    arch_clean_invalidate_cache_range(start, len);
+
+    PL310_REG(REG9_LOCK_LINE_EN) = 1;
+    DSB;
+
+    while (len > 0) {
+        asm volatile("pld [%0]" :: "r"(start) : "memory");
+        start += CACHE_LINE;
+        len -= CACHE_LINE;
+    }
+
+    DSB;
+    PL310_REG(REG9_LOCK_LINE_EN) = 0;
+
+    arch_enable_ints();
+}
+
diff --git a/src/bsp/lk/dev/cache/pl310/rules.mk b/src/bsp/lk/dev/cache/pl310/rules.mk
new file mode 100644
index 0000000..36fef28
--- /dev/null
+++ b/src/bsp/lk/dev/cache/pl310/rules.mk
@@ -0,0 +1,10 @@
+LOCAL_DIR := $(GET_LOCAL_DIR)
+
+MODULE := $(LOCAL_DIR)
+
+GLOBAL_DEFINES +=
+
+MODULE_SRCS += \
+	$(LOCAL_DIR)/pl310.c
+
+include make/module.mk
diff --git a/src/bsp/lk/dev/class/block_api.c b/src/bsp/lk/dev/class/block_api.c
new file mode 100644
index 0000000..014eaba
--- /dev/null
+++ b/src/bsp/lk/dev/class/block_api.c
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2013 Corey Tabaka
+ *
+ * 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 <err.h>
+#include <dev/class/block.h>
+
+ssize_t class_block_get_size(struct device *dev)
+{
+	struct block_ops *ops = device_get_driver_ops(dev, struct block_ops, std);
+	if (!ops)
+		return ERR_NOT_CONFIGURED;
+	
+	if (ops->get_block_size)
+		return ops->get_block_size(dev);
+	else
+		return ERR_NOT_SUPPORTED;
+}
+
+ssize_t class_block_get_count(struct device *dev)
+{
+	struct block_ops *ops = device_get_driver_ops(dev, struct block_ops, std);
+	if (!ops)
+		return ERR_NOT_CONFIGURED;
+	
+	if (ops->get_block_count)
+		return ops->get_block_count(dev);
+	else
+		return ERR_NOT_SUPPORTED;
+}
+
+ssize_t class_block_write(struct device *dev, off_t offset, const void *buf, size_t count)
+{
+	struct block_ops *ops = device_get_driver_ops(dev, struct block_ops, std);
+	if (!ops)
+		return ERR_NOT_CONFIGURED;
+	
+	if (ops->write)
+		return ops->write(dev, offset, buf, count);
+	else
+		return ERR_NOT_SUPPORTED;
+}
+
+ssize_t class_block_read(struct device *dev, off_t offset, void *buf, size_t count)
+{
+	struct block_ops *ops = device_get_driver_ops(dev, struct block_ops, std);
+	if (!ops)
+		return ERR_NOT_CONFIGURED;
+	
+	if (ops->read)
+		return ops->read(dev, offset, buf, count);
+	else
+		return ERR_NOT_SUPPORTED;
+}
+
+status_t class_block_flush(struct device *dev)
+{
+	struct block_ops *ops = device_get_driver_ops(dev, struct block_ops, std);
+	if (!ops)
+		return ERR_NOT_CONFIGURED;
+	
+	if (ops->flush)
+		return ops->flush(dev);
+	else
+		return ERR_NOT_SUPPORTED;
+}
+
diff --git a/src/bsp/lk/dev/class/fb_api.c b/src/bsp/lk/dev/class/fb_api.c
new file mode 100644
index 0000000..314442e
--- /dev/null
+++ b/src/bsp/lk/dev/class/fb_api.c
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2013 Corey Tabaka
+ *
+ * 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 <err.h>
+#include <dev/class/fb.h>
+
+status_t class_fb_set_mode(struct device *dev, size_t width, size_t height, size_t bpp)
+{
+	struct fb_ops *ops = device_get_driver_ops(dev, struct fb_ops, std);
+	if (!ops)
+		return ERR_NOT_CONFIGURED;
+	
+	if (ops->set_mode)
+		return ops->set_mode(dev, width, height, bpp);
+	else
+		return ERR_NOT_SUPPORTED;
+}
+
+status_t class_fb_get_info(struct device *dev, struct fb_info *info)
+{
+	struct fb_ops *ops = device_get_driver_ops(dev, struct fb_ops, std);
+	if (!ops)
+		return ERR_NOT_CONFIGURED;
+	
+	if (ops->get_info)
+		return ops->get_info(dev, info);
+	else
+		return ERR_NOT_SUPPORTED;
+}
+
+status_t class_fb_update(struct device *dev)
+{
+	struct fb_ops *ops = device_get_driver_ops(dev, struct fb_ops, std);
+	if (!ops)
+		return ERR_NOT_CONFIGURED;
+	
+	if (ops->update)
+		return ops->update(dev);
+	else
+		return ERR_NOT_SUPPORTED;
+}
+
+status_t class_fb_update_region(struct device *dev, size_t x, size_t y, size_t width, size_t height)
+{
+	struct fb_ops *ops = device_get_driver_ops(dev, struct fb_ops, std);
+	if (!ops)
+		return ERR_NOT_CONFIGURED;
+	
+	if (ops->update_region)
+		return ops->update_region(dev, x, y, width, height);
+	else
+		return ERR_NOT_SUPPORTED;
+}
+
diff --git a/src/bsp/lk/dev/class/i2c_api.c b/src/bsp/lk/dev/class/i2c_api.c
new file mode 100644
index 0000000..60e2a8a
--- /dev/null
+++ b/src/bsp/lk/dev/class/i2c_api.c
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2013 Corey Tabaka
+ *
+ * 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 <err.h>
+#include <dev/class/i2c.h>
+
+status_t class_i2c_write(struct device *dev, uint8_t addr, const void *buf, size_t len)
+{
+	struct i2c_ops *ops = device_get_driver_ops(dev, struct i2c_ops, std);
+	if (!ops)
+		return ERR_NOT_CONFIGURED;
+	
+	if (ops->write)
+		return ops->write(dev, addr, buf, len);
+	else
+		return ERR_NOT_SUPPORTED;
+}
+
+status_t class_i2c_read(struct device *dev, uint8_t addr, void *buf, size_t len)
+{
+	struct i2c_ops *ops = device_get_driver_ops(dev, struct i2c_ops, std);
+	if (!ops)
+		return ERR_NOT_CONFIGURED;
+	
+	if (ops->read)
+		return ops->read(dev, addr, buf, len);
+	else
+		return ERR_NOT_SUPPORTED;
+}
+
+status_t class_i2c_write_reg(struct device *dev, uint8_t addr, uint8_t reg, uint8_t value)
+{
+	struct i2c_ops *ops = device_get_driver_ops(dev, struct i2c_ops, std);
+	if (!ops)
+		return ERR_NOT_CONFIGURED;
+	
+	if (ops->write_reg)
+		return ops->write_reg(dev, addr, reg, value);
+	else
+		return ERR_NOT_SUPPORTED;
+}
+
+status_t class_i2c_read_reg(struct device *dev, uint8_t addr, uint8_t reg, void *value)
+{
+	struct i2c_ops *ops = device_get_driver_ops(dev, struct i2c_ops, std);
+	if (!ops)
+		return ERR_NOT_CONFIGURED;
+	
+	if (ops->read_reg)
+		return ops->read_reg(dev, addr, reg, value);
+	else
+		return ERR_NOT_SUPPORTED;
+}
+
diff --git a/src/bsp/lk/dev/class/netif_api.c b/src/bsp/lk/dev/class/netif_api.c
new file mode 100644
index 0000000..87d617b
--- /dev/null
+++ b/src/bsp/lk/dev/class/netif_api.c
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2013 Corey Tabaka
+ *
+ * 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 <err.h>
+#include <dev/class/netif.h>
+
+status_t class_netif_set_state(struct device *dev, struct netstack_state *state)
+{
+	struct netif_ops *ops = device_get_driver_ops(dev, struct netif_ops, std);
+	if (!ops)
+		return ERR_NOT_CONFIGURED;
+	
+	if (ops->set_state)
+		return ops->set_state(dev, state);
+	else
+		return ERR_NOT_SUPPORTED;
+}
+
+ssize_t class_netif_get_hwaddr(struct device *dev, void *buf, size_t max_len)
+{
+	struct netif_ops *ops = device_get_driver_ops(dev, struct netif_ops, std);
+	if (!ops)
+		return ERR_NOT_CONFIGURED;
+	
+	if (ops->get_hwaddr)
+		return ops->get_hwaddr(dev, buf, max_len);
+	else
+		return ERR_NOT_SUPPORTED;
+}
+
+ssize_t class_netif_get_mtu(struct device *dev)
+{
+	struct netif_ops *ops = device_get_driver_ops(dev, struct netif_ops, std);
+	if (!ops)
+		return ERR_NOT_CONFIGURED;
+	
+	if (ops->get_mtu)
+		return ops->get_mtu(dev);
+	else
+		return ERR_NOT_SUPPORTED;
+}
+
+status_t class_netif_set_status(struct device *dev, bool up)
+{
+	struct netif_ops *ops = device_get_driver_ops(dev, struct netif_ops, std);
+	if (!ops)
+		return ERR_NOT_CONFIGURED;
+	
+	if (ops->set_status)
+		return ops->set_status(dev, up);
+	else
+		return ERR_NOT_SUPPORTED;
+}
+
+status_t class_netif_output(struct device *dev, struct pbuf *p)
+{
+	struct netif_ops *ops = device_get_driver_ops(dev, struct netif_ops, std);
+	if (!ops)
+		return ERR_NOT_CONFIGURED;
+	
+	if (ops->output)
+		return ops->output(dev, p);
+	else
+		return ERR_NOT_SUPPORTED;
+}
+
+status_t class_netif_mcast_filter(struct device *dev, const uint8_t *mac, int action)
+{
+	struct netif_ops *ops = device_get_driver_ops(dev, struct netif_ops, std);
+	if (!ops)
+		return ERR_NOT_CONFIGURED;
+	
+	if (ops->mcast_filter)
+		return ops->mcast_filter(dev, mac, action);
+	else
+		return ERR_NOT_SUPPORTED;
+}
+
diff --git a/src/bsp/lk/dev/class/spi_api.c b/src/bsp/lk/dev/class/spi_api.c
new file mode 100644
index 0000000..9453dc3
--- /dev/null
+++ b/src/bsp/lk/dev/class/spi_api.c
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2013 Corey Tabaka
+ *
+ * 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 <err.h>
+#include <dev/class/spi.h>
+
+ssize_t class_spi_transaction(struct device *dev, struct spi_transaction *txn, size_t count)
+{
+	struct spi_ops *ops = device_get_driver_ops(dev, struct spi_ops, std);
+	if (!ops)
+		return ERR_NOT_CONFIGURED;
+	
+	if (ops->transaction)
+		return ops->transaction(dev, txn, count);
+	else
+		return ERR_NOT_SUPPORTED;
+}
+
diff --git a/src/bsp/lk/dev/class/uart_api.c b/src/bsp/lk/dev/class/uart_api.c
new file mode 100644
index 0000000..31e2c9d
--- /dev/null
+++ b/src/bsp/lk/dev/class/uart_api.c
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2013 Corey Tabaka
+ *
+ * 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 <err.h>
+#include <dev/class/uart.h>
+
+ssize_t class_uart_read(struct device *dev, void *buf, size_t len)
+{
+	struct uart_ops *ops = device_get_driver_ops(dev, struct uart_ops, std);
+
+	if (!ops)
+		return ERR_NOT_CONFIGURED;
+	
+	if (ops->read)
+		return ops->read(dev, buf, len);
+	else
+		return ERR_NOT_SUPPORTED;
+}
+
+ssize_t class_uart_write(struct device *dev, const void *buf, size_t len)
+{
+	struct uart_ops *ops = device_get_driver_ops(dev, struct uart_ops, std);
+
+	if (!ops)
+		return ERR_NOT_CONFIGURED;
+	
+	if (ops->write)
+		return ops->write(dev, buf, len);
+	else
+		return ERR_NOT_SUPPORTED;
+}
+
diff --git a/src/bsp/lk/dev/dev.c b/src/bsp/lk/dev/dev.c
new file mode 100644
index 0000000..5288b51
--- /dev/null
+++ b/src/bsp/lk/dev/dev.c
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2008 Travis Geiselbrecht
+ *
+ * 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.
+ */
+
+void dev_init(void)
+{
+
+}
+
diff --git a/src/bsp/lk/dev/devices.ld b/src/bsp/lk/dev/devices.ld
new file mode 100644
index 0000000..1e16e65
--- /dev/null
+++ b/src/bsp/lk/dev/devices.ld
@@ -0,0 +1,8 @@
+SECTIONS {
+    .devices : {
+        __devices = .;
+        KEEP (*(.devices))
+        __devices_end = .;
+    }
+}
+INSERT AFTER .data;
diff --git a/src/bsp/lk/dev/driver.c b/src/bsp/lk/dev/driver.c
new file mode 100644
index 0000000..4b31ac1
--- /dev/null
+++ b/src/bsp/lk/dev/driver.c
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2012 Corey Tabaka
+ *
+ * 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 <dev/driver.h>
+#include <assert.h>
+#include <err.h>
+#include <trace.h>
+
+extern struct device __devices[];
+extern struct device __devices_end[];
+
+status_t device_init_all(void)
+{
+	status_t res = NO_ERROR;
+
+	struct device *dev = __devices;
+	while (dev != __devices_end) {
+		status_t code = device_init(dev);
+
+		if (code < 0) {
+			TRACEF("Driver init failed for driver \"%s\", device \"%s\", reason %d\n",
+					dev->driver->type, dev->name, code);
+
+			res = code;
+		}
+
+		dev++;
+	}
+
+	return res;
+}
+
+status_t device_fini_all(void)
+{
+	status_t res = NO_ERROR;
+
+	struct device *dev = __devices;
+	while (dev != __devices_end) {
+		status_t code = device_fini(dev);
+
+		if (code < 0) {
+			TRACEF("Driver fini failed for driver \"%s\", device \"%s\", reason %d\n",
+					dev->driver->type, dev->name, code);
+
+			res = code;
+		}
+
+		dev++;
+	}
+
+	return res;
+}
+
+status_t device_init(struct device *dev)
+{
+	if (!dev)
+		return ERR_INVALID_ARGS;
+
+	DEBUG_ASSERT(dev->driver);
+	
+	const struct driver_ops *ops = dev->driver->ops;
+
+	if (ops && ops->init)
+		return ops->init(dev);
+	else
+		return ERR_NOT_SUPPORTED;
+}
+
+status_t device_fini(struct device *dev)
+{
+	if (!dev)
+		return ERR_INVALID_ARGS;
+	
+	DEBUG_ASSERT(dev->driver);
+
+	const struct driver_ops *ops = dev->driver->ops;
+
+	if (ops && ops->fini)
+		return ops->fini(dev);
+	else
+		return ERR_NOT_SUPPORTED;
+}
+
+status_t device_suspend(struct device *dev)
+{
+	if (!dev)
+		return ERR_NOT_SUPPORTED;
+	
+	DEBUG_ASSERT(dev->driver);
+
+	const struct driver_ops *ops = dev->driver->ops;
+
+	if (ops && ops->suspend)
+		return ops->suspend(dev);
+	else
+		return ERR_NOT_SUPPORTED;
+}
+
+status_t device_resume(struct device *dev)
+{
+	if (!dev)
+		return ERR_NOT_SUPPORTED;
+	
+	DEBUG_ASSERT(dev->driver);
+
+	const struct driver_ops *ops = dev->driver->ops;
+
+	if (ops && ops->resume)
+		return ops->resume(dev);
+	else
+		return ERR_NOT_SUPPORTED;
+}
+
diff --git a/src/bsp/lk/dev/drivers.ld b/src/bsp/lk/dev/drivers.ld
new file mode 100644
index 0000000..62b7fff
--- /dev/null
+++ b/src/bsp/lk/dev/drivers.ld
@@ -0,0 +1,8 @@
+SECTIONS {
+    .drivers : {
+        __drivers = .;
+        KEEP (*(.drivers))
+        __drivers_end = .;
+    }
+}
+INSERT AFTER .rodata;
diff --git a/src/bsp/lk/dev/fbcon/fbcon.c b/src/bsp/lk/dev/fbcon/fbcon.c
new file mode 100644
index 0000000..796fc6f
--- /dev/null
+++ b/src/bsp/lk/dev/fbcon/fbcon.c
@@ -0,0 +1,200 @@
+/*
+ * Copyright (c) 2008, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <debug.h>
+#include <err.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <dev/fbcon.h>
+
+#include "font5x12.h"
+
+struct pos {
+	int x;
+	int y;
+};
+
+static struct fbcon_config *config = NULL;
+
+#define RGB565_BLUE     0x001f
+#define RGB565_WHITE        0xffff
+
+#define FONT_WIDTH      5
+#define FONT_HEIGHT     12
+
+static uint16_t         BGCOLOR;
+static uint16_t         FGCOLOR;
+
+static struct pos       cur_pos;
+static struct pos       max_pos;
+
+static void fbcon_drawglyph(uint16_t *pixels, uint16_t paint, unsigned stride,
+                            unsigned *glyph)
+{
+	unsigned x, y, data;
+	stride -= FONT_WIDTH;
+
+	data = glyph[0];
+	for (y = 0; y < (FONT_HEIGHT / 2); ++y) {
+		for (x = 0; x < FONT_WIDTH; ++x) {
+			if (data & 1)
+				*pixels = paint;
+			data >>= 1;
+			pixels++;
+		}
+		pixels += stride;
+	}
+
+	data = glyph[1];
+	for (y = 0; y < (FONT_HEIGHT / 2); y++) {
+		for (x = 0; x < FONT_WIDTH; x++) {
+			if (data & 1)
+				*pixels = paint;
+			data >>= 1;
+			pixels++;
+		}
+		pixels += stride;
+	}
+}
+
+static void fbcon_flush(void)
+{
+	if (config->update_start)
+		config->update_start();
+	if (config->update_done)
+		while (!config->update_done());
+}
+
+/* TODO: Take stride into account */
+static void fbcon_scroll_up(void)
+{
+	unsigned short *dst = config->base;
+	unsigned short *src = dst + (config->width * FONT_HEIGHT);
+	unsigned count = config->width * (config->height - FONT_HEIGHT);
+
+	while (count--) {
+		*dst++ = *src++;
+	}
+
+	count = config->width * FONT_HEIGHT;
+	while (count--) {
+		*dst++ = BGCOLOR;
+	}
+
+	fbcon_flush();
+}
+
+/* TODO: take stride into account */
+static void fbcon_clear(void)
+{
+	uint16_t *dst = config->base;
+	unsigned count = config->width * config->height;
+
+	cur_pos.x = 0;
+	cur_pos.y = 0;
+
+	while (count--)
+		*dst++ = BGCOLOR;
+}
+
+
+static void fbcon_set_colors(unsigned bg, unsigned fg)
+{
+	BGCOLOR = bg;
+	FGCOLOR = fg;
+}
+
+void fbcon_putc(char c)
+{
+	uint16_t *pixels;
+
+	/* ignore anything that happens before fbcon is initialized */
+	if (!config)
+		return;
+
+	if ((unsigned char)c > 127)
+		return;
+	if ((unsigned char)c < 32) {
+		if (c == '\n')
+			goto newline;
+		else if (c == '\r')
+			cur_pos.x = 0;
+		return;
+	}
+
+	pixels = config->base;
+	pixels += cur_pos.y * FONT_HEIGHT * config->width;
+	pixels += cur_pos.x * (FONT_WIDTH + 1);
+	fbcon_drawglyph(pixels, FGCOLOR, config->stride,
+	                font5x12 + (c - 32) * 2);
+
+	cur_pos.x++;
+	if (cur_pos.x < max_pos.x)
+		return;
+
+newline:
+	cur_pos.y++;
+	cur_pos.x = 0;
+	if (cur_pos.y >= max_pos.y) {
+		cur_pos.y = max_pos.y - 1;
+		fbcon_scroll_up();
+	} else
+		fbcon_flush();
+}
+
+void fbcon_setup(struct fbcon_config *_config)
+{
+	uint32_t bg;
+	uint32_t fg;
+
+	ASSERT(_config);
+
+	config = _config;
+
+	switch (config->format) {
+		case FB_FORMAT_RGB565:
+			bg = RGB565_BLUE;
+			fg = RGB565_WHITE;
+			break;
+
+		default:
+			dprintf(CRITICAL, "unknown framebuffer pixel format\n");
+			ASSERT(0);
+			break;
+	}
+
+	fbcon_set_colors(bg, fg);
+
+	fbcon_clear();
+	fbcon_flush();
+
+	cur_pos.x = 0;
+	cur_pos.y = 0;
+	max_pos.x = config->width / (FONT_WIDTH+1);
+	max_pos.y = (config->height - 1) / FONT_HEIGHT;
+}
diff --git a/src/bsp/lk/dev/fbcon/font5x12.h b/src/bsp/lk/dev/fbcon/font5x12.h
new file mode 100644
index 0000000..a7d09aa
--- /dev/null
+++ b/src/bsp/lk/dev/fbcon/font5x12.h
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+unsigned font5x12[] = {
+	0x00000000, 0x00000000,
+	0x08421080, 0x00020084,
+	0x00052940, 0x00000000,
+	0x15f52800, 0x0000295f,
+	0x1c52f880, 0x00023e94,
+	0x08855640, 0x0004d542,
+	0x04528800, 0x000b2725,
+	0x00021080, 0x00000000,
+	0x04211088, 0x00821042,
+	0x10841082, 0x00221108,
+	0x09575480, 0x00000000,
+	0x3e420000, 0x00000084,
+	0x00000000, 0x00223000,
+	0x3e000000, 0x00000000,
+	0x00000000, 0x00471000,
+	0x08844200, 0x00008442,
+	0x2318a880, 0x00022a31,
+	0x08429880, 0x000f9084,
+	0x1108c5c0, 0x000f8444,
+	0x1c4443e0, 0x00074610,
+	0x14a62100, 0x000423e9,
+	0x26d087e0, 0x00074610,
+	0x1e10c5c0, 0x00074631,
+	0x088443e0, 0x00010844,
+	0x1d18c5c0, 0x00074631,
+	0x3d18c5c0, 0x00074610,
+	0x08e20000, 0x00471000,
+	0x08e20000, 0x00223000,
+	0x02222200, 0x00082082,
+	0x01f00000, 0x000003e0,
+	0x20820820, 0x00008888,
+	0x1108c5c0, 0x00020084,
+	0x2b98c5c0, 0x000f05b5,
+	0x2318a880, 0x0008c63f,
+	0x1d2949e0, 0x0007ca52,
+	0x0210c5c0, 0x00074421,
+	0x252949e0, 0x0007ca52,
+	0x1e1087e0, 0x000f8421,
+	0x1e1087e0, 0x00008421,
+	0x0210c5c0, 0x00074639,
+	0x3f18c620, 0x0008c631,
+	0x084211c0, 0x00071084,
+	0x10842380, 0x00032508,
+	0x0654c620, 0x0008c525,
+	0x02108420, 0x000f8421,
+	0x2b5dc620, 0x0008c631,
+	0x2b59ce20, 0x0008c739,
+	0x2318c5c0, 0x00074631,
+	0x1f18c5e0, 0x00008421,
+	0x2318c5c0, 0x01075631,
+	0x1f18c5e0, 0x0008c525,
+	0x1c10c5c0, 0x00074610,
+	0x084213e0, 0x00021084,
+	0x2318c620, 0x00074631,
+	0x1518c620, 0x0002114a,
+	0x2b18c620, 0x000556b5,
+	0x08a54620, 0x0008c54a,
+	0x08a54620, 0x00021084,
+	0x088443e0, 0x000f8442,
+	0x0421084e, 0x00e10842,
+	0x08210420, 0x00084108,
+	0x1084210e, 0x00e42108,
+	0x0008a880, 0x00000000,
+	0x00000000, 0x01f00000,
+	0x00000104, 0x00000000,
+	0x20e00000, 0x000b663e,
+	0x22f08420, 0x0007c631,
+	0x22e00000, 0x00074421,
+	0x23e84200, 0x000f4631,
+	0x22e00000, 0x0007443f,
+	0x1e214980, 0x00010842,
+	0x22e00000, 0x1d187a31,
+	0x26d08420, 0x0008c631,
+	0x08601000, 0x00071084,
+	0x10c02000, 0x0c94a108,
+	0x0a908420, 0x0008a4a3,
+	0x084210c0, 0x00071084,
+	0x2ab00000, 0x0008d6b5,
+	0x26d00000, 0x0008c631,
+	0x22e00000, 0x00074631,
+	0x22f00000, 0x0210be31,
+	0x23e00000, 0x21087a31,
+	0x26d00000, 0x00008421,
+	0x22e00000, 0x00074506,
+	0x04f10800, 0x00064842,
+	0x23100000, 0x000b6631,
+	0x23100000, 0x00022951,
+	0x23100000, 0x000556b5,
+	0x15100000, 0x0008a884,
+	0x23100000, 0x1d185b31,
+	0x11f00000, 0x000f8444,
+	0x06421098, 0x01821084,
+	0x08421080, 0x00021084,
+	0x30421083, 0x00321084,
+	0x0004d640, 0x00000000,
+	0x00000000, 0x00000000,
+};
diff --git a/src/bsp/lk/dev/fbcon/rules.mk b/src/bsp/lk/dev/fbcon/rules.mk
new file mode 100644
index 0000000..18e1082
--- /dev/null
+++ b/src/bsp/lk/dev/fbcon/rules.mk
@@ -0,0 +1,8 @@
+LOCAL_DIR := $(GET_LOCAL_DIR)
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_SRCS += \
+	$(LOCAL_DIR)/fbcon.c
+
+include make/module.mk
diff --git a/src/bsp/lk/dev/gpio/debug.c b/src/bsp/lk/dev/gpio/debug.c
new file mode 100644
index 0000000..9a32ae5
--- /dev/null
+++ b/src/bsp/lk/dev/gpio/debug.c
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2015 Christopher Anderson
+ *
+ * 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 <dev/gpio.h>
+#include <lib/console.h>
+#include <string.h>
+#include <stdio.h>
+
+#if WITH_LIB_CONSOLE
+
+struct flag_labels {
+    unsigned id;
+    const char *label;
+};
+
+struct flag_labels gpio_flag_labels[] = {
+    { GPIO_INPUT,       "input" },
+    { GPIO_OUTPUT,      "output" },
+    { GPIO_LEVEL,       "level" },
+    { GPIO_EDGE,        "edge" },
+    { GPIO_RISING,      "rising" },
+    { GPIO_FALLING,     "falling" },
+    { GPIO_HIGH,        "high" },
+    { GPIO_LOW,         "low" },
+    { GPIO_PULLUP,      "pullup" },
+    { GPIO_PULLDOWN,    "pulldown" },
+};
+
+static unsigned int get_flag_value(const char *str)
+{
+    for (unsigned i = 0; i < countof(gpio_flag_labels); i++) {
+        if (!strcmp(str, gpio_flag_labels[i].label)) {
+            return gpio_flag_labels[i].id;
+        }
+    }
+
+    return 0;
+}
+
+/* Ideally this would be generic, but different platforms have varying manners of handling gpio
+ * numbers / banks etc. Nvidia uses A-F, ST uses # and ports, Xilinx uses #s and banks. Etc.
+ */
+static int cmd_gpio(int argc, const cmd_args *argv)
+{
+    if (argc == 4 && !strcmp(argv[1].str,"set")) {
+        unsigned int gpio = argv[2].u;
+        unsigned int value = argv[3].u;
+
+        gpio_set(gpio, value);
+    } else if (argc == 3 && !strcmp(argv[1].str,"get")) {
+        unsigned int gpio = argv[2].u;
+
+        printf("gpio %u: %d\n", gpio, gpio_get(gpio));
+    } else if (argc >= 3 && !strcmp(argv[1].str,"cfg")) {
+        unsigned int gpio = argv[2].u;
+        unsigned int flags = 0;
+
+        for (int i = 3; i < argc; i++) {
+            flags |= get_flag_value(argv[i].str);
+        }
+
+        gpio_config(gpio, flags);
+    } else {
+        printf("gpio set <gpio #> <value>\n");
+        printf("gpio get <gpio #>\n");
+        printf("gpio cfg <gpio #> [flags: ");
+        for (unsigned int i = 0; i < countof(gpio_flag_labels); i++) {
+            printf("%s ", gpio_flag_labels[i].label);
+        }
+        putchar(']');
+        putchar('\n');
+    }
+
+    return 0;
+}
+STATIC_COMMAND_START
+STATIC_COMMAND("gpio", "commands for manipulating system gpios", &cmd_gpio)
+STATIC_COMMAND_END(gpio);
+
+#endif
diff --git a/src/bsp/lk/dev/gpio/rules.mk b/src/bsp/lk/dev/gpio/rules.mk
new file mode 100644
index 0000000..fcee316
--- /dev/null
+++ b/src/bsp/lk/dev/gpio/rules.mk
@@ -0,0 +1,9 @@
+LOCAL_DIR := $(GET_LOCAL_DIR)
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_SRCS += \
+	$(LOCAL_DIR)/debug.c
+
+include make/module.mk
+
diff --git a/src/bsp/lk/dev/gpio_i2c/gpio_i2c.c b/src/bsp/lk/dev/gpio_i2c/gpio_i2c.c
new file mode 100644
index 0000000..b182930
--- /dev/null
+++ b/src/bsp/lk/dev/gpio_i2c/gpio_i2c.c
@@ -0,0 +1,287 @@
+/* vim: set expandtab ts=4 sw=4 tw=100: */
+/*
+ * Copyright (c) 2013 Google Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL 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 <assert.h>
+#include <compiler.h>
+#include <debug.h>
+#include <printf.h>
+#include <err.h>
+
+#include <dev/gpio.h>
+#include <dev/gpio_i2c.h>
+#include <kernel/mutex.h>
+
+#if (!(defined(GPIO_I2C_BUS_COUNT)) || (GPIO_I2C_BUS_COUNT <= 0))
+#error ERROR: Must define GPIO_I2C_BUS_COUNT
+#endif
+
+typedef struct gpio_i2c_state {
+    mutex_t                 lock;
+    const gpio_i2c_info_t*  info;
+} gpio_i2c_state_t;
+
+static gpio_i2c_state_t gpio_i2c_states[GPIO_I2C_BUS_COUNT];
+
+/******************************************************************************
+ *
+ * Internal implementation.
+ *
+ ******************************************************************************/
+static inline void send_start(const gpio_i2c_info_t* i)
+{
+    gpio_config(i->sda, GPIO_OUTPUT);
+    spin_cycles(i->qcd);
+    gpio_config(i->scl, GPIO_OUTPUT);
+    spin_cycles(i->hcd);
+}
+
+static inline void send_stop(const gpio_i2c_info_t* i)
+{
+    gpio_config(i->sda, GPIO_OUTPUT);
+    gpio_config(i->scl, GPIO_INPUT);
+    spin_cycles(i->qcd);
+    gpio_config(i->sda, GPIO_INPUT);
+}
+
+static inline void send_restart(const gpio_i2c_info_t* i)
+{
+    gpio_config(i->scl, GPIO_INPUT);
+    spin_cycles(i->qcd);
+    send_start(i);
+}
+
+static inline void send_nack(const gpio_i2c_info_t* i)
+{
+    spin_cycles(i->hcd);
+    gpio_config(i->scl, GPIO_INPUT);
+    spin_cycles(i->hcd);
+    gpio_config(i->scl, GPIO_OUTPUT);
+    gpio_config(i->sda, GPIO_INPUT);
+}
+
+static inline void send_ack(const gpio_i2c_info_t* i)
+{
+    gpio_config(i->sda, GPIO_OUTPUT);
+    send_nack(i);
+}
+
+static inline bool send_byte(const gpio_i2c_info_t* i, uint32_t b)
+{
+    bool ret;
+
+    for (size_t j = 0; j < 8; ++j) {
+        if (b & 0x80)
+            gpio_config(i->sda, GPIO_INPUT);
+        else
+            gpio_config(i->sda, GPIO_OUTPUT);
+        b <<= 1;
+        /* setup time for data (the time between when data becomes stable and
+         * clock becomes a stable high) is spec'ed to be 250ns for 100KHz i2c
+         * and 100nsec for 400KHz i2c.  If any micro running LK needs to spin
+         * here in order to hit that timing, they are welcome to add a spin
+         * right here.
+         */
+        spin_cycles(i->hcd);
+        gpio_config(i->scl, GPIO_INPUT);
+        spin_cycles(i->hcd);
+        gpio_config(i->scl, GPIO_OUTPUT);
+    }
+
+    gpio_config(i->sda, GPIO_INPUT);
+    spin_cycles(i->hcd);
+    gpio_config(i->scl, GPIO_INPUT);
+    spin_cycles(i->hcd);
+    ret = (0 == gpio_get(i->sda));
+    gpio_config(i->scl, GPIO_OUTPUT);
+    spin_cycles(i->hcd);
+
+    return ret;
+}
+
+static inline void recv_byte(const gpio_i2c_info_t* i, uint8_t* b)
+{
+    uint32_t tmp = 0;
+
+    for (size_t j = 0; j < 7; ++j) {
+        gpio_config(i->scl, GPIO_INPUT);
+        spin_cycles(i->hcd);
+        if (gpio_get(i->sda))
+            tmp |= 1;
+        tmp <<= 1;
+        gpio_config(i->scl, GPIO_OUTPUT);
+        spin_cycles(i->hcd);
+    }
+
+    gpio_config(i->scl, GPIO_INPUT);
+    spin_cycles(i->hcd);
+    if (gpio_get(i->sda))
+        tmp |= 1;
+    gpio_config(i->scl, GPIO_OUTPUT);
+
+    *b = (uint8_t)tmp;
+}
+
+static status_t gpio_i2c_tx_common(gpio_i2c_state_t* s,
+                                   uint8_t address,
+                                   const uint8_t* reg,
+                                   const void* buf,
+                                   size_t cnt)
+{
+    const gpio_i2c_info_t* i = s->info;
+    status_t ret = ERR_I2C_NACK;
+
+    DEBUG_ASSERT(buf || !cnt);
+
+    mutex_acquire(&s->lock);
+    send_start(i);
+    if (!send_byte(i, address << 1))
+        goto finished;
+
+    if ((NULL != reg) && !send_byte(i, *reg))
+        goto finished;
+
+    for (size_t j = 0; j < cnt; ++j)
+        if (!send_byte(i, ((const uint8_t*)buf)[j]))
+            goto finished;
+
+    ret = NO_ERROR;
+
+finished:
+    send_stop(i);
+    mutex_release(&s->lock);
+    return ret;
+}
+
+static status_t gpio_i2c_rx_common(gpio_i2c_state_t* s,
+                                   uint8_t address,
+                                   const uint8_t* reg,
+                                   void* buf,
+                                   size_t cnt)
+{
+    const gpio_i2c_info_t* i = s->info;
+    status_t ret = ERR_I2C_NACK;
+
+    DEBUG_ASSERT(buf && cnt);
+
+    address <<= 1;
+
+    mutex_acquire(&s->lock);
+    send_start(i);
+    if (!send_byte(i, address | (!reg ? 0x1 : 0x0)))
+        goto finished;
+
+    if (NULL != reg) {
+        if (!send_byte(i, *reg))
+            goto finished;
+
+        send_restart(i);
+
+        if (!send_byte(i, address | 0x1))
+            goto finished;
+    }
+
+    recv_byte(i, buf++);
+    for (size_t j = 0; j < (cnt - 1); ++j) {
+        send_ack(i);
+        recv_byte(i, buf++);
+    }
+    send_nack(i);
+    ret = NO_ERROR;
+
+finished:
+    send_stop(i);
+    mutex_release(&s->lock);
+    return ret;
+}
+
+void gpio_i2c_add_bus(uint32_t bus_id, const gpio_i2c_info_t* info)
+{
+    gpio_i2c_state_t* s = gpio_i2c_states + bus_id;
+
+    DEBUG_ASSERT(info);
+    DEBUG_ASSERT(bus_id < GPIO_I2C_BUS_COUNT);
+    DEBUG_ASSERT(!s->info);
+
+    gpio_config(info->scl, GPIO_INPUT);
+    gpio_config(info->sda, GPIO_INPUT);
+    gpio_set(info->scl, 0);
+    gpio_set(info->sda, 0);
+
+    mutex_init(&s->lock);
+    s->info = info;
+}
+
+/******************************************************************************
+*
+*  LK facing API
+*
+* *****************************************************************************/
+void gpio_i2c_init_early(void) { }
+void gpio_i2c_init(void) { }
+
+status_t gpio_i2c_transmit(int bus, uint8_t address, const void* buf, size_t cnt)
+{
+    gpio_i2c_state_t* s = gpio_i2c_states + bus;
+    if (((unsigned)bus >= countof(gpio_i2c_states)) || !s->info)
+        return ERR_NOT_FOUND;
+
+    return gpio_i2c_tx_common(s, address, NULL, buf, cnt);
+}
+
+status_t gpio_i2c_receive(int bus, uint8_t address, void* buf, size_t cnt)
+{
+    gpio_i2c_state_t* s = gpio_i2c_states + bus;
+    if (((unsigned)bus >= countof(gpio_i2c_states)) || !s->info)
+        return ERR_NOT_FOUND;
+
+    return gpio_i2c_rx_common(s, address, NULL, buf, cnt);
+}
+
+status_t gpio_i2c_write_reg_bytes(int bus, uint8_t address, uint8_t reg, const uint8_t* buf, size_t cnt)
+{
+    gpio_i2c_state_t* s = gpio_i2c_states + bus;
+    if (((unsigned)bus >= countof(gpio_i2c_states)) || !s->info)
+        return ERR_NOT_FOUND;
+
+    return gpio_i2c_tx_common(s, address, &reg, buf, cnt);
+}
+
+status_t gpio_i2c_read_reg_bytes(int bus, uint8_t address, uint8_t reg, uint8_t* buf, size_t cnt)
+{
+    gpio_i2c_state_t* s = gpio_i2c_states + bus;
+    if (((unsigned)bus >= countof(gpio_i2c_states)) || !s->info) {
+        return ERR_NOT_FOUND;
+    }
+
+    return gpio_i2c_rx_common(s, address, &reg, buf, cnt);
+}
+
+void i2c_init_early(void) __WEAK_ALIAS("gpio_i2c_init_early");
+void i2c_init(void) __WEAK_ALIAS("gpio_i2c_init");
+status_t i2c_transmit(int, uint8_t, const void*, size_t) __WEAK_ALIAS("gpio_i2c_transmit");
+status_t i2c_receive(int, uint8_t, void*, size_t) __WEAK_ALIAS("gpio_i2c_receive");
+status_t i2c_write_reg_bytes(int, uint8_t, uint8_t,
+                             const uint8_t*, size_t) __WEAK_ALIAS("gpio_i2c_write_reg_bytes");
+status_t i2c_read_reg_bytes(int, uint8_t, uint8_t,
+                            uint8_t*, size_t) __WEAK_ALIAS("gpio_i2c_read_reg_bytes");
diff --git a/src/bsp/lk/dev/gpio_i2c/rules.mk b/src/bsp/lk/dev/gpio_i2c/rules.mk
new file mode 100644
index 0000000..57dbbca
--- /dev/null
+++ b/src/bsp/lk/dev/gpio_i2c/rules.mk
@@ -0,0 +1,11 @@
+LOCAL_DIR := $(GET_LOCAL_DIR)
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_SRCS := \
+	$(LOCAL_DIR)/gpio_i2c.c
+
+MODULE_DEFINES += \
+	GPIO_I2C_BUS_COUNT=$(GPIO_I2C_BUS_COUNT)
+
+include make/module.mk
diff --git a/src/bsp/lk/dev/interrupt/arm_gic/arm_gic.c b/src/bsp/lk/dev/interrupt/arm_gic/arm_gic.c
new file mode 100644
index 0000000..b16a603
--- /dev/null
+++ b/src/bsp/lk/dev/interrupt/arm_gic/arm_gic.c
@@ -0,0 +1,618 @@
+/*
+ * Copyright (c) 2012-2015 Travis Geiselbrecht
+ *
+ * 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 <assert.h>
+#include <bits.h>
+#include <err.h>
+#include <sys/types.h>
+#include <debug.h>
+#include <dev/interrupt/arm_gic.h>
+#include <reg.h>
+#include <kernel/thread.h>
+#include <kernel/debug.h>
+#include <lk/init.h>
+#include <platform/interrupts.h>
+#include <arch/ops.h>
+#include <platform/gic.h>
+#include <trace.h>
+#if WITH_LIB_SM
+#include <lib/sm.h>
+#include <lib/sm/sm_err.h>
+#endif
+
+#define LOCAL_TRACE 0
+
+#if ARCH_ARM
+#include <arch/arm.h>
+#define iframe arm_iframe
+#define IFRAME_PC(frame) ((frame)->pc)
+#endif
+#if ARCH_ARM64
+#include <arch/arm64.h>
+#define iframe arm64_iframe_short
+#define IFRAME_PC(frame) ((frame)->elr)
+#endif
+
+static status_t arm_gic_set_secure_locked(u_int irq, bool secure);
+
+static spin_lock_t gicd_lock;
+#if WITH_LIB_SM
+#define GICD_LOCK_FLAGS SPIN_LOCK_FLAG_IRQ_FIQ
+#else
+#define GICD_LOCK_FLAGS SPIN_LOCK_FLAG_INTERRUPTS
+#endif
+#define GIC_MAX_PER_CPU_INT 32
+
+#if WITH_LIB_SM
+static bool arm_gic_non_secure_interrupts_frozen;
+
+static bool arm_gic_interrupt_change_allowed(int irq)
+{
+	if (!arm_gic_non_secure_interrupts_frozen)
+		return true;
+
+	TRACEF("change to interrupt %d ignored after booting ns\n", irq);
+	return false;
+}
+
+static void suspend_resume_fiq(bool resume_gicc, bool resume_gicd);
+#else
+static bool arm_gic_interrupt_change_allowed(int irq)
+{
+	return true;
+}
+
+static void suspend_resume_fiq(bool resume_gicc, bool resume_gicd)
+{
+}
+#endif
+
+
+struct int_handler_struct {
+	int_handler handler;
+	void *arg;
+};
+
+static struct int_handler_struct int_handler_table_per_cpu[GIC_MAX_PER_CPU_INT][SMP_MAX_CPUS];
+static struct int_handler_struct int_handler_table_shared[MAX_INT-GIC_MAX_PER_CPU_INT];
+
+static struct int_handler_struct *get_int_handler(unsigned int vector, uint cpu)
+{
+	if (vector < GIC_MAX_PER_CPU_INT)
+		return &int_handler_table_per_cpu[vector][cpu];
+	else
+		return &int_handler_table_shared[vector - GIC_MAX_PER_CPU_INT];
+}
+
+void register_int_handler(unsigned int vector, int_handler handler, void *arg)
+{
+	struct int_handler_struct *h;
+	uint cpu = arch_curr_cpu_num();
+
+	spin_lock_saved_state_t state;
+
+	if (vector >= MAX_INT)
+		panic("register_int_handler: vector out of range %d\n", vector);
+
+	spin_lock_save(&gicd_lock, &state, GICD_LOCK_FLAGS);
+
+	if (arm_gic_interrupt_change_allowed(vector)) {
+		h = get_int_handler(vector, cpu);
+		h->handler = handler;
+		h->arg = arg;
+	}
+
+	spin_unlock_restore(&gicd_lock, state, GICD_LOCK_FLAGS);
+}
+
+#define GICREG(gic, reg) (*REG32(GICBASE(gic) + (reg)))
+
+/* main cpu regs */
+#define GICC_CTLR               (GICC_OFFSET + 0x0000)
+#define GICC_PMR                (GICC_OFFSET + 0x0004)
+#define GICC_BPR                (GICC_OFFSET + 0x0008)
+#define GICC_IAR                (GICC_OFFSET + 0x000c)
+#define GICC_EOIR               (GICC_OFFSET + 0x0010)
+#define GICC_RPR                (GICC_OFFSET + 0x0014)
+#define GICC_HPPIR              (GICC_OFFSET + 0x0018)
+#define GICC_APBR               (GICC_OFFSET + 0x001c)
+#define GICC_AIAR               (GICC_OFFSET + 0x0020)
+#define GICC_AEOIR              (GICC_OFFSET + 0x0024)
+#define GICC_AHPPIR             (GICC_OFFSET + 0x0028)
+#define GICC_APR(n)             (GICC_OFFSET + 0x00d0 + (n) * 4)
+#define GICC_NSAPR(n)           (GICC_OFFSET + 0x00e0 + (n) * 4)
+#define GICC_IIDR               (GICC_OFFSET + 0x00fc)
+#define GICC_DIR                (GICC_OFFSET + 0x1000)
+
+/* distribution regs */
+#define GICD_CTLR               (GICD_OFFSET + 0x000)
+#define GICD_TYPER              (GICD_OFFSET + 0x004)
+#define GICD_IIDR               (GICD_OFFSET + 0x008)
+#define GICD_IGROUPR(n)         (GICD_OFFSET + 0x080 + (n) * 4)
+#define GICD_ISENABLER(n)       (GICD_OFFSET + 0x100 + (n) * 4)
+#define GICD_ICENABLER(n)       (GICD_OFFSET + 0x180 + (n) * 4)
+#define GICD_ISPENDR(n)         (GICD_OFFSET + 0x200 + (n) * 4)
+#define GICD_ICPENDR(n)         (GICD_OFFSET + 0x280 + (n) * 4)
+#define GICD_ISACTIVER(n)       (GICD_OFFSET + 0x300 + (n) * 4)
+#define GICD_ICACTIVER(n)       (GICD_OFFSET + 0x380 + (n) * 4)
+#define GICD_IPRIORITYR(n)      (GICD_OFFSET + 0x400 + (n) * 4)
+#define GICD_ITARGETSR(n)       (GICD_OFFSET + 0x800 + (n) * 4)
+#define GICD_ICFGR(n)           (GICD_OFFSET + 0xc00 + (n) * 4)
+#define GICD_NSACR(n)           (GICD_OFFSET + 0xe00 + (n) * 4)
+#define GICD_SGIR               (GICD_OFFSET + 0xf00)
+#define GICD_CPENDSGIR(n)       (GICD_OFFSET + 0xf10 + (n) * 4)
+#define GICD_SPENDSGIR(n)       (GICD_OFFSET + 0xf20 + (n) * 4)
+
+#define DIV_ROUND_UP(n,d) (((n) + (d) - 1) / (d))
+#define GIC_REG_COUNT(bit_per_reg) DIV_ROUND_UP(MAX_INT, (bit_per_reg))
+#define DEFINE_GIC_SHADOW_REG(name, bit_per_reg, init_val, init_from) \
+	uint32_t (name)[GIC_REG_COUNT(bit_per_reg)] = { \
+		[(init_from / bit_per_reg) ... \
+		 (GIC_REG_COUNT(bit_per_reg) - 1)] = (init_val) \
+	}
+
+#if WITH_LIB_SM
+static DEFINE_GIC_SHADOW_REG(gicd_igroupr, 32, ~0U, 0);
+#endif
+static DEFINE_GIC_SHADOW_REG(gicd_itargetsr, 4, 0x01010101, 32);
+
+static void gic_set_enable(uint vector, bool enable)
+{
+	int reg = vector / 32;
+	uint32_t mask = 1ULL << (vector % 32);
+
+	if (enable)
+		GICREG(0, GICD_ISENABLER(reg)) = mask;
+	else
+		GICREG(0, GICD_ICENABLER(reg)) = mask;
+}
+
+static void arm_gic_init_percpu(uint level)
+{
+#if WITH_LIB_SM
+	GICREG(0, GICC_CTLR) = 0xb; // enable GIC0 and select fiq mode for secure
+	GICREG(0, GICD_IGROUPR(0)) = ~0U; /* GICD_IGROUPR0 is banked */
+#else
+	GICREG(0, GICC_CTLR) = 1; // enable GIC0
+#endif
+	GICREG(0, GICC_PMR) = 0xFF; // unmask interrupts at all priority levels
+}
+
+LK_INIT_HOOK_FLAGS(arm_gic_init_percpu,
+       arm_gic_init_percpu,
+       LK_INIT_LEVEL_PLATFORM_EARLY, LK_INIT_FLAG_SECONDARY_CPUS);
+
+static void arm_gic_suspend_cpu(uint level)
+{
+	suspend_resume_fiq(false, false);
+}
+
+LK_INIT_HOOK_FLAGS(arm_gic_suspend_cpu, arm_gic_suspend_cpu,
+		LK_INIT_LEVEL_PLATFORM, LK_INIT_FLAG_CPU_SUSPEND);
+
+static void arm_gic_resume_cpu(uint level)
+{
+	spin_lock_saved_state_t state;
+	bool resume_gicd = false;
+
+	spin_lock_save(&gicd_lock, &state, GICD_LOCK_FLAGS);
+	if (!(GICREG(0, GICD_CTLR) & 1)) {
+		dprintf(SPEW, "%s: distibutor is off, calling arm_gic_init instead\n", __func__);
+		arm_gic_init();
+		resume_gicd = true;
+	} else {
+		arm_gic_init_percpu(0);
+	}
+	spin_unlock_restore(&gicd_lock, state, GICD_LOCK_FLAGS);
+	suspend_resume_fiq(true, resume_gicd);
+}
+
+LK_INIT_HOOK_FLAGS(arm_gic_resume_cpu, arm_gic_resume_cpu,
+		LK_INIT_LEVEL_PLATFORM, LK_INIT_FLAG_CPU_RESUME);
+
+static int arm_gic_max_cpu(void)
+{
+	return (GICREG(0, GICD_TYPER) >> 5) & 0x7;
+}
+
+void arm_gic_init(void)
+{
+	int i;
+
+	for (i = 0; i < MAX_INT; i+= 32) {
+		GICREG(0, GICD_ICENABLER(i / 32)) = ~0;
+		GICREG(0, GICD_ICPENDR(i / 32)) = ~0;
+	}
+
+	if (arm_gic_max_cpu() > 0) {
+		/* Set external interrupts to target cpu 0 */
+		for (i = 32; i < MAX_INT; i += 4) {
+			GICREG(0, GICD_ITARGETSR(i / 4)) = gicd_itargetsr[i / 4];
+		}
+	}
+
+	GICREG(0, GICD_CTLR) = 1; // enable GIC0
+#if WITH_LIB_SM
+	GICREG(0, GICD_CTLR) = 3; // enable GIC0 ns interrupts
+	/*
+	 * Iterate through all IRQs and set them to non-secure
+	 * mode. This will allow the non-secure side to handle
+	 * all the interrupts we don't explicitly claim.
+	 */
+	for (i = 32; i < MAX_INT; i += 32) {
+		u_int reg = i / 32;
+		GICREG(0, GICD_IGROUPR(reg)) = gicd_igroupr[reg];
+	}
+#endif
+	arm_gic_init_percpu(0);
+}
+
+static status_t arm_gic_set_secure_locked(u_int irq, bool secure)
+{
+#if WITH_LIB_SM
+	int reg = irq / 32;
+	uint32_t mask = 1ULL << (irq % 32);
+
+	if (irq >= MAX_INT)
+		return ERR_INVALID_ARGS;
+
+	if (secure)
+		GICREG(0, GICD_IGROUPR(reg)) = (gicd_igroupr[reg] &= ~mask);
+	else
+		GICREG(0, GICD_IGROUPR(reg)) = (gicd_igroupr[reg] |= mask);
+	LTRACEF("irq %d, secure %d, GICD_IGROUP%d = %x\n",
+		irq, secure, reg, GICREG(0, GICD_IGROUPR(reg)));
+#endif
+	return NO_ERROR;
+}
+
+static status_t arm_gic_set_target_locked(u_int irq, u_int cpu_mask, u_int enable_mask)
+{
+	u_int reg = irq / 4;
+	u_int shift = 8 * (irq % 4);
+	u_int old_val;
+	u_int new_val;
+
+	cpu_mask = (cpu_mask & 0xff) << shift;
+	enable_mask = (enable_mask << shift) & cpu_mask;
+
+	old_val = GICREG(0, GICD_ITARGETSR(reg));
+	new_val = (gicd_itargetsr[reg] & ~cpu_mask) | enable_mask;
+	GICREG(0, GICD_ITARGETSR(reg)) = gicd_itargetsr[reg] = new_val;
+	LTRACEF("irq %i, GICD_ITARGETSR%d %x => %x (got %x)\n",
+		irq, reg, old_val, new_val, GICREG(0, GICD_ITARGETSR(reg)));
+
+	return NO_ERROR;
+}
+
+static status_t arm_gic_get_priority(u_int irq)
+{
+	u_int reg = irq / 4;
+	u_int shift = 8 * (irq % 4);
+	return (GICREG(0, GICD_IPRIORITYR(reg)) >> shift) & 0xff;
+}
+
+static status_t arm_gic_set_priority_locked(u_int irq, uint8_t priority)
+{
+	u_int reg = irq / 4;
+	u_int shift = 8 * (irq % 4);
+	u_int mask = 0xff << shift;
+	uint32_t regval;
+
+	regval = GICREG(0, GICD_IPRIORITYR(reg));
+	LTRACEF("irq %i, old GICD_IPRIORITYR%d = %x\n", irq, reg, regval);
+	regval = (regval & ~mask) | ((uint32_t)priority << shift);
+	GICREG(0, GICD_IPRIORITYR(reg)) = regval;
+	LTRACEF("irq %i, new GICD_IPRIORITYR%d = %x, req %x\n",
+		irq, reg, GICREG(0, GICD_IPRIORITYR(reg)), regval);
+
+	return 0;
+}
+
+status_t arm_gic_sgi(u_int irq, u_int flags, u_int cpu_mask)
+{
+	u_int val =
+		((flags & ARM_GIC_SGI_FLAG_TARGET_FILTER_MASK) << 24) |
+		((cpu_mask & 0xff) << 16) |
+		((flags & ARM_GIC_SGI_FLAG_NS) ? (1U << 15) : 0) |
+		(irq & 0xf);
+
+	if (irq >= 16)
+		return ERR_INVALID_ARGS;
+
+	LTRACEF("GICD_SGIR: %x\n", val);
+
+	GICREG(0, GICD_SGIR) = val;
+
+	return NO_ERROR;
+}
+
+status_t mask_interrupt(unsigned int vector)
+{
+	if (vector >= MAX_INT)
+		return ERR_INVALID_ARGS;
+
+	if (arm_gic_interrupt_change_allowed(vector))
+		gic_set_enable(vector, false);
+
+	return NO_ERROR;
+}
+
+status_t unmask_interrupt(unsigned int vector)
+{
+	if (vector >= MAX_INT)
+		return ERR_INVALID_ARGS;
+
+	if (arm_gic_interrupt_change_allowed(vector))
+		gic_set_enable(vector, true);
+
+	return NO_ERROR;
+}
+
+static
+enum handler_return __platform_irq(struct iframe *frame)
+{
+	// get the current vector
+	uint32_t iar = GICREG(0, GICC_IAR);
+	unsigned int vector = iar & 0x3ff;
+
+	if (vector >= 0x3fe) {
+		// spurious
+		return INT_NO_RESCHEDULE;
+	}
+
+	THREAD_STATS_INC(interrupts);
+	KEVLOG_IRQ_ENTER(vector);
+
+	uint cpu = arch_curr_cpu_num();
+
+	LTRACEF_LEVEL(2, "iar 0x%x cpu %u currthread %p vector %d pc 0x%lx\n", iar, cpu,
+	       get_current_thread(), vector, (uintptr_t)IFRAME_PC(frame));
+
+	// deliver the interrupt
+	enum handler_return ret;
+
+	ret = INT_NO_RESCHEDULE;
+	struct int_handler_struct *handler = get_int_handler(vector, cpu);
+	if (handler->handler)
+		ret = handler->handler(handler->arg);
+
+	GICREG(0, GICC_EOIR) = iar;
+
+	LTRACEF_LEVEL(2, "cpu %u exit %d\n", cpu, ret);
+
+	KEVLOG_IRQ_EXIT(vector);
+
+	return ret;
+}
+
+enum handler_return platform_irq(struct iframe *frame)
+{
+#if WITH_LIB_SM
+	uint32_t ahppir = GICREG(0, GICC_AHPPIR);
+	uint32_t pending_irq = ahppir & 0x3ff;
+	struct int_handler_struct *h;
+	uint cpu = arch_curr_cpu_num();
+
+	LTRACEF("ahppir %d\n", ahppir);
+	if (pending_irq < MAX_INT && get_int_handler(pending_irq, cpu)->handler) {
+		enum handler_return ret = 0;
+		uint32_t irq;
+		uint8_t old_priority;
+		spin_lock_saved_state_t state;
+
+		spin_lock_save(&gicd_lock, &state, GICD_LOCK_FLAGS);
+
+		/* Temporarily raise the priority of the interrupt we want to
+		 * handle so another interrupt does not take its place before
+		 * we can acknowledge it.
+		 */
+		old_priority = arm_gic_get_priority(pending_irq);
+		arm_gic_set_priority_locked(pending_irq, 0);
+		DSB;
+		irq = GICREG(0, GICC_AIAR) & 0x3ff;
+		arm_gic_set_priority_locked(pending_irq, old_priority);
+
+		spin_unlock_restore(&gicd_lock, state, GICD_LOCK_FLAGS);
+
+		LTRACEF("irq %d\n", irq);
+		if (irq < MAX_INT && (h = get_int_handler(pending_irq, cpu))->handler)
+			ret = h->handler(h->arg);
+		else
+			TRACEF("unexpected irq %d != %d may get lost\n", irq, pending_irq);
+		GICREG(0, GICC_AEOIR) = irq;
+		return ret;
+	}
+	return sm_handle_irq();
+#else
+	return __platform_irq(frame);
+#endif
+}
+
+void platform_fiq(struct iframe *frame)
+{
+#if WITH_LIB_SM
+	sm_handle_fiq();
+#else
+	PANIC_UNIMPLEMENTED;
+#endif
+}
+
+#if WITH_LIB_SM
+static status_t arm_gic_get_next_irq_locked(u_int min_irq, bool per_cpu)
+{
+	u_int irq;
+	u_int max_irq = per_cpu ? GIC_MAX_PER_CPU_INT : MAX_INT;
+	uint cpu = arch_curr_cpu_num();
+
+	if (!per_cpu && min_irq < GIC_MAX_PER_CPU_INT)
+		min_irq = GIC_MAX_PER_CPU_INT;
+
+	for (irq = min_irq; irq < max_irq; irq++)
+		if (get_int_handler(irq, cpu)->handler)
+			return irq;
+
+	return SM_ERR_END_OF_INPUT;
+}
+
+long smc_intc_get_next_irq(smc32_args_t *args)
+{
+	status_t ret;
+	spin_lock_saved_state_t state;
+
+	spin_lock_save(&gicd_lock, &state, GICD_LOCK_FLAGS);
+
+	arm_gic_non_secure_interrupts_frozen = true;
+	ret = arm_gic_get_next_irq_locked(args->params[0], args->params[1]);
+	LTRACEF("min_irq %d, per_cpu %d, ret %d\n",
+		args->params[0], args->params[1], ret);
+
+	spin_unlock_restore(&gicd_lock, state, GICD_LOCK_FLAGS);
+
+	return ret;
+}
+
+static u_long enabled_fiq_mask[BITMAP_NUM_WORDS(MAX_INT)];
+
+static void bitmap_update_locked(u_long *bitmap, u_int bit, bool set)
+{
+	u_long mask = 1UL << BITMAP_BIT_IN_WORD(bit);
+
+	bitmap += BITMAP_WORD(bit);
+	if (set)
+		*bitmap |= mask;
+	else
+		*bitmap &= ~mask;
+}
+
+long smc_intc_request_fiq(smc32_args_t *args)
+{
+	u_int fiq = args->params[0];
+	bool enable = args->params[1];
+	spin_lock_saved_state_t state;
+
+	dprintf(SPEW, "%s: fiq %d, enable %d\n", __func__, fiq, enable);
+	spin_lock_save(&gicd_lock, &state, GICD_LOCK_FLAGS);
+
+	arm_gic_set_secure_locked(fiq, true);
+	arm_gic_set_target_locked(fiq, ~0, ~0);
+	arm_gic_set_priority_locked(fiq, 0);
+
+	gic_set_enable(fiq, enable);
+	bitmap_update_locked(enabled_fiq_mask, fiq, enable);
+
+	dprintf(SPEW, "%s: fiq %d, enable %d done\n", __func__, fiq, enable);
+
+	spin_unlock_restore(&gicd_lock, state, GICD_LOCK_FLAGS);
+
+	return NO_ERROR;
+}
+
+static u_int current_fiq[8] = { 0x3ff, 0x3ff, 0x3ff, 0x3ff, 0x3ff, 0x3ff, 0x3ff, 0x3ff };
+
+static bool update_fiq_targets(u_int cpu, bool enable, u_int triggered_fiq, bool resume_gicd)
+{
+	u_int i, j;
+	u_long mask;
+	u_int fiq;
+	bool smp = arm_gic_max_cpu() > 0;
+	bool ret = false;
+
+	spin_lock(&gicd_lock); /* IRQs and FIQs are already masked */
+	for (i = 0; i < BITMAP_NUM_WORDS(MAX_INT); i++) {
+		mask = enabled_fiq_mask[i];
+		while (mask) {
+			j = _ffz(~mask);
+			mask &= ~(1UL << j);
+			fiq = i * BITMAP_BITS_PER_WORD + j;
+			if (fiq == triggered_fiq)
+				ret = true;
+			LTRACEF("cpu %d, irq %i, enable %d\n", cpu, fiq, enable);
+			if (smp)
+				arm_gic_set_target_locked(fiq, 1U << cpu, enable ? ~0 : 0);
+			if (!smp || resume_gicd)
+				gic_set_enable(fiq, enable);
+		}
+	}
+	spin_unlock(&gicd_lock);
+	return ret;
+}
+
+static void suspend_resume_fiq(bool resume_gicc, bool resume_gicd)
+{
+	u_int cpu = arch_curr_cpu_num();
+
+	ASSERT(cpu < 8);
+
+	update_fiq_targets(cpu, resume_gicc, ~0, resume_gicd);
+}
+
+status_t sm_intc_fiq_enter(void)
+{
+	u_int cpu = arch_curr_cpu_num();
+	u_int irq = GICREG(0, GICC_IAR) & 0x3ff;
+	bool fiq_enabled;
+
+	ASSERT(cpu < 8);
+
+	LTRACEF("cpu %d, irq %i\n", cpu, irq);
+
+	if (irq >= 1020) {
+		LTRACEF("spurious fiq: cpu %d, old %d, new %d\n", cpu, current_fiq[cpu], irq);
+		return ERR_NO_MSG;
+	}
+
+	fiq_enabled = update_fiq_targets(cpu, false, irq, false);
+	GICREG(0, GICC_EOIR) = irq;
+
+	if (current_fiq[cpu] != 0x3ff) {
+		dprintf(INFO, "more than one fiq active: cpu %d, old %d, new %d\n", cpu, current_fiq[cpu], irq);
+		return ERR_ALREADY_STARTED;
+	}
+
+	if (!fiq_enabled) {
+		dprintf(INFO, "got disabled fiq: cpu %d, new %d\n", cpu, irq);
+		return ERR_NOT_READY;
+	}
+
+	current_fiq[cpu] = irq;
+
+	return 0;
+}
+
+void sm_intc_fiq_exit(void)
+{
+	u_int cpu = arch_curr_cpu_num();
+
+	ASSERT(cpu < 8);
+
+	LTRACEF("cpu %d, irq %i\n", cpu, current_fiq[cpu]);
+	if (current_fiq[cpu] == 0x3ff) {
+		dprintf(INFO, "%s: no fiq active, cpu %d\n", __func__, cpu);
+		return;
+	}
+	update_fiq_targets(cpu, true, current_fiq[cpu], false);
+	current_fiq[cpu] = 0x3ff;
+}
+#endif
+
+/* vim: set ts=4 sw=4 noexpandtab: */
diff --git a/src/bsp/lk/dev/interrupt/arm_gic/include/dev/interrupt/arm_gic.h b/src/bsp/lk/dev/interrupt/arm_gic/include/dev/interrupt/arm_gic.h
new file mode 100644
index 0000000..ee0fe61
--- /dev/null
+++ b/src/bsp/lk/dev/interrupt/arm_gic/include/dev/interrupt/arm_gic.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2013, 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.
+ */
+#ifndef __DEV_INTERRUPT_ARM_GIC_H
+#define __DEV_INTERRUPT_ARM_GIC_H
+
+#include <sys/types.h>
+
+void arm_gic_init(void);
+
+enum {
+	/* Ignore cpu_mask and forward interrupt to all CPUs other than the current cpu */
+	ARM_GIC_SGI_FLAG_TARGET_FILTER_NOT_SENDER = 0x1,
+	/* Ignore cpu_mask and forward interrupt to current CPU only */
+	ARM_GIC_SGI_FLAG_TARGET_FILTER_SENDER = 0x2,
+	ARM_GIC_SGI_FLAG_TARGET_FILTER_MASK = 0x3,
+
+	/* Only forward the interrupt to CPUs that has the interrupt configured as group 1 (non-secure) */
+	ARM_GIC_SGI_FLAG_NS = 0x4,
+};
+status_t arm_gic_sgi(u_int irq, u_int flags, u_int cpu_mask);
+
+#endif
+
diff --git a/src/bsp/lk/dev/interrupt/arm_gic/rules.mk b/src/bsp/lk/dev/interrupt/arm_gic/rules.mk
new file mode 100644
index 0000000..d77ca24
--- /dev/null
+++ b/src/bsp/lk/dev/interrupt/arm_gic/rules.mk
@@ -0,0 +1,8 @@
+LOCAL_DIR := $(GET_LOCAL_DIR)
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_SRCS += \
+	$(LOCAL_DIR)/arm_gic.c
+
+include make/module.mk
diff --git a/src/bsp/lk/dev/interrupt/arm_gic_v3/arm_gic.c b/src/bsp/lk/dev/interrupt/arm_gic_v3/arm_gic.c
new file mode 100644
index 0000000..7e95771
--- /dev/null
+++ b/src/bsp/lk/dev/interrupt/arm_gic_v3/arm_gic.c
@@ -0,0 +1,1115 @@
+/*
+ * Copyright (c) 2012-2015 Travis Geiselbrecht
+ *
+ * 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 <err.h>
+#include <debug.h>
+#include <dev/interrupt/arm_gic.h>
+#include <reg.h>
+#include <kernel/thread.h>
+#include <kernel/debug.h>
+#include <lk/init.h>
+#include <platform/interrupts.h>
+#include <platform/mt_irq.h>
+#include <platform/mt_reg_base.h>
+#include <arch/ops.h>
+#include <platform/gic.h>
+#include <trace.h>
+
+#if WITH_LIB_SM
+#include <lib/sm.h>
+#include <lib/sm/sm_err.h>
+#endif
+
+#define LOCAL_TRACE 0
+
+#if ARCH_ARM
+#include <arch/arm.h>
+#define iframe arm_iframe
+#define IFRAME_PC(frame) ((frame)->pc)
+#endif
+#if ARCH_ARM64
+#include <arch/arm64.h>
+#define iframe arm64_iframe_short
+#define IFRAME_PC(frame) ((frame)->elr)
+#endif
+
+/* helpers for later ICC encode macros
+ * Indirect stringification.  Doing two levels allows the parameter to be a
+ * macro itself.  For example, compile with -DFOO=bar, __stringify(FOO)
+ * converts to "bar".
+ */
+#define __stringify_1(x)     #x
+#define __stringify(x)       __stringify_1(x)
+
+#define GIC_READ(a) readl(a)
+#define GIC_WRITE(a, v) writel(v, a)
+
+static status_t arm_gic_set_secure_locked(u_int irq, bool secure);
+static spin_lock_t gicd_lock;
+
+#if WITH_LIB_SM
+#define GICD_LOCK_FLAGS SPIN_LOCK_FLAG_IRQ_FIQ
+#else
+#define GICD_LOCK_FLAGS SPIN_LOCK_FLAG_INTERRUPTS
+#endif
+
+#define GIC_MAX_PER_CPU_INT 32
+
+#define DIV_ROUND_UP(n,d) (((n) + (d) - 1) / (d))
+#define GIC_REG_COUNT(bit_per_reg) DIV_ROUND_UP(MAX_INT, (bit_per_reg))
+#define DEFINE_GIC_SHADOW_REG(name, bit_per_reg, init_val, init_from) \
+    uint32_t (name)[GIC_REG_COUNT(bit_per_reg)] = { \
+        [(init_from / bit_per_reg) ... \
+         (GIC_REG_COUNT(bit_per_reg) - 1)] = (init_val) \
+    }
+
+__asm__ (
+    "       .irp    num,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30\n"
+    "       .equ    __reg_num_x\\num, \\num\n"
+    "       .endr\n"
+    "       .equ    __reg_num_xzr, 31\n"
+    "\n"
+    "       .macro  mrs_s, rt, sreg\n"
+    "       .inst   0xd5300000|(\\sreg)|(__reg_num_\\rt)\n"
+    "       .endm\n"
+    "\n"
+    "       .macro  msr_s, sreg, rt\n"
+    "       .inst   0xd5100000|(\\sreg)|(__reg_num_\\rt)\n"
+    "       .endm\n"
+);
+
+/* since gcc not support most ARMv8 ICC sysreg in asm,
+ * we learn Linux's way to encode them */
+#define sys_reg(op0, op1, crn, crm, op2) \
+        ((((op0)-2)<<19)|((op1)<<16)|((crn)<<12)|((crm)<<8)|((op2)<<5))
+
+#if WITH_LIB_SM
+static bool arm_gic_non_secure_interrupts_frozen;
+
+static bool arm_gic_interrupt_change_allowed(int irq)
+{
+    if (!arm_gic_non_secure_interrupts_frozen)
+        return true;
+
+    TRACEF("change to interrupt %d ignored after booting ns\n", irq);
+    return false;
+}
+
+static void suspend_resume_fiq(bool resume_gicc, bool resume_gicd);
+#else
+static bool arm_gic_interrupt_change_allowed(int irq)
+{
+    return true;
+}
+
+static void suspend_resume_fiq(bool resume_gicc, bool resume_gicd)
+{
+}
+#endif
+
+#define _DEFINE_SYSREG_READ_FUNC(_name, _reg_name)       \
+static inline uint64_t read_ ## _name(void)              \
+{                                                        \
+    uint64_t v;                                          \
+    __asm__ volatile ("mrs %0, " #_reg_name : "=r" (v)); \
+    return v;                                            \
+}
+
+#define _DEFINE_SYSREG_WRITE_FUNC(_name, _reg_name)          \
+static inline void write_ ## _name(uint64_t v)               \
+{                                                            \
+    __asm__ volatile ("msr " #_reg_name ", %0" : : "r" (v)); \
+}
+
+#define DEFINE_SYSOP_FUNC(_op) \
+static inline void _op(void)   \
+{                              \
+    __asm__ (#_op);            \
+}
+
+/* Define read & write function for system register */
+#define DEFINE_SYSREG_RW_FUNCS(_name)       \
+    _DEFINE_SYSREG_READ_FUNC(_name, _name)  \
+    _DEFINE_SYSREG_WRITE_FUNC(_name, _name)
+
+DEFINE_SYSREG_RW_FUNCS(scr_el3)
+
+/* Define read & write function for renamed system register */
+#define DEFINE_RENAME_SYSREG_RW_FUNCS(_name, _reg_name) \
+    _DEFINE_SYSREG_READ_FUNC(_name, _reg_name)          \
+    _DEFINE_SYSREG_WRITE_FUNC(_name, _reg_name)
+
+DEFINE_RENAME_SYSREG_RW_FUNCS(icc_sre_el3, S3_6_C12_C12_5)
+DEFINE_RENAME_SYSREG_RW_FUNCS(icc_sre_el2, S3_4_C12_C9_5)
+DEFINE_RENAME_SYSREG_RW_FUNCS(icc_sre_el1, S3_0_C12_C12_5)
+DEFINE_RENAME_SYSREG_RW_FUNCS(icc_pmr_el1, S3_0_C4_C6_0)
+DEFINE_RENAME_SYSREG_RW_FUNCS(icc_grpen1_el3, S3_6_C12_C12_7)
+DEFINE_RENAME_SYSREG_RW_FUNCS(icc_grpen1_el1, S3_0_C12_C12_7)
+
+DEFINE_SYSOP_FUNC(isb)
+
+/*******************************************************************************
+ * This function does some minimal GICv3 configuration. The Firmware itself does
+ * not fully support GICv3 at this time and relies on GICv2 emulation as
+ * provided by GICv3. This function allows software (like Linux) in later stages
+ * to use full GICv3 features.
+ ******************************************************************************/
+static void gicv3_cpuif_setup(void)
+{
+    /* set all SGI/PPI as non-secure GROUP1 by default.
+       rdist_base + 64K == SGI_base */
+    GIC_WRITE(GIC_REDIS_BASE+SZ_64K+GICE_V3_IGROUP0, 0xffffffff);
+    GIC_WRITE(GIC_REDIS_BASE+SZ_64K+GICE_V3_IGRPMOD0, 0x0);
+}
+
+static void mt_git_dist_rwp(void)
+{
+    /*
+     * check GICD_CTLR.RWP for done check
+     */
+    while (GIC_READ(GIC_DIST_BASE + GICD_CTLR) & GICD_CTLR_RWP) {
+
+    }
+}
+
+struct int_handler_struct {
+    int_handler handler;
+    void *arg;
+};
+
+static struct int_handler_struct int_handler_table_per_cpu[GIC_MAX_PER_CPU_INT][SMP_MAX_CPUS];
+static struct int_handler_struct int_handler_table_shared[MAX_INT-GIC_MAX_PER_CPU_INT];
+
+static struct int_handler_struct *get_int_handler(unsigned int vector, uint cpu)
+{
+    if (vector < GIC_MAX_PER_CPU_INT)
+        return &int_handler_table_per_cpu[vector][cpu];
+    else
+        return &int_handler_table_shared[vector - GIC_MAX_PER_CPU_INT];
+}
+
+void register_int_handler(unsigned int vector, int_handler handler, void *arg)
+{
+    struct int_handler_struct *h;
+    uint cpu = arch_curr_cpu_num();
+
+    spin_lock_saved_state_t state;
+
+    if (vector >= MAX_INT)
+        panic("register_int_handler: vector out of range %d\n", vector);
+
+    spin_lock_save(&gicd_lock, &state, GICD_LOCK_FLAGS);
+
+    if (arm_gic_interrupt_change_allowed(vector)) {
+        h = get_int_handler(vector, cpu);
+        h->handler = handler;
+        h->arg = arg;
+    }
+
+    spin_unlock_restore(&gicd_lock, state, GICD_LOCK_FLAGS);
+}
+
+#if WITH_LIB_SM
+static DEFINE_GIC_SHADOW_REG(gicd_igroupr, 32, ~0U, 0);
+#endif
+static DEFINE_GIC_SHADOW_REG(gicd_itargetsr, 4, 0x01010101, 32);
+
+static void gic_set_enable(uint vector, bool enable)
+{
+    int reg = vector / 32;
+    uint32_t mask = 1ULL << (vector % 32);
+
+    if (vector < 32) {
+        if (enable)
+            GIC_WRITE(GIC_REDIS_BASE + SZ_64K + GICD_ISENABLER, mask);
+        else
+            GIC_WRITE(GIC_REDIS_BASE + SZ_64K + GICD_ICENABLER, mask);
+
+    } else {
+        if (enable)
+            GIC_WRITE(GIC_DIST_BASE + GICD_ISENABLER + reg * 4, mask);
+        else
+            GIC_WRITE(GIC_DIST_BASE + GICD_ICENABLER + reg * 4, mask);
+    }
+}
+
+static void arm_gic_init_percpu(uint level)
+{
+#if WITH_LIB_SM
+    GIC_WRITE(GIC_REDIS_BASE + GICC_CTLR, 0xb); // enable GIC0 and select fiq mode for secure
+    GIC_WRITE(GIC_DIST_BASE + GICD_IGROUPR, ~0U); /* GICD_IGROUPR0 is banked */
+#else
+    GIC_WRITE(GIC_REDIS_BASE + GICC_CTLR, 1); // enable GIC0
+#endif
+    GIC_WRITE(GIC_REDIS_BASE + GICC_PMR, 0xFF);  // unmask interrupts at all priority levels
+}
+
+LK_INIT_HOOK_FLAGS(arm_gic_init_percpu,
+                   arm_gic_init_percpu,
+                   LK_INIT_LEVEL_PLATFORM_EARLY, LK_INIT_FLAG_SECONDARY_CPUS);
+
+static void arm_gic_suspend_cpu(uint level)
+{
+    suspend_resume_fiq(false, false);
+}
+
+LK_INIT_HOOK_FLAGS(arm_gic_suspend_cpu, arm_gic_suspend_cpu,
+                   LK_INIT_LEVEL_PLATFORM, LK_INIT_FLAG_CPU_SUSPEND);
+
+static void arm_gic_resume_cpu(uint level)
+{
+    spin_lock_saved_state_t state;
+    bool resume_gicd = false;
+
+    spin_lock_save(&gicd_lock, &state, GICD_LOCK_FLAGS);
+    if (!(GIC_READ(GIC_DIST_BASE + GICD_CTLR) & 1)) {
+        dprintf(SPEW, "%s: distibutor is off, calling arm_gic_init instead\n", __func__);
+        arm_gic_init();
+        resume_gicd = true;
+    } else {
+        arm_gic_init_percpu(0);
+    }
+    spin_unlock_restore(&gicd_lock, state, GICD_LOCK_FLAGS);
+    suspend_resume_fiq(true, resume_gicd);
+}
+
+LK_INIT_HOOK_FLAGS(arm_gic_resume_cpu, arm_gic_resume_cpu,
+                   LK_INIT_LEVEL_PLATFORM, LK_INIT_FLAG_CPU_RESUME);
+
+static int arm_gic_max_cpu(void)
+{
+    return (GIC_READ(GIC_DIST_BASE + GICD_TYPER) >> 5) & 0x7;
+}
+
+/*******************************************************************************
+ * Enable secure interrupts and use FIQs to route them. Disable legacy bypass
+ * and set the priority mask register to allow all interrupts to trickle in.
+ ******************************************************************************/
+void arm_gic_redist_init(void)
+{
+    /* Set all SGI/PPI as secure GROUP1.
+       rdist_base + 64K == SGI_base */
+    GIC_WRITE(GIC_REDIS_BASE + SZ_64K + GICE_V3_IGROUP0, 0x0);
+    GIC_WRITE(GIC_REDIS_BASE + SZ_64K + GICE_V3_IGRPMOD0, 0xffffffff);
+}
+
+static void arm_gic_distif_init(void)
+{
+    unsigned int i, ctrl, irq_set;
+
+    /* Disable the distributor before going further */
+    ctrl = GIC_READ(GIC_DIST_BASE + GICD_CTLR);
+    ctrl &= ~(GICD_CTLR_ENABLE_GRP0 | GICD_CTLR_ENGRP1NS | GICD_CTLR_ENGRP1S);
+    GIC_WRITE(GIC_DIST_BASE + GICD_CTLR, ctrl);
+
+    mt_git_dist_rwp();
+
+    /*
+     * Mark out non-secure SPI interrupts. The number of interrupts is
+     * calculated as 32 * (IT_LINES + 1). We do 32 at a time.
+     */
+    irq_set = (GIC_READ(GIC_DIST_BASE + GICD_TYPER)&IT_LINES_NO_MASK) + 1;
+    irq_set = irq_set * 32;
+
+    /* Set all SPI as secure group1 */
+    for (i = 32; i < irq_set; i += 32) {
+        GIC_WRITE(GIC_DIST_BASE + GICD_IGROUPR + i * 4 / 32, 0x0);
+        GIC_WRITE(GIC_DIST_BASE + GICD_IGRPMODR + i * 4 / 32, 0xffffffff);
+    }
+
+    /*
+     * Set all global interrupts to be level triggered, active low.
+     */
+    for (i = 32; i < irq_set; i += 16) {
+        GIC_WRITE(GIC_DIST_BASE + GICD_ICFGR + i * 4 / 16, 0);
+    }
+
+    /*
+     * Set all global interrupts to this CPU only.
+     */
+    if (arm_gic_max_cpu() > 0) {
+        /* Set external interrupts to target cpu 0 */
+        for (i = 32; i < irq_set; i += 4) {
+            GIC_READ(GIC_DIST_BASE + GICD_ITARGETSR + (i / 4) * 4) = gicd_itargetsr[i / 4];
+        }
+    }
+
+    /*
+     * Set priority on all interrupts.
+     */
+    for (i = 0; i < irq_set; i += 4) {
+        GIC_WRITE(GIC_DIST_BASE + GICD_IPRIORITYR + i * 4 / 4, 0xA0A0A0A0);
+    }
+
+    /*
+     * Disable all interrupts.
+     */
+    for (i = 0; i < irq_set; i += 32) {
+        GIC_WRITE(GIC_DIST_BASE + GICD_ICENABLER + i * 4 / 32, 0xFFFFFFFF);
+    }
+
+    /*
+     * Clear all active status
+     */
+    for (i = 0; i < irq_set; i += 32) {
+        GIC_WRITE(GIC_DIST_BASE + GICD_ICACTIVER + i * 4 / 32, 0xFFFFFFFF);
+    }
+
+    /*
+     * Clear all pending status
+     */
+    for (i = 0; i < irq_set; i += 32) {
+        GIC_WRITE(GIC_DIST_BASE + GICD_ICPENDR + i * 4 / 32, 0xFFFFFFFF);
+    }
+
+    /* Enable all groups & ARE */
+    ctrl = GICD_CTLR_ENABLE_GRP0 | GICD_CTLR_ENGRP1NS | GICD_CTLR_ENGRP1S |
+           GICD_CTLR_ARE | GICD_CTLR_ARE_NS;
+    GIC_WRITE(GIC_DIST_BASE + GICD_CTLR, ctrl);
+
+    mt_git_dist_rwp();
+}
+
+void clear_sec_pol_ctl_en(void)
+{
+    unsigned int i;
+
+    /* total 19 polarity ctrl registers */
+    for (i = 0; i <= NR_INT_POL_CTL-1; i++) {
+        GIC_WRITE((SEC_POL_CTL_EN0 + (i * 4)), 0);
+    }
+}
+
+#if ARCH_ARM64
+void gic_setup(void)
+{
+    uint64_t val, scr_val;
+
+#if GICV3_SUPPORT_GIC600
+    /* Power on redisdtibuter */
+    gicv3_rdistif_on();
+#endif
+
+    /* GIC V3 redistributor initialization (all CPUs) */
+    val = GIC_READ(GIC_REDIS_BASE_PHY + GICR_V3_WAKER);
+    val &= ~GICR_V3_WAKER_ProcessorSleep;
+    GIC_WRITE(GIC_REDIS_BASE_PHY + GICR_V3_WAKER, val);
+    while ((GIC_READ(GIC_REDIS_BASE_PHY + GICR_V3_WAKER) & GICR_V3_WAKER_ChildrenAsleep));
+
+    /*
+     * We need to set SCR_EL3.NS in order to see GICv3 non-secure state.
+     * Restore SCR_EL3.NS again before exit.
+     */
+    scr_val = read_scr_el3();
+    write_scr_el3(scr_val | SCR_NS_BIT);
+    isb(); /* ensure NS=1 takes effect before accessing ICC_SRE_EL2 */
+
+    /* GIC V3 CPU interface initialization (all CPUs) */
+    val = read_icc_sre_el3();
+    write_icc_sre_el3(val | ICC_SRE_EN_BIT | ICC_SRE_SRE_BIT);
+    isb(); /* before enable lower SRE, be sure SRE in el3 takes effect */
+
+    write_icc_grpen1_el3(0x1LL);
+
+    val = read_icc_sre_el2();
+    write_icc_sre_el2(val | ICC_SRE_EN_BIT | ICC_SRE_SRE_BIT);
+    isb(); /* before enable lower SRE, be sure SRE in el2 takes effect */
+
+    write_icc_pmr_el1(GIC_PRI_MASK);
+    isb(); /* commite ICC_* changes before setting NS=0 */
+
+    /* Restore SCR_EL3 */
+    write_scr_el3(scr_val);
+    isb(); /* ensure NS=0 takes effect immediately */
+
+    write_icc_grpen1_el1(0x1LL);
+
+    /* MUST set secure copy of icc_sre_el1 as SRE_SRE to enable FIQ,
+     * see GICv3 spec 4.6.4 FIQ Enable
+     */
+    val = read_icc_sre_el1();
+    write_icc_sre_el1(val | ICC_SRE_SRE_BIT);
+    isb(); /* before we can touch other ICC_* system registers, make sure this have effect */
+}
+#endif
+
+#if ARCH_ARM
+static void gic_dist_wait_for_rwp(void)
+{
+    while ((*(volatile unsigned int *)(GIC_DIST_BASE + GIC_DIST_CTRL)) & GICD_CTLR_RWP)
+        isb();
+}
+
+static void gic_redist_wait_for_rwp(void)
+{
+    while ((*(volatile unsigned int *)(GIC_REDIS_BASE + GICR_V3_CTLR)) & GICR_V3_CTLR_RWP)
+        isb();
+}
+
+static void gic_enable_sre(void)
+{
+    uint32_t val;
+
+    /* for suspend */
+    /* Enable ICC_MSRE */
+    asm volatile("cps #22");
+    asm volatile("mrc p15, 6, %0, c12, c12, 5" : "=r" (val));
+    val |= (ICC_SRE_SRE_BIT | ICC_SRE_EN_BIT);
+    asm volatile("mcr p15, 6, %0, c12, c12, 5" : : "r" (val));
+    asm volatile("cps #19");
+    isb();
+
+    asm volatile("MRC p15, 0, %0, c12, c12, 5" : "=r" (val));
+    val |= ICC_SRE_SRE_BIT;
+    asm volatile("MCR p15, 0, %0, c12, c12, 5" : : "r" (val));
+    isb();
+}
+
+/* Our default, arbitrary priority value. Linux only uses one anyway. */
+#define DEFAULT_PMR_VALUE   0xf0
+
+/* Low level accessors */
+static void gic_write_pmr(uint32_t val)
+{
+    asm volatile("MCR p15, 0, %0, c4, c6, 0" : : "r" (val));
+}
+
+#define ICC_CTLR_EL1_EOImode_drop_dir   (0U << 1)
+static void gic_write_ctlr(uint32_t val)
+{
+    asm volatile("MCR p15, 0, %0, c12, c12, 4" : : "r" (val));
+    isb();
+}
+
+static void gic_write_grpen1(uint32_t val)
+{
+    asm volatile("MCR p15, 0, %0, c12, c12, 7" : : "r" (val));
+    asm volatile("MCR p15, 0, %0, c12, c12, 6" : : "r" (val));
+    isb();
+}
+
+void gic_setup(void)
+{
+    uint32_t gic_irqs, i, val;
+    uint32_t mpidr, affinity;
+
+    /*
+     * Find out how many interrupts are supported.
+     * The GIC only supports up to 1020 interrupt sources (SGI+PPI+SPI)
+     */
+    gic_irqs = *(volatile unsigned int *)(GIC_DIST_BASE + GICD_TYPER) & 0x1f;
+    gic_irqs = (gic_irqs + 1) * 32;
+    if (gic_irqs > 1020)
+        gic_irqs = 1020;
+
+    /* Disable the distributor */
+    GIC_WRITE(GIC_DIST_BASE + GICD_CTLR, 0);
+    gic_dist_wait_for_rwp();
+
+    /*
+    * Set all global interrupts to be level triggered, active low.
+    */
+    for (i = 32; i < gic_irqs; i += 16)
+        GIC_WRITE(GIC_DIST_BASE + GIC_DIST_CONFIG + i / 4, 0);
+
+    /*
+    * Set priority on all global interrupts.
+    */
+    for (i = 32; i < gic_irqs; i += 4)
+        GIC_WRITE(GIC_DIST_BASE + GIC_DIST_PRI + i, 0xa0a0a0a0);
+
+    /*
+    * Set all interrupts to G1S.  Leave the PPI and SGIs alone
+    * as they are set by redistributor registers.
+    */
+    for (i = 32; i < gic_irqs; i += 32)
+        GIC_WRITE(GIC_DIST_BASE + GICD_IGRPMODR + i / 8, 0xffffffff);
+
+    /*
+    * Disable all interrupts.  Leave the PPI and SGIs alone
+    * as they are enabled by redistributor registers.
+    */
+    for (i = 32; i < gic_irqs; i += 32)
+        GIC_WRITE(GIC_DIST_BASE + GIC_DIST_ENABLE_CLEAR + i / 8, 0xffffffff);
+
+    gic_dist_wait_for_rwp();
+
+    /* FIXME */
+    /* Enable distributor with ARE, Group1 */
+    GIC_WRITE(GIC_DIST_BASE + GIC_DIST_CTRL,
+              GICD_CTLR_ARE | GICD_CTLR_ENGRP1S |
+              GICD_CTLR_ENGRP1NS | GICD_CTLR_ENABLE_GRP0);
+
+    /*
+    * Set all global interrupts to the boot CPU only. ARE must be
+    * enabled.
+    */
+    mpidr = 0x0;
+    affinity = mpidr;
+    /*
+    affinity = (MPIDR_AFFINITY_LEVEL(mpidr, 3) << 32 |
+            MPIDR_AFFINITY_LEVEL(mpidr, 2) << 16 |
+            MPIDR_AFFINITY_LEVEL(mpidr, 1) << 8  |
+            MPIDR_AFFINITY_LEVEL(mpidr, 0));
+    */
+    for (i = 32; i < gic_irqs; i++)
+        GIC_WRITE(GIC_DIST_BASE + GICD_IROUTER + i * 8, affinity);
+
+#if GICV3_SUPPORT_GIC600
+    /* Power on redisdtibuter */
+    gicv3_rdistif_on();
+#endif
+
+    /* Wake up this CPU redistributor */
+    val = GIC_READ(GIC_REDIS_BASE + GICR_V3_WAKER);
+    val &= ~GICR_WAKER_ProcessorSleep;
+    GIC_WRITE(GIC_REDIS_BASE + GICR_V3_WAKER, val);
+
+    while ((GIC_READ(GIC_REDIS_BASE + GICR_V3_WAKER)) &
+            GICR_WAKER_ChildrenAsleep)
+        ;
+
+    /*
+    * Deal with the banked PPI and SGI interrupts - disable all
+    * PPI interrupts, ensure all SGI interrupts are enabled.
+    */
+    GIC_WRITE(GIC_REDIS_BASE + SZ_64K + GIC_DIST_ENABLE_CLEAR, 0xffff0000);
+    GIC_WRITE(GIC_REDIS_BASE + SZ_64K + GIC_DIST_ENABLE_SET, 0x0000ffff);
+
+    /*
+    * Set priority on PPI and SGI interrupts
+    */
+    for (i = 0; i < 32; i += 4)
+        GIC_WRITE(GIC_REDIS_BASE + SZ_64K + GIC_DIST_PRI + i, 0xa0a0a0a0);
+
+    gic_redist_wait_for_rwp();
+
+    /* Enable system registers */
+    gic_enable_sre();
+
+    /* Set priority mask register */
+    gic_write_pmr(DEFAULT_PMR_VALUE);
+
+    /* EOI deactivates interrupt too (mode 0) */
+    gic_write_ctlr(ICC_CTLR_EL1_EOImode_drop_dir);
+
+    /* ... and let's hit the road... */
+    gic_write_grpen1(1);
+
+    isb();
+}
+#endif
+
+void arm_gic_init(void)
+{
+#if ARCH_ARM
+    /* for ARCH_ARM64, gic_setup will be called in platform_el3_init in start.s */
+    LTRACEF("[LK GIC] before gic_setup\n");
+    gic_setup();
+#endif
+
+    LTRACEF("[LK GIC] before arm_gic_cpuif_setup\n");
+    arm_gic_redist_init();
+
+    LTRACEF("[LK GIC] before arm_gic_distif_init\n");
+    arm_gic_distif_init();
+
+#if WITH_LIB_SM
+    GIC_WRITE(GIC_DIST_BASE + GICD_CTLR, 3); // enable GIC0 ns interrupts
+    /*
+     * Iterate through all IRQs and set them to non-secure
+     * mode. This will allow the non-secure side to handle
+     * all the interrupts we don't explicitly claim.
+     */
+    for (i = 32; i < MAX_INT; i += 32) {
+        u_int reg = i / 32;
+        GIC_WRITE(GIC_DIST_BASE + GICD_IGROUPR + reg * 4, gicd_igroupr[reg]);
+    }
+#endif
+    arm_gic_init_percpu(0);
+
+    LTRACEF("[LK GIC] before clear_sec_pol_ctl_en\n");
+    clear_sec_pol_ctl_en();
+
+}
+
+static status_t arm_gic_set_secure_locked(u_int irq, bool secure)
+{
+#if WITH_LIB_SM
+    int reg = irq / 32;
+    uint32_t mask = 1ULL << (irq % 32);
+
+    if (irq >= MAX_INT)
+        return ERR_INVALID_ARGS;
+
+    if (secure)
+        GIC_WEITE(GIC_DIST_BASE + GICD_IGROUPR + reg * 4, (gicd_igroupr[reg] &= ~mask));
+    else
+        GIC_WRITE(GIC_DIST_BASE + GICD_IGROUPR + reg * 4, ((gicd_igroupr[reg] |= mask));
+    LTRACEF("irq %d, secure %d, GICD_IGROUP%d = %x\n",
+            irq, secure, reg, GIC_READ(GIC_DIST_BASE + GICD_IGROUPR + reg * 4);
+#endif
+    return NO_ERROR;
+}
+
+static status_t arm_gic_set_target_locked(u_int irq, u_int cpu_mask, u_int enable_mask)
+{
+    u_int reg = irq / 4;
+    u_int shift = 8 * (irq % 4);
+    u_int old_val;
+    u_int new_val;
+
+    cpu_mask = (cpu_mask & 0xff) << shift;
+    enable_mask = (enable_mask << shift) & cpu_mask;
+
+    old_val = GIC_READ(GIC_DIST_BASE + GICD_ITARGETSR + reg * 4);
+    new_val = (gicd_itargetsr[reg] & ~cpu_mask) | enable_mask;
+    GIC_WRITE(GIC_DIST_BASE + GICD_ITARGETSR + reg * 4,  new_val);
+    gicd_itargetsr[reg] = new_val;
+    LTRACEF("irq %i, GICD_ITARGETSR%d %x => %x (got %x)\n",
+            irq, reg, old_val, new_val, GIC_READ(GIC_DIST_BASE + GICD_ITARGETSR + reg));
+
+    return NO_ERROR;
+}
+
+static status_t arm_gic_get_priority(u_int irq)
+{
+    u_int reg = irq / 4;
+    u_int shift = 8 * (irq % 4);
+    return (GIC_READ(GIC_DIST_BASE + GICD_IPRIORITYR + reg * 4) >> shift) & 0xff;
+}
+
+static status_t arm_gic_set_priority_locked(u_int irq, uint8_t priority)
+{
+    u_int reg = irq / 4;
+    u_int shift = 8 * (irq % 4);
+    u_int mask = 0xff << shift;
+    uint32_t regval;
+
+    regval = GIC_READ(GIC_DIST_BASE + GICD_IPRIORITYR + reg);
+    LTRACEF("irq %i, old GICD_IPRIORITYR%d = %x\n", irq, reg, regval);
+    regval = (regval & ~mask) | ((uint32_t)priority << shift);
+    GIC_READ(GIC_DIST_BASE + GICD_IPRIORITYR + reg * 4) = regval;
+    LTRACEF("irq %i, new GICD_IPRIORITYR%d = %x, req %x\n",
+            irq, reg, GIC_READ(GIC_DIST_BASE + GICD_IPRIORITYR + reg * 4), regval);
+
+    return 0;
+}
+
+status_t arm_gic_sgi(u_int irq, u_int flags, u_int cpu_mask)
+{
+    u_int val =
+        ((flags & ARM_GIC_SGI_FLAG_TARGET_FILTER_MASK) << 24) |
+        ((cpu_mask & 0xff) << 16) |
+        ((flags & ARM_GIC_SGI_FLAG_NS) ? (1U << 15) : 0) |
+        (irq & 0xf);
+
+    if (irq >= 16)
+        return ERR_INVALID_ARGS;
+
+    LTRACEF("GICD_SGIR: %x\n", val);
+
+    GIC_WRITE(GIC_DIST_BASE + GICD_SGIR, val);
+
+    return NO_ERROR;
+}
+
+status_t mask_interrupt(unsigned int vector)
+{
+    if (vector >= MAX_INT)
+        return ERR_INVALID_ARGS;
+
+    if (arm_gic_interrupt_change_allowed(vector))
+        gic_set_enable(vector, false);
+
+    return NO_ERROR;
+}
+
+status_t unmask_interrupt(unsigned int vector)
+{
+    if (vector >= MAX_INT)
+        return ERR_INVALID_ARGS;
+
+    if (arm_gic_interrupt_change_allowed(vector))
+        gic_set_enable(vector, true);
+
+    return NO_ERROR;
+}
+
+#if ARCH_ARM64
+static uint64_t gicc_read_hppir1_el1(void)
+{
+    uint64_t val = 0;
+    __asm__ volatile("mrs_s %0, " __stringify(ICC_HPPIR1_EL1) : "=r" (val));
+    return val;
+}
+#endif
+
+#if ARCH_ARM
+static uint32_t gicc_read_hppir1_el1(void)
+{
+    uint32_t val = 0;
+    /* aarch32 ICC_HPPIR1, 1111 000 1100 1100 010 */
+    __asm__ volatile("MRC p15, 0, %0, c12, c12, 2" : "=r" (val));
+    return val;
+}
+#endif
+
+uint32_t arm_gic_get_pending_interrupt_id(void)
+{
+    return gicc_read_hppir1_el1();
+}
+
+#if ARCH_ARM64
+uint64_t gicc_read_iar1_el1(void)
+{
+    u64 irqstat = 0;
+    __asm__ volatile("mrs_s %0, " __stringify(ICC_IAR1_EL1) : "=r" (irqstat));
+
+    return irqstat;
+}
+#endif
+
+#if ARCH_ARM
+uint32_t gicc_read_iar1_el1(void)
+{
+    uint32_t val = 0;
+    /* aarch32 ICC_IAR1, 1111 000 1100 1100 000 */
+    __asm__ volatile("MRC p15, 0, %0, c12, c12, 0" : "=r" (val));
+    return val;
+}
+#endif
+
+
+uint64_t arm_gic_acknowledge_irq(void)
+{
+    return gicc_read_iar1_el1();
+}
+
+static void gicc_write_eoi1_el1(uint32_t irq)
+{
+#if ARCH_ARM64
+    __asm__ volatile("msr_s " __stringify(ICC_EOIR1_EL1) ", %0" : : "r" (irq));
+#endif
+#if ARCH_ARM
+    /* aarch32 ICC_EOIR1, 1111 000 1100 1100 001 */
+    __asm__ volatile("MCR p15, 0, %0, c12, c12, 1" : : "r" (irq));
+#endif
+
+}
+
+static void arm_gic_end_of_irq(uint32_t id)
+{
+    gicc_write_eoi1_el1(id);
+}
+
+#if ARCH_ARM64
+uint64_t gicc_read_iar0_el1(void)
+{
+    u64 irqstat = 0;
+    __asm__ volatile("mrs_s %0, " __stringify(ICC_IAR0_EL1) : "=r" (irqstat));
+
+    return irqstat;
+}
+#endif
+
+#if ARCH_ARM
+uint32_t gicc_read_iar0_el1(void)
+{
+    uint32_t val = 0;
+    /* aarch32 ICC_IAR0, 1111 000 1100 1000 000 */
+    __asm__ volatile("MRC p15, 0, %0, c12, c8, 0" : "=r" (val));
+    return val;
+}
+#endif
+
+uint64_t arm_gic_acknowledge_fiq(void)
+{
+    return gicc_read_iar0_el1();
+}
+
+static void gicc_write_eoi0_el1(uint32_t irq)
+{
+#if ARCH_ARM64
+    __asm__ volatile("msr_s " __stringify(ICC_EOIR0_EL1) ", %0" : : "r" (irq));
+#endif
+#if ARCH_ARM
+    /* aarch32 ICC_EOIR0, 1111 000 1100 1000 001 */
+    __asm__ volatile("MCR p15, 0, %0, c12, c8, 1" : : "r" (irq));
+#endif
+}
+
+static void arm_gic_end_of_fiq(uint32_t id)
+{
+    gicc_write_eoi0_el1(id);
+}
+
+static
+enum handler_return __platform_irq(struct iframe *frame)
+{
+    // get the current vector
+    uint32_t iar = arm_gic_acknowledge_irq();
+    unsigned int vector = iar & 0x3ff;
+
+    if (vector >= 0x3fe) {
+        // spurious
+        return INT_NO_RESCHEDULE;
+    }
+
+    THREAD_STATS_INC(interrupts);
+    KEVLOG_IRQ_ENTER(vector);
+
+    uint cpu = arch_curr_cpu_num();
+
+    LTRACEF_LEVEL(2, "iar 0x%x cpu %u currthread %p vector %d pc 0x%lx\n", iar, cpu,
+                  get_current_thread(), vector, (uintptr_t)IFRAME_PC(frame));
+
+    // deliver the interrupt
+    enum handler_return ret;
+
+    ret = INT_NO_RESCHEDULE;
+    struct int_handler_struct *handler = get_int_handler(vector, cpu);
+    if (handler->handler)
+        ret = handler->handler(handler->arg);
+
+    arm_gic_end_of_irq(iar);
+
+    LTRACEF_LEVEL(2, "cpu %u exit %d\n", cpu, ret);
+
+    KEVLOG_IRQ_EXIT(vector);
+
+    return ret;
+}
+
+enum handler_return platform_irq(struct iframe *frame)
+{
+#if WITH_LIB_SM
+    uint32_t ahppir = arm_gic_get_pending_interrupt_id();
+    uint32_t pending_irq = ahppir & 0x3ff;
+    struct int_handler_struct *h;
+    uint cpu = arch_curr_cpu_num();
+
+    LTRACEF("ahppir %d\n", ahppir);
+    if (pending_irq < MAX_INT && get_int_handler(pending_irq, cpu)->handler) {
+        enum handler_return ret = 0;
+        uint32_t irq;
+        uint8_t old_priority;
+        spin_lock_saved_state_t state;
+
+        spin_lock_save(&gicd_lock, &state, GICD_LOCK_FLAGS);
+
+        /* Temporarily raise the priority of the interrupt we want to
+         * handle so another interrupt does not take its place before
+         * we can acknowledge it.
+         */
+        old_priority = arm_gic_get_priority(pending_irq);
+        arm_gic_set_priority_locked(pending_irq, 0);
+        DSB;
+        irq = arm_gic_acknowledge_irq() & 0x3ff;
+        arm_gic_set_priority_locked(pending_irq, old_priority);
+
+        spin_unlock_restore(&gicd_lock, state, GICD_LOCK_FLAGS);
+
+        LTRACEF("irq %d\n", irq);
+        if (irq < MAX_INT && (h = get_int_handler(pending_irq, cpu))->handler)
+            ret = h->handler(h->arg);
+        else
+            TRACEF("unexpected irq %d != %d may get lost\n", irq, pending_irq);
+        arm_gic_end_of_fiq(irq);
+
+        return ret;
+    }
+    return sm_handle_irq();
+#else
+    return __platform_irq(frame);
+#endif
+}
+
+void platform_fiq(struct iframe *frame)
+{
+#if WITH_LIB_SM
+    sm_handle_fiq();
+#else
+    PANIC_UNIMPLEMENTED;
+#endif
+}
+
+#if WITH_LIB_SM
+static status_t arm_gic_get_next_irq_locked(u_int min_irq, bool per_cpu)
+{
+    u_int irq;
+    u_int max_irq = per_cpu ? GIC_MAX_PER_CPU_INT : MAX_INT;
+    uint cpu = arch_curr_cpu_num();
+
+    if (!per_cpu && min_irq < GIC_MAX_PER_CPU_INT)
+        min_irq = GIC_MAX_PER_CPU_INT;
+
+    for (irq = min_irq; irq < max_irq; irq++)
+        if (get_int_handler(irq, cpu)->handler)
+            return irq;
+
+    return SM_ERR_END_OF_INPUT;
+}
+
+long smc_intc_get_next_irq(smc32_args_t *args)
+{
+    status_t ret;
+    spin_lock_saved_state_t state;
+
+    spin_lock_save(&gicd_lock, &state, GICD_LOCK_FLAGS);
+
+    arm_gic_non_secure_interrupts_frozen = true;
+    ret = arm_gic_get_next_irq_locked(args->params[0], args->params[1]);
+    LTRACEF("min_irq %d, per_cpu %d, ret %d\n",
+            args->params[0], args->params[1], ret);
+
+    spin_unlock_restore(&gicd_lock, state, GICD_LOCK_FLAGS);
+
+    return ret;
+}
+
+static u_long enabled_fiq_mask[BITMAP_NUM_WORDS(MAX_INT)];
+
+static void bitmap_update_locked(u_long *bitmap, u_int bit, bool set)
+{
+    u_long mask = 1UL << BITMAP_BIT_IN_WORD(bit);
+
+    bitmap += BITMAP_WORD(bit);
+    if (set)
+        *bitmap |= mask;
+    else
+        *bitmap &= ~mask;
+}
+
+long smc_intc_request_fiq(smc32_args_t *args)
+{
+    u_int fiq = args->params[0];
+    bool enable = args->params[1];
+    spin_lock_saved_state_t state;
+
+    dprintf(SPEW, "%s: fiq %d, enable %d\n", __func__, fiq, enable);
+    spin_lock_save(&gicd_lock, &state, GICD_LOCK_FLAGS);
+
+    arm_gic_set_secure_locked(fiq, true);
+    arm_gic_set_target_locked(fiq, ~0, ~0);
+    arm_gic_set_priority_locked(fiq, 0);
+
+    gic_set_enable(fiq, enable);
+    bitmap_update_locked(enabled_fiq_mask, fiq, enable);
+
+    dprintf(SPEW, "%s: fiq %d, enable %d done\n", __func__, fiq, enable);
+
+    spin_unlock_restore(&gicd_lock, state, GICD_LOCK_FLAGS);
+
+    return NO_ERROR;
+}
+
+static u_int current_fiq[8] = { 0x3ff, 0x3ff, 0x3ff, 0x3ff, 0x3ff, 0x3ff, 0x3ff, 0x3ff };
+
+static bool update_fiq_targets(u_int cpu, bool enable, u_int triggered_fiq, bool resume_gicd)
+{
+    u_int i, j;
+    u_long mask;
+    u_int fiq;
+    bool smp = arm_gic_max_cpu() > 0;
+    bool ret = false;
+
+    spin_lock(&gicd_lock); /* IRQs and FIQs are already masked */
+    for (i = 0; i < BITMAP_NUM_WORDS(MAX_INT); i++) {
+        mask = enabled_fiq_mask[i];
+        while (mask) {
+            j = _ffz(~mask);
+            mask &= ~(1UL << j);
+            fiq = i * BITMAP_BITS_PER_WORD + j;
+            if (fiq == triggered_fiq)
+                ret = true;
+            LTRACEF("cpu %d, irq %i, enable %d\n", cpu, fiq, enable);
+            if (smp)
+                arm_gic_set_target_locked(fiq, 1U << cpu, enable ? ~0 : 0);
+            if (!smp || resume_gicd)
+                gic_set_enable(fiq, enable);
+        }
+    }
+    spin_unlock(&gicd_lock);
+    return ret;
+}
+
+static void suspend_resume_fiq(bool resume_gicc, bool resume_gicd)
+{
+    u_int cpu = arch_curr_cpu_num();
+
+    ASSERT(cpu < 8);
+
+    update_fiq_targets(cpu, resume_gicc, ~0, resume_gicd);
+}
+
+status_t sm_intc_fiq_enter(void)
+{
+    u_int cpu = arch_curr_cpu_num();
+    u_int irq = arm_gic_acknowledge_fiq() & 0x3ff;
+    bool fiq_enabled;
+
+    ASSERT(cpu < 8);
+
+    LTRACEF("cpu %d, irq %i\n", cpu, irq);
+
+    if (irq >= 1020) {
+        LTRACEF("spurious fiq: cpu %d, old %d, new %d\n", cpu, current_fiq[cpu], irq);
+        return ERR_NO_MSG;
+    }
+
+    fiq_enabled = update_fiq_targets(cpu, false, irq, false);
+    arm_gic_end_of_fiq(irq);
+
+    if (current_fiq[cpu] != 0x3ff) {
+        dprintf(INFO, "more than one fiq active: cpu %d, old %d, new %d\n", cpu, current_fiq[cpu], irq);
+        return ERR_ALREADY_STARTED;
+    }
+
+    if (!fiq_enabled) {
+        dprintf(INFO, "got disabled fiq: cpu %d, new %d\n", cpu, irq);
+        return ERR_NOT_READY;
+    }
+
+    current_fiq[cpu] = irq;
+
+    return 0;
+}
+
+void sm_intc_fiq_exit(void)
+{
+    u_int cpu = arch_curr_cpu_num();
+
+    ASSERT(cpu < 8);
+
+    LTRACEF("cpu %d, irq %i\n", cpu, current_fiq[cpu]);
+    if (current_fiq[cpu] == 0x3ff) {
+        dprintf(INFO, "%s: no fiq active, cpu %d\n", __func__, cpu);
+        return;
+    }
+    update_fiq_targets(cpu, true, current_fiq[cpu], false);
+    current_fiq[cpu] = 0x3ff;
+}
+#endif
+
+/* vim: set ts=4 sw=4 noexpandtab: */
diff --git a/src/bsp/lk/dev/interrupt/arm_gic_v3/gic600.c b/src/bsp/lk/dev/interrupt/arm_gic_v3/gic600.c
new file mode 100644
index 0000000..7e2f2bf
--- /dev/null
+++ b/src/bsp/lk/dev/interrupt/arm_gic_v3/gic600.c
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2017-2019, ARM Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+/*
+ * Driver for GIC-600 specific features. This driver only overrides
+ * APIs that are different to those generic ones in GICv3 driver.
+ *
+ * GIC-600 supports independently power-gating redistributor interface.
+ */
+
+#include <assert.h>
+#include <dev/interrupt/arm_gic.h>
+#include <platform/mt_reg_base.h>
+#include <reg.h>
+
+/* GIC-600 specific register offsets */
+#define GICR_PWRR   0x24
+
+/* GICR_PWRR fields */
+#define PWRR_RDPD_SHIFT     0
+#define PWRR_RDAG_SHIFT     1
+#define PWRR_RDGPD_SHIFT    2
+#define PWRR_RDGPO_SHIFT    3
+
+#define PWRR_RDPD   (1 << PWRR_RDPD_SHIFT)
+#define PWRR_RDAG   (1 << PWRR_RDAG_SHIFT)
+#define PWRR_RDGPD  (1 << PWRR_RDGPD_SHIFT)
+#define PWRR_RDGPO  (1 << PWRR_RDGPO_SHIFT)
+
+/*
+ * Values to write to GICR_PWRR register to power redistributor
+ * for operating through the core (GICR_PWRR.RDAG = 0)
+ */
+#define PWRR_ON     (0 << PWRR_RDPD_SHIFT)
+#define PWRR_OFF    (1 << PWRR_RDPD_SHIFT)
+
+/* GIC-600 specific accessor functions */
+static void gicr_write_pwrr(uintptr_t base, unsigned int val)
+{
+    GIC_WRITE(base + GICR_PWRR,PWRR_ON);
+}
+
+static uint32_t gicr_read_pwrr(uintptr_t base)
+{
+    return GIC_READ(base + GICR_PWRR);
+}
+
+static void gicr_wait_group_not_in_transit(uintptr_t base)
+{
+    /* Check group not transitioning: RDGPD == RDGPO */
+    while (((gicr_read_pwrr(base) & PWRR_RDGPD) >> PWRR_RDGPD_SHIFT) !=
+            ((gicr_read_pwrr(base) & PWRR_RDGPO) >> PWRR_RDGPO_SHIFT))
+        ;
+}
+
+static void gic600_pwr_on(uintptr_t base)
+{
+    do {    /* Wait until group not transitioning */
+        gicr_wait_group_not_in_transit(base);
+
+        /* Power on redistributor */
+        gicr_write_pwrr(base, PWRR_ON);
+
+        /*
+         * Wait until the power on state is reflected.
+         * If RDPD == 0 then powered on.
+         */
+    } while ((gicr_read_pwrr(base) & PWRR_RDPD) != PWRR_ON);
+}
+
+static void gic600_pwr_off(uintptr_t base)
+{
+
+    /* Wait until group not transitioning */
+    gicr_wait_group_not_in_transit(base);
+
+    /* Power off redistributor */
+    gicr_write_pwrr(base, PWRR_OFF);
+
+    /*
+     * If this is the last man, turning this redistributor frame off will
+     * result in the group itself being powered off and RDGPD = 1.
+     * In that case, wait as long as it's in transition, or has aborted
+     * the transition altogether for any reason.
+     */
+    if ((gicr_read_pwrr(base) & PWRR_RDGPD) != 0) {
+        /* Wait until group not transitioning */
+        gicr_wait_group_not_in_transit(base);
+    }
+}
+
+/*
+ * Power off GIC-600 redistributor
+ */
+void gicv3_rdistif_off(void)
+{
+    uintptr_t gicr_base;
+
+    gicr_base = GIC_REDIS_BASE;
+    assert(gicr_base);
+
+    /* Attempt to power redistributor off */
+    gic600_pwr_off(gicr_base);
+}
+
+/*
+ * Power on GIC-600 redistributor
+ */
+void gicv3_rdistif_on(void)
+{
+    uintptr_t gicr_base;
+
+    gicr_base = GIC_REDIS_BASE;
+    assert(gicr_base);
+
+    /* Power redistributor on */
+    gic600_pwr_on(gicr_base);
+}
+
+/* vim: set ts=4 sw=4 noexpandtab: */
diff --git a/src/bsp/lk/dev/interrupt/arm_gic_v3/include/dev/interrupt/arm_gic.h b/src/bsp/lk/dev/interrupt/arm_gic_v3/include/dev/interrupt/arm_gic.h
new file mode 100644
index 0000000..3802032
--- /dev/null
+++ b/src/bsp/lk/dev/interrupt/arm_gic_v3/include/dev/interrupt/arm_gic.h
@@ -0,0 +1,196 @@
+/*
+ * Copyright (c) 2013, 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.
+ */
+#ifndef __DEV_INTERRUPT_ARM_GIC_H
+#define __DEV_INTERRUPT_ARM_GIC_H
+
+#include <sys/types.h>
+
+#define ICC_IAR1_EL1    sys_reg(3, 0, 12, 12, 0)
+#define ICC_EOIR1_EL1   sys_reg(3, 0, 12, 12, 1)
+#define ICC_IAR0_EL1    sys_reg(3, 0, 12,  8, 0)
+#define ICC_EOIR0_EL1   sys_reg(3, 0, 12,  8, 1)
+#define ICC_HPPIR1_EL1  sys_reg(3, 0, 12, 12, 2)
+
+#define GICD_CTLR_ENABLE_GRP0       (1 << 0)
+#define GICD_CTLR_ENGRP1NS          (1 << 1)
+#define GICD_CTLR_ENGRP1S           (1 << 2)
+#define GICD_CTLR_ARE               (1 << 4)
+#define GICD_CTLR_ARE_NS            (1 << 5)
+#define GICD_CTLR_DS                (1 << 6)
+#define GICD_CTLR_E1NWF             (1 << 7)
+#define GICD_CTLR_RWP               (1 << 31)
+
+#define GICR_WAKER_ProcessorSleep   (1 << 1)
+#define GICR_WAKER_ChildrenAsleep   (1 << 2)
+
+/* GICD_TYPER bit definitions */
+#define IT_LINES_NO_MASK    (0x1f)
+#define ENABLE_GRP0         (1 << 0)
+#define ENABLE_GRP1         (1 << 1)
+
+/* Mask for the priority field common to all GIC interfaces */
+#define GIC_PRI_MASK    0xff
+
+/* GICC_CTLR bit definitions */
+#define EOI_MODE_NS         (1 << 10)
+#define EOI_MODE_S          (1 << 9)
+#define IRQ_BYP_DIS_GRP1    (1 << 8)
+#define FIQ_BYP_DIS_GRP1    (1 << 7)
+#define IRQ_BYP_DIS_GRP0    (1 << 6)
+#define FIQ_BYP_DIS_GRP0    (1 << 5)
+#define CBPR                (1 << 4)
+#define FIQ_EN              (1 << 3)
+#define ACK_CTL             (1 << 2)
+
+/* GICv3 ICC_SRE register bit definitions*/
+#define ICC_SRE_EN      (1 << 3)
+#define ICC_SRE_SRE     (1 << 0)
+
+/* GICC_IIDR bit masks and shifts */
+#define GICC_IIDR_PID_SHIFT  20
+#define GICC_IIDR_ARCH_SHIFT 16
+#define GICC_IIDR_REV_SHIFT  12
+#define GICC_IIDR_IMP_SHIFT  0
+
+#define GICC_IIDR_PID_MASK   0xfff
+#define GICC_IIDR_ARCH_MASK  0xf
+#define GICC_IIDR_REV_MASK   0xf
+#define GICC_IIDR_IMP_MASK   0xfff
+
+#define SZ_64K               (0x00010000)
+#define INT_POL_SECCTL_NUM   20
+#define NR_INT_POL_CTL       (20)
+
+/* main cpu regs */
+#define GICC_CTLR               0x0000
+#define GICC_PMR                0x0004
+#define GICC_BPR                0x0008
+#define GICC_IAR                0x000c
+#define GICC_EOIR               0x0010
+#define GICC_RPR                0x0014
+#define GICC_HPPIR              0x0018
+#define GICC_APBR               0x001c
+#define GICC_AIAR               0x0020
+#define GICC_AEOIR              0x0024
+#define GICC_AHPPIR             0x0028
+#define GICC_APR                0x00d0
+#define GICC_NSAPR              0x00e0
+#define GICC_IIDR               0x00fc
+#define GICC_DIR                0x1000
+
+/* distribution regs */
+#define GICD_CTLR               0x000
+#define GICD_TYPER              0x004
+#define GICD_IIDR               0x008
+#define GICD_STATUSR            0x010
+#define GICD_SEIR               0x068
+#define GICD_IGROUPR            0x080
+#define GICD_ISENABLER          0x100
+#define GICD_ICENABLER          0x180
+#define GICD_ISPENDR            0x200
+#define GICD_ICPENDR            0x280
+#define GICD_ISACTIVER          0x300
+#define GICD_ICACTIVER          0x380
+#define GICD_IPRIORITYR         0x400
+#define GICD_ITARGETSR          0x800
+#define GICD_ICFGR              0xc00
+#define GICD_IGRPMODR           0xd00
+#define GICD_NSACR              0xe00
+#define GICD_SGIR               0xf00
+#define GICD_CPENDSGIR          0xf10
+#define GICD_SPENDSGIR          0xf20
+#define GICD_IROUTER            0x6000
+#define GICD_PIDR2              0xFFE8
+
+/*
+ * Re-Distributor registers, offsets from RD_base
+ */
+#define GICR_V3_CTLR                       GICD_CTLR
+#define GICR_V3_IIDR                       0x0004
+#define GICR_V3_TYPER                      0x0008
+#define GICR_V3_CTLR_RWP                   0x0008
+#define GICR_V3_STATUSR                    GICD_STATUSR
+#define GICR_V3_WAKER                      0x0014
+#define GICR_V3_SETLPIR                    0x0040
+#define GICR_V3_CLRLPIR                    0x0048
+#define GICR_V3_SEIR                       GICD_SEIR
+#define GICR_V3_PROPBASER                  0x0070
+#define GICR_V3_PENDBASER                  0x0078
+#define GICE_V3_IGROUP0                    0x0080
+#define GICR_V3_INVLPIR                    0x00A0
+#define GICR_V3_INVALLR                    0x00B0
+#define GICR_V3_SYNCR                      0x00C0
+#define GICR_V3_MOVLPIR                    0x0100
+#define GICR_V3_MOVALLR                    0x0110
+#define GICE_V3_IGRPMOD0                   0x0d00
+#define GICR_V3_PIDR2                      GICD_PIDR2
+
+#define GIC_V3_PIDR2_ARCH_MASK             0xf0
+#define GIC_V3_PIDR2_ARCH_GICv3            0x30
+#define GIC_V3_PIDR2_ARCH_GICv4            0x40
+
+#define GICR_V3_WAKER_ProcessorSleep       (1U << 1)
+#define GICR_V3_WAKER_ChildrenAsleep       (1U << 2)
+
+/*******************************************************************************
+ * GICv3 CPU interface registers & constants
+ ******************************************************************************/
+/* SCR bit definitions */
+#define SCR_NS_BIT                         (1U << 0)
+
+/* ICC_SRE bit definitions*/
+#define ICC_SRE_EN_BIT                     (1 << 3)
+#define ICC_SRE_DIB_BIT                    (1 << 2)
+#define ICC_SRE_DFB_BIT                    (1 << 1)
+#define ICC_SRE_SRE_BIT                    (1 << 0)
+
+#define GIC_READ(a) readl(a)
+#define GIC_WRITE(a, v) writel(v, a)
+
+enum {
+    /* Ignore cpu_mask and forward interrupt to all CPUs other than the current cpu */
+    ARM_GIC_SGI_FLAG_TARGET_FILTER_NOT_SENDER = 0x1,
+    /* Ignore cpu_mask and forward interrupt to current CPU only */
+    ARM_GIC_SGI_FLAG_TARGET_FILTER_SENDER = 0x2,
+    ARM_GIC_SGI_FLAG_TARGET_FILTER_MASK = 0x3,
+    /*
+     * Only forward the interrupt to CPUs that has the interrupt
+     * configured as group 1 (non-secure)
+     */
+    ARM_GIC_SGI_FLAG_NS = 0x4,
+};
+status_t arm_gic_sgi(u_int irq, u_int flags, u_int cpu_mask);
+
+void arm_gic_init(void);
+
+/* For lk called in EL3, MMU disabled context. */
+void gic_setup(void);
+
+#if GICV3_SUPPORT_GIC600
+void gicv3_rdistif_off(void);
+void gicv3_rdistif_on(void);
+#endif
+
+#endif
+
+/* vim: set ts=4 sw=4 noexpandtab: */
diff --git a/src/bsp/lk/dev/interrupt/arm_gic_v3/rules.mk b/src/bsp/lk/dev/interrupt/arm_gic_v3/rules.mk
new file mode 100644
index 0000000..a361ce6
--- /dev/null
+++ b/src/bsp/lk/dev/interrupt/arm_gic_v3/rules.mk
@@ -0,0 +1,14 @@
+LOCAL_DIR := $(GET_LOCAL_DIR)
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_SRCS += \
+	$(LOCAL_DIR)/arm_gic.c
+
+ifeq ($(GICV3_SUPPORT_GIC600),1)
+MODULE_SRCS += \
+	$(LOCAL_DIR)/gic600.c
+endif
+
+
+include make/module.mk
diff --git a/src/bsp/lk/dev/interrupt/or1k_pic/or1k_pic.c b/src/bsp/lk/dev/interrupt/or1k_pic/or1k_pic.c
new file mode 100644
index 0000000..12fcadf
--- /dev/null
+++ b/src/bsp/lk/dev/interrupt/or1k_pic/or1k_pic.c
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2015 Stefan Kristiansson
+ *
+ * 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 <err.h>
+#include <debug.h>
+#include <kernel/thread.h>
+#include <platform/interrupts.h>
+#include <platform/pic.h>
+#include <arch/or1k.h>
+
+static spin_lock_t gicd_lock;
+#if WITH_LIB_SM
+#define GICD_LOCK_FLAGS SPIN_LOCK_FLAG_IRQ_FIQ
+#else
+#define GICD_LOCK_FLAGS SPIN_LOCK_FLAG_INTERRUPTS
+#endif
+
+struct int_handler_struct {
+    int_handler handler;
+    void *arg;
+};
+
+static struct int_handler_struct int_handler_table[MAX_INT];
+
+void register_int_handler(unsigned int vector, int_handler handler, void *arg)
+{
+    spin_lock_saved_state_t state;
+
+    if (vector >= MAX_INT)
+        panic("%s: vector out of range %d\n", __FUNCTION__, vector);
+
+    spin_lock_save(&gicd_lock, &state, GICD_LOCK_FLAGS);
+
+    int_handler_table[vector].handler = handler;
+    int_handler_table[vector].arg = arg;
+
+    spin_unlock_restore(&gicd_lock, state, GICD_LOCK_FLAGS);
+}
+
+status_t mask_interrupt(unsigned int vector)
+{
+    if (vector >= MAX_INT)
+        return ERR_INVALID_ARGS;
+
+    mtspr(OR1K_SPR_PIC_PICMR_ADDR, mfspr(OR1K_SPR_PIC_PICMR_ADDR) & ~(1 << vector));
+
+    return NO_ERROR;
+}
+
+status_t unmask_interrupt(unsigned int vector)
+{
+    if (vector >= MAX_INT)
+        return ERR_INVALID_ARGS;
+
+    mtspr(OR1K_SPR_PIC_PICMR_ADDR, mfspr(OR1K_SPR_PIC_PICMR_ADDR) | (1 << vector));
+
+    return NO_ERROR;
+}
+
+enum handler_return platform_irq(void)
+{
+    enum handler_return ret = INT_NO_RESCHEDULE;
+
+    uint irq = __builtin_ffs(mfspr(OR1K_SPR_PIC_PICSR_ADDR)) - 1;
+
+    if (irq < MAX_INT && int_handler_table[irq].handler)
+        ret = int_handler_table[irq].handler(int_handler_table[irq].arg);
+
+    return ret;
+}
diff --git a/src/bsp/lk/dev/interrupt/or1k_pic/rules.mk b/src/bsp/lk/dev/interrupt/or1k_pic/rules.mk
new file mode 100644
index 0000000..1148b7f
--- /dev/null
+++ b/src/bsp/lk/dev/interrupt/or1k_pic/rules.mk
@@ -0,0 +1,8 @@
+LOCAL_DIR := $(GET_LOCAL_DIR)
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_SRCS += \
+	$(LOCAL_DIR)/or1k_pic.c
+
+include make/module.mk
diff --git a/src/bsp/lk/dev/keys/gpio_keypad.c b/src/bsp/lk/dev/keys/gpio_keypad.c
new file mode 100644
index 0000000..f0b8d12
--- /dev/null
+++ b/src/bsp/lk/dev/keys/gpio_keypad.c
@@ -0,0 +1,178 @@
+/*
+ * Copyright (c) 2009, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *  * Neither the name of Google, Inc. nor the names of its contributors
+ *    may be used to endorse or promote products derived from this
+ *    software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <assert.h>
+#include <bits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <dev/keys.h>
+#include <dev/gpio.h>
+#include <dev/gpio_keypad.h>
+#include <kernel/event.h>
+#include <kernel/timer.h>
+
+struct gpio_kp {
+	struct gpio_keypad_info *keypad_info;
+	struct timer timer;
+	event_t full_scan;
+	int current_output;
+	unsigned int some_keys_pressed:2;
+	unsigned long keys_pressed[0];
+};
+
+/* TODO: Support multiple keypads? */
+static struct gpio_kp *keypad;
+
+static void check_output(struct gpio_kp *kp, int out, int polarity)
+{
+	struct gpio_keypad_info *kpinfo = kp->keypad_info;
+	int key_index;
+	int in;
+	int gpio;
+	int changed = 0;
+
+	key_index = out * kpinfo->ninputs;
+	for (in = 0; in < kpinfo->ninputs; in++, key_index++) {
+		gpio = kpinfo->input_gpios[in];
+		changed = 0;
+		if (gpio_get(gpio) ^ !polarity) {
+			if (kp->some_keys_pressed < 3)
+				kp->some_keys_pressed++;
+			changed = !bitmap_set(kp->keys_pressed, key_index);
+		} else {
+			changed = bitmap_clear(kp->keys_pressed, key_index);
+		}
+		if (changed) {
+			int state = bitmap_test(kp->keys_pressed, key_index);
+			keys_post_event(kpinfo->keymap[key_index], state);
+		}
+	}
+
+	/* sets up the right state for the next poll cycle */
+	gpio = kpinfo->output_gpios[out];
+	if (kpinfo->flags & GPIOKPF_DRIVE_INACTIVE)
+		gpio_set(gpio, !polarity);
+	else
+		gpio_config(gpio, GPIO_INPUT);
+}
+
+static enum handler_return
+gpio_keypad_timer_func(struct timer *timer, time_t now, void *arg)
+{
+	struct gpio_kp *kp = keypad;
+	struct gpio_keypad_info *kpinfo = kp->keypad_info;
+	int polarity = !!(kpinfo->flags & GPIOKPF_ACTIVE_HIGH);
+	int out;
+	int gpio;
+
+	out = kp->current_output;
+	if (out == kpinfo->noutputs) {
+		out = 0;
+		kp->some_keys_pressed = 0;
+	} else {
+		check_output(kp, out, polarity);
+		out++;
+	}
+
+	kp->current_output = out;
+	if (out < kpinfo->noutputs) {
+		gpio = kpinfo->output_gpios[out];
+		if (kpinfo->flags & GPIOKPF_DRIVE_INACTIVE)
+			gpio_set(gpio, polarity);
+		else
+			gpio_config(gpio, polarity ? GPIO_OUTPUT : 0);
+		timer_set_oneshot(timer, kpinfo->settle_time,
+		                  gpio_keypad_timer_func, NULL);
+		goto done;
+	}
+
+	if (/*!kp->use_irq*/ 1 || kp->some_keys_pressed) {
+		event_signal(&kp->full_scan, false);
+		timer_set_oneshot(timer, kpinfo->poll_time,
+		                  gpio_keypad_timer_func, NULL);
+		goto done;
+	}
+
+#if 0
+	/* No keys are pressed, reenable interrupt */
+	for (out = 0; out < kpinfo->noutputs; out++) {
+		if (gpio_keypad_flags & GPIOKPF_DRIVE_INACTIVE)
+			gpio_set(kpinfo->output_gpios[out], polarity);
+		else
+			gpio_config(kpinfo->output_gpios[out], polarity ? GPIO_OUTPUT : 0);
+	}
+	for (in = 0; in < kpinfo->ninputs; in++)
+		enable_irq(gpio_to_irq(kpinfo->input_gpios[in]));
+	return INT_RESCHEDULE;
+#endif
+
+done:
+	return INT_RESCHEDULE;
+}
+
+void gpio_keypad_init(struct gpio_keypad_info *kpinfo)
+{
+	int key_count;
+	int output_val;
+	int output_cfg;
+	int i;
+	int len;
+
+	ASSERT(kpinfo->keymap && kpinfo->input_gpios && kpinfo->output_gpios);
+	key_count = kpinfo->ninputs * kpinfo->noutputs;
+
+	len = sizeof(struct gpio_kp) + (sizeof(unsigned long) *
+	                                BITMAP_NUM_WORDS(key_count));
+	keypad = malloc(len);
+	ASSERT(keypad);
+
+	memset(keypad, 0, len);
+	keypad->keypad_info = kpinfo;
+
+	output_val = (!!(kpinfo->flags & GPIOKPF_ACTIVE_HIGH)) ^
+	             (!!(kpinfo->flags & GPIOKPF_DRIVE_INACTIVE));
+	output_cfg = kpinfo->flags & GPIOKPF_DRIVE_INACTIVE ? GPIO_OUTPUT : 0;
+	for (i = 0; i < kpinfo->noutputs; i++) {
+		gpio_set(kpinfo->output_gpios[i], output_val);
+		gpio_config(kpinfo->output_gpios[i], output_cfg);
+	}
+	for (i = 0; i < kpinfo->ninputs; i++)
+		gpio_config(kpinfo->input_gpios[i], GPIO_INPUT);
+
+	keypad->current_output = kpinfo->noutputs;
+
+	event_init(&keypad->full_scan, false, EVENT_FLAG_AUTOUNSIGNAL);
+	timer_initialize(&keypad->timer);
+	timer_set_oneshot(&keypad->timer, 0, gpio_keypad_timer_func, NULL);
+
+	/* wait for the keypad to complete one full scan */
+	event_wait(&keypad->full_scan);
+}
diff --git a/src/bsp/lk/dev/keys/keys.c b/src/bsp/lk/dev/keys/keys.c
new file mode 100644
index 0000000..3e7c452
--- /dev/null
+++ b/src/bsp/lk/dev/keys/keys.c
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2008, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *  * Neither the name of Google, Inc. nor the names of its contributors
+ *    may be used to endorse or promote products derived from this
+ *    software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <bits.h>
+#include <debug.h>
+#include <string.h>
+#include <dev/keys.h>
+
+static unsigned long key_bitmap[BITMAP_NUM_WORDS(MAX_KEYS)];
+
+void keys_init(void)
+{
+	memset(key_bitmap, 0, sizeof(key_bitmap));
+}
+
+void keys_post_event(uint16_t code, int16_t value)
+{
+	if (code >= MAX_KEYS) {
+		dprintf(INFO, "Invalid keycode posted: %d\n", code);
+		return;
+	}
+
+	/* TODO: Implement an actual event queue if it becomes necessary */
+	if (value)
+		bitmap_set(key_bitmap, code);
+	else
+		bitmap_clear(key_bitmap, code);
+
+//	dprintf(INFO, "key state change: %d %d\n", code, value);
+}
+
+int keys_get_state(uint16_t code)
+{
+	if (code >= MAX_KEYS) {
+		dprintf(INFO, "Invalid keycode requested: %d\n", code);
+		return -1;
+	}
+	return bitmap_test(key_bitmap, code);
+}
diff --git a/src/bsp/lk/dev/keys/rules.mk b/src/bsp/lk/dev/keys/rules.mk
new file mode 100644
index 0000000..8a30ea0
--- /dev/null
+++ b/src/bsp/lk/dev/keys/rules.mk
@@ -0,0 +1,14 @@
+LOCAL_DIR := $(GET_LOCAL_DIR)
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_SRCS += \
+	$(LOCAL_DIR)/keys.c
+
+ifeq ($(KEYS_USE_GPIO_KEYPAD),1)
+MODULE_SRCS += \
+	$(LOCAL_DIR)/gpio_keypad.c
+endif
+
+include make/module.mk
+
diff --git a/src/bsp/lk/dev/net/pcnet/pcnet.c b/src/bsp/lk/dev/net/pcnet/pcnet.c
new file mode 100644
index 0000000..b2dbada
--- /dev/null
+++ b/src/bsp/lk/dev/net/pcnet/pcnet.c
@@ -0,0 +1,636 @@
+/*
+ * Copyright (c) 2013 Corey Tabaka
+ * Copyright (c) 2015 Travis Geiselbrecht
+ *
+ * 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 <reg.h>
+#include <err.h>
+#include <pcnet.h>
+#include <debug.h>
+#include <trace.h>
+#include <assert.h>
+#include <arch/x86.h>
+#include <platform/pc.h>
+#include <platform/pcnet.h>
+#include <platform/interrupts.h>
+#include <kernel/thread.h>
+#include <kernel/mutex.h>
+#include <kernel/event.h>
+#include <dev/class/netif.h>
+#include <dev/pci.h>
+#include <stdlib.h>
+#include <malloc.h>
+#include <string.h>
+#include <lwip/pbuf.h>
+#include <lk/init.h>
+
+#define LOCAL_TRACE 0
+
+#define PCNET_INIT_TIMEOUT 20000
+#define MAX_PACKET_SIZE 1518
+
+#define QEMU_IRQ_BUG_WORKAROUND 1
+
+struct pcnet_state {
+    int irq;
+    addr_t base;
+
+    uint8_t padr[6];
+
+    struct init_block_32 *ib;
+
+    struct rd_style3 *rd;
+    struct td_style3 *td;
+
+    struct pbuf **rx_buffers;
+    struct pbuf **tx_buffers;
+
+    /* queue accounting */
+    int rd_head;
+    int td_head;
+    int td_tail;
+
+    int rd_count;
+    int td_count;
+
+    int tx_pending;
+
+    mutex_t tx_lock;
+
+    /* bottom half state */
+    event_t event;
+    event_t initialized;
+    bool done;
+
+    struct netstack_state *netstack_state;
+};
+
+static status_t pcnet_init(struct device *dev);
+static status_t pcnet_read_pci_config(struct device *dev, pci_location_t *loc);
+
+static enum handler_return pcnet_irq_handler(void *arg);
+
+static int pcnet_thread(void *arg);
+static bool pcnet_service_tx(struct device *dev);
+static bool pcnet_service_rx(struct device *dev);
+
+static status_t pcnet_set_state(struct device *dev, struct netstack_state *state);
+static ssize_t pcnet_get_hwaddr(struct device *dev, void *buf, size_t max_len);
+static ssize_t pcnet_get_mtu(struct device *dev);
+
+static status_t pcnet_output(struct device *dev, struct pbuf *p);
+
+static struct netif_ops pcnet_ops = {
+    .std = {
+        .init = pcnet_init,
+    },
+
+    .set_state = pcnet_set_state,
+    .get_hwaddr = pcnet_get_hwaddr,
+    .get_mtu = pcnet_get_mtu,
+
+    .output = pcnet_output,
+};
+
+DRIVER_EXPORT(netif, &pcnet_ops.std);
+
+static inline uint32_t pcnet_read_csr(struct device *dev, uint8_t rap)
+{
+    struct pcnet_state *state = dev->state;
+
+    outpd(state->base + REG_RAP, rap);
+    return inpd(state->base + REG_RDP);
+}
+
+static inline void pcnet_write_csr(struct device *dev, uint8_t rap, uint16_t data)
+{
+    struct pcnet_state *state = dev->state;
+
+    outpd(state->base + REG_RAP, rap);
+    outpd(state->base + REG_RDP, data);
+}
+
+static inline uint32_t pcnet_read_bcr(struct device *dev, uint8_t rap)
+{
+    struct pcnet_state *state = dev->state;
+
+    outpd(state->base + REG_RAP, rap);
+    return inpd(state->base + REG_BDP);
+}
+
+static inline void pcnet_write_bcr(struct device *dev, uint8_t rap, uint16_t data)
+{
+    struct pcnet_state *state = dev->state;
+
+    outpd(state->base + REG_RAP, rap);
+    outpd(state->base + REG_BDP, data);
+}
+
+static status_t pcnet_init(struct device *dev)
+{
+    status_t res = NO_ERROR;
+    pci_location_t loc;
+    int i;
+
+    const struct platform_pcnet_config *config = dev->config;
+
+    if (!config)
+        return ERR_NOT_CONFIGURED;
+
+    if (pci_find_pci_device(&loc, config->device_id, config->vendor_id, config->index) != _PCI_SUCCESSFUL)
+        return ERR_NOT_FOUND;
+
+    struct pcnet_state *state = calloc(1, sizeof(struct pcnet_state));
+    if (!state)
+        return ERR_NO_MEMORY;
+
+    dev->state = state;
+
+    res = pcnet_read_pci_config(dev, &loc);
+    if (res)
+        goto error;
+
+    for (i=0; i < 6; i++)
+        state->padr[i] = inp(state->base + i);
+
+    LTRACEF("MAC: %02x:%02x:%02x:%02x:%02x:%02x\n", state->padr[0], state->padr[1], state->padr[2],
+            state->padr[3], state->padr[4], state->padr[5]);
+
+    /* put the controller into 32bit wide mode by performing a 32bit write to CSR0 */
+    outpd(state->base + 0, 0);
+
+    /* stop the controller for configuration */
+    pcnet_write_csr(dev, 0, CSR0_STOP);
+
+    /* setup 32bit (style 3) structures, burst, all CSR4 bits valid, TDM1[29] is ADD_FCS */
+    pcnet_write_csr(dev, 58, 3);
+
+    /* DMA plus enable */
+    pcnet_write_csr(dev, 4, pcnet_read_csr(dev, 4) | CSR4_DMAPLUS);
+
+    /* allocate 128 tx and 128 rx descriptor rings */
+    state->td_count = 128;
+    state->rd_count = 128;
+    state->td = memalign(16, state->td_count * DESC_SIZE);
+    state->rd = memalign(16, state->rd_count * DESC_SIZE);
+
+    state->rx_buffers = calloc(state->rd_count, sizeof(struct pbuf *));
+    state->tx_buffers = calloc(state->td_count, sizeof(struct pbuf *));
+
+    state->tx_pending = 0;
+
+    if (!state->td || !state->rd || !state->tx_buffers || !state->rx_buffers) {
+        res = ERR_NO_MEMORY;
+        goto error;
+    }
+
+    memset(state->td, 0, state->td_count * DESC_SIZE);
+    memset(state->rd, 0, state->rd_count * DESC_SIZE);
+
+    /* allocate temporary init block space */
+    state->ib = memalign(4, sizeof(struct init_block_32));
+    if (!state->ib) {
+        res = ERR_NO_MEMORY;
+        goto error;
+    }
+
+    LTRACEF("Init block addr: %p\n", state->ib);
+
+    /* setup init block */
+    state->ib->tlen = 7; // 128 descriptors
+    state->ib->rlen = 7; // 128 descriptors
+    state->ib->mode = 0;
+
+    state->ib->ladr = ~0;
+    state->ib->tdra = (uint32_t) state->td;
+    state->ib->rdra = (uint32_t) state->rd;
+
+    memcpy(state->ib->padr, state->padr, 6);
+
+    /* load the init block address */
+    pcnet_write_csr(dev, 1, (uint32_t) state->ib);
+    pcnet_write_csr(dev, 2, (uint32_t) state->ib >> 16);
+
+    /* setup receive descriptors */
+    for (i=0; i < state->rd_count; i++) {
+        //LTRACEF("Allocating pbuf %d\n", i);
+        struct pbuf *p = pbuf_alloc(PBUF_RAW, MAX_PACKET_SIZE, PBUF_RAM);
+
+        state->rd[i].rbadr = (uint32_t) p->payload;
+        state->rd[i].bcnt = -p->tot_len;
+        state->rd[i].ones = 0xf;
+        state->rd[i].own = 1;
+
+        state->rx_buffers[i] = p;
+    }
+
+    mutex_init(&state->tx_lock);
+
+    state->done = false;
+    event_init(&state->event, false, EVENT_FLAG_AUTOUNSIGNAL);
+    event_init(&state->initialized, false, 0);
+
+    /* start up a thread to process packet activity */
+    thread_resume(thread_create("[pcnet bh]", pcnet_thread, dev, DEFAULT_PRIORITY,
+                                DEFAULT_STACK_SIZE));
+
+    register_int_handler(state->irq, pcnet_irq_handler, dev);
+    unmask_interrupt(state->irq);
+
+#if QEMU_IRQ_BUG_WORKAROUND
+    register_int_handler(INT_BASE + 15, pcnet_irq_handler, dev);
+    unmask_interrupt(INT_BASE + 15);
+#endif
+
+    /* wait for initialization to complete */
+    res = event_wait_timeout(&state->initialized, PCNET_INIT_TIMEOUT);
+    if (res) {
+        /* TODO: cancel bottom half thread and tear down device instance */
+        LTRACEF("Failed to wait for IDON: %d\n", res);
+        return res;
+    }
+
+    LTRACE_EXIT;
+    return res;
+
+error:
+    LTRACEF("Error: %d\n", res);
+
+    if (state) {
+        free(state->td);
+        free(state->rd);
+        free(state->ib);
+        free(state->tx_buffers);
+        free(state->rx_buffers);
+    }
+
+    free(state);
+
+    return res;
+}
+
+static status_t pcnet_read_pci_config(struct device *dev, pci_location_t *loc)
+{
+    status_t res = NO_ERROR;
+    pci_config_t config;
+    uint8_t *buf = (uint8_t *) &config;
+    unsigned i;
+
+    DEBUG_ASSERT(dev->state);
+
+    struct pcnet_state *state = dev->state;
+
+    for (i=0; i < sizeof(config); i++)
+        pci_read_config_byte(loc, i, buf + i);
+
+    LTRACEF("Resources:\n");
+
+    for (i=0; i < countof(config.base_addresses); i++) {
+        if (config.base_addresses[i] & 0x1) {
+            LTRACEF("  BAR %d  I/O REG: %04x\n", i, config.base_addresses[i] & ~0x3);
+
+            state->base = config.base_addresses[i] & ~0x3;
+            break;
+        }
+    }
+
+    if (!state->base) {
+        res = ERR_NOT_CONFIGURED;
+        goto error;
+    }
+
+    if (config.interrupt_line != 0xff) {
+        LTRACEF("  IRQ %u\n", config.interrupt_line);
+
+        state->irq = config.interrupt_line + INT_BASE;
+    } else {
+        res = ERR_NOT_CONFIGURED;
+        goto error;
+    }
+
+    LTRACEF("Command: %04x\n", config.command);
+    LTRACEF("Status:  %04x\n", config.status);
+
+    pci_write_config_half(loc, PCI_CONFIG_COMMAND,
+                          (config.command | PCI_COMMAND_IO_EN | PCI_COMMAND_BUS_MASTER_EN) & ~PCI_COMMAND_MEM_EN);
+
+error:
+    return res;
+}
+
+static enum handler_return pcnet_irq_handler(void *arg)
+{
+    struct device *dev = arg;
+    struct pcnet_state *state = dev->state;
+
+    mask_interrupt(state->irq);
+
+#if QEMU_IRQ_BUG_WORKAROUND
+    mask_interrupt(INT_BASE + 15);
+#endif
+
+    event_signal(&state->event, false);
+
+    return INT_RESCHEDULE;
+}
+
+static int pcnet_thread(void *arg)
+{
+    DEBUG_ASSERT(arg);
+
+    struct device *dev = arg;
+    struct pcnet_state *state = dev->state;
+
+    /* kick off init, enable ints, and start operation */
+    pcnet_write_csr(dev, 0, CSR0_INIT | CSR0_IENA | CSR0_STRT);
+
+    while (!state->done) {
+        LTRACEF("Waiting for event.\n");
+        //event_wait_timeout(&state->event, 5000);
+        event_wait(&state->event);
+
+        int csr0 = pcnet_read_csr(dev, 0);
+
+        /* disable interrupts at the controller */
+        pcnet_write_csr(dev, 0, csr0 & ~CSR0_IENA);
+
+        LTRACEF("CSR0 = %04x\n", csr0);
+
+#if LOCAL_TRACE
+        if (csr0 & CSR0_RINT) TRACEF("RINT\n");
+        if (csr0 & CSR0_TINT) TRACEF("TINT\n");
+#endif
+
+        if (csr0 & CSR0_IDON) {
+            LTRACEF("IDON\n");
+
+            /* free the init block that we no longer need */
+            free(state->ib);
+            state->ib = NULL;
+
+            event_signal(&state->initialized, true);
+        }
+
+        if (csr0 & CSR0_ERR) {
+            LTRACEF("ERR\n");
+
+            /* TODO: handle errors, though not many need it */
+
+            /* clear flags, preserve necessary enables */
+            pcnet_write_csr(dev, 0, csr0 & (CSR0_TXON | CSR0_RXON | CSR0_IENA));
+        }
+
+        bool again = !!(csr0 & (CSR0_RINT | CSR0_TINT));
+        while (again) {
+            again = pcnet_service_tx(dev) | pcnet_service_rx(dev);
+        }
+
+        /* enable interrupts at the controller */
+        pcnet_write_csr(dev, 0, CSR0_IENA);
+        unmask_interrupt(state->irq);
+
+#if QEMU_IRQ_BUG_WORKAROUND
+        unmask_interrupt(INT_BASE + 15);
+#endif
+    }
+
+    return 0;
+}
+
+static bool pcnet_service_tx(struct device *dev)
+{
+    LTRACE_ENTRY;
+
+    struct pcnet_state *state = dev->state;
+
+    mutex_acquire(&state->tx_lock);
+
+    struct td_style3 *td = &state->td[state->td_tail];
+
+    if (state->tx_pending && td->own == 0) {
+        struct pbuf *p = state->tx_buffers[state->td_tail];
+        DEBUG_ASSERT(p);
+
+        state->tx_buffers[state->td_tail] = NULL;
+
+        LTRACEF("Retiring packet: td_tail=%d p=%p tot_len=%u\n", state->td_tail, p, p->tot_len);
+
+        state->tx_pending--;
+        state->td_tail = (state->td_tail + 1) % state->td_count;
+
+        if (td->err) {
+            LTRACEF("Descriptor error status encountered\n");
+            hexdump8(td, sizeof(*td));
+        }
+
+        mutex_release(&state->tx_lock);
+
+        pbuf_free(p);
+
+        LTRACE_EXIT;
+        return true;
+    } else {
+        mutex_release(&state->tx_lock);
+
+#if 0
+        LTRACEF("Nothing to do for TX.\n");
+        for (int i=0; i < state->td_count; i++)
+            printf("%d ", state->td[i].own);
+        printf("\n");
+#endif
+
+        LTRACE_EXIT;
+        return false;
+    }
+}
+
+static bool pcnet_service_rx(struct device *dev)
+{
+    LTRACE_ENTRY;
+
+    struct pcnet_state *state = dev->state;
+
+    struct rd_style3 *rd = &state->rd[state->rd_head];
+
+    if (rd->own == 0) {
+        struct pbuf *p = state->rx_buffers[state->rd_head];
+        DEBUG_ASSERT(p);
+
+        LTRACEF("Processing RX descriptor %d\n", state->rd_head);
+
+        if (rd->err) {
+            LTRACEF("Descriptor error status encountered\n");
+            hexdump8(rd, sizeof(*rd));
+        } else {
+            if (rd->mcnt <= p->tot_len) {
+
+                pbuf_realloc(p, rd->mcnt);
+
+#if LOCAL_TRACE
+                LTRACEF("payload=%p len=%u\n", p->payload, p->tot_len);
+                hexdump8(p->payload, p->tot_len);
+#endif
+
+                class_netstack_input(dev, state->netstack_state, p);
+
+                p = state->rx_buffers[state->rd_head] = pbuf_alloc(PBUF_RAW, MAX_PACKET_SIZE, PBUF_RAM);
+            } else {
+                LTRACEF("RX packet size error: mcnt = %u, buf len = %u\n", rd->mcnt, p->tot_len);
+            }
+        }
+
+        memset(rd, 0, sizeof(*rd));
+        memset(p->payload, 0, p->tot_len);
+
+        rd->rbadr = (uint32_t) p->payload;
+        rd->bcnt = -p->tot_len;
+        rd->ones = 0xf;
+        rd->own = 1;
+
+        state->rd_head = (state->rd_head + 1) % state->rd_count;
+
+        LTRACE_EXIT;
+        return true;
+    } else {
+#if 0
+        LTRACEF("Nothing to do for RX: rd_head=%d.\n", state->rd_head);
+        for (int i=0; i < state->rd_count; i++)
+            printf("%d ", state->rd[i].own);
+        printf("\n");
+#endif
+    }
+
+    LTRACE_EXIT;
+    return false;
+}
+
+static status_t pcnet_set_state(struct device *dev, struct netstack_state *netstack_state)
+{
+    if (!dev)
+        return ERR_INVALID_ARGS;
+
+    if (!dev->state)
+        return ERR_NOT_CONFIGURED;
+
+    struct pcnet_state *state = dev->state;
+
+    state->netstack_state = netstack_state;
+
+    return NO_ERROR;
+}
+
+static ssize_t pcnet_get_hwaddr(struct device *dev, void *buf, size_t max_len)
+{
+    if (!dev || !buf)
+        return ERR_INVALID_ARGS;
+
+    if (!dev->state)
+        return ERR_NOT_CONFIGURED;
+
+    struct pcnet_state *state = dev->state;
+
+    memcpy(buf, state->padr, MIN(sizeof(state->padr), max_len));
+
+    return sizeof(state->padr);
+}
+
+static ssize_t pcnet_get_mtu(struct device *dev)
+{
+    if (!dev)
+        return ERR_INVALID_ARGS;
+
+    return 1500;
+}
+
+static status_t pcnet_output(struct device *dev, struct pbuf *p)
+{
+    LTRACE_ENTRY;
+
+    if (!dev || !p)
+        return ERR_INVALID_ARGS;
+
+    if (!dev->state)
+        return ERR_NOT_CONFIGURED;
+
+    status_t res = NO_ERROR;
+    struct pcnet_state *state = dev->state;
+
+    mutex_acquire(&state->tx_lock);
+
+    struct td_style3 *td = &state->td[state->td_head];
+
+    if (td->own) {
+        LTRACEF("TX descriptor ring full\n");
+        res = ERR_NOT_READY; // maybe this should be ERR_NOT_ENOUGH_BUFFER?
+        goto done;
+    }
+
+    pbuf_ref(p);
+    p = pbuf_coalesce(p, PBUF_RAW);
+
+#if LOCAL_TRACE
+    LTRACEF("Queuing packet: td_head=%d p=%p tot_len=%u\n", state->td_head, p, p->tot_len);
+    hexdump8(p->payload, p->tot_len);
+#endif
+
+    /* clear flags */
+    memset(td, 0, sizeof(*td));
+
+    td->tbadr = (uint32_t) p->payload;
+    td->bcnt = -p->tot_len;
+    td->stp = 1;
+    td->enp = 1;
+    td->add_no_fcs = 1;
+    td->ones = 0xf;
+
+    state->tx_buffers[state->td_head] = p;
+    state->tx_pending++;
+
+    state->td_head = (state->td_head + 1) % state->td_count;
+
+    td->own = 1;
+
+    /* trigger tx */
+    pcnet_write_csr(dev, 0, CSR0_TDMD);
+
+done:
+    mutex_release(&state->tx_lock);
+    LTRACE_EXIT;
+    return res;
+}
+
+static const struct platform_pcnet_config pcnet0_config = {
+    .vendor_id = 0x1022,
+    .device_id = 0x2000,
+    .index = 0,
+};
+
+DEVICE_INSTANCE(netif, pcnet0, &pcnet0_config);
+
+static void pcnet_init_hook(uint level)
+{
+    device_init(device_get_by_name(netif, pcnet0));
+    class_netif_add(device_get_by_name(netif, pcnet0));
+}
+
+LK_INIT_HOOK(pcnet, &pcnet_init_hook, LK_INIT_LEVEL_PLATFORM);
+
diff --git a/src/bsp/lk/dev/net/pcnet/rules.mk b/src/bsp/lk/dev/net/pcnet/rules.mk
new file mode 100644
index 0000000..979a611
--- /dev/null
+++ b/src/bsp/lk/dev/net/pcnet/rules.mk
@@ -0,0 +1,10 @@
+LOCAL_DIR := $(GET_LOCAL_DIR)
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_SRCS += \
+	$(LOCAL_DIR)/pcnet.c
+
+MODULE_DEPS := lib/lwip
+
+include make/module.mk
diff --git a/src/bsp/lk/dev/net/smc91c96/include/dev/net/smc91c96.h b/src/bsp/lk/dev/net/smc91c96/include/dev/net/smc91c96.h
new file mode 100644
index 0000000..acd543e
--- /dev/null
+++ b/src/bsp/lk/dev/net/smc91c96/include/dev/net/smc91c96.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2008 Travis Geiselbrecht
+ *
+ * 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.
+ */
+#ifndef _DEV_NET_SMC91C96_H
+#define _DEV_NET_SMC91C96_H
+
+void smc91c96_init(void);
+
+#endif
+
diff --git a/src/bsp/lk/dev/net/smc91c96/rules.mk b/src/bsp/lk/dev/net/smc91c96/rules.mk
new file mode 100644
index 0000000..4f237b5
--- /dev/null
+++ b/src/bsp/lk/dev/net/smc91c96/rules.mk
@@ -0,0 +1,8 @@
+LOCAL_DIR := $(GET_LOCAL_DIR)
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_SRCS += \
+	$(LOCAL_DIR)/smc91c96.c
+
+include make/module.mk
diff --git a/src/bsp/lk/dev/net/smc91c96/smc91c96.c b/src/bsp/lk/dev/net/smc91c96/smc91c96.c
new file mode 100644
index 0000000..9c902e0
--- /dev/null
+++ b/src/bsp/lk/dev/net/smc91c96/smc91c96.c
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2008 Travis Geiselbrecht
+ *
+ * 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 <debug.h>
+#include <trace.h>
+#include <stdio.h>
+#include <dev/net/smc91c96.h>
+#include "smc91c96_p.h"
+
+#if !defined(SMC91C96_BASE_ADDR) || !defined(SMC91C96_IRQ)
+#error need to define SMC91C96_BASE_ADDR and SMC91C96_IRQ in project
+#endif
+
+static addr_t smc91c96_base = SMC91C96_BASE_ADDR;
+static uint8_t mac_addr[6];
+
+#define SMC_REG16(reg) ((volatile uint16_t *)(smc91c96_base + (reg)))
+#define SMC_REG8(reg) ((volatile uint8_t *)(smc91c96_base + (reg)))
+
+static inline void smc_bank(int bank)
+{
+	*SMC_REG16(SMC_BSR) = bank;
+}
+
+void smc91c96_init(void)
+{
+	int i;
+
+	TRACE;
+
+	// try to detect it
+	if ((*SMC_REG16(SMC_BSR) & 0xff00) != 0x3300) {
+		TRACEF("didn't see smc91c96 chip at 0x%x\n", (unsigned int)smc91c96_base);
+	}
+
+	// read revision
+	smc_bank(3);
+	TRACEF("detected, revision 0x%x\n", *SMC_REG16(SMC_REV));
+
+	// read in the mac address
+	smc_bank(1);
+	for (i=0; i < 6; i++) {
+		mac_addr[i] = *SMC_REG8(SMC_IAR0 + i);
+	}
+	TRACEF("mac address %02x:%02x:%02x:%02x:%02x:%02x\n",
+	       mac_addr[0], mac_addr[1], mac_addr[2],
+	       mac_addr[3], mac_addr[4], mac_addr[5]);
+
+	smc_bank(0);
+}
+
diff --git a/src/bsp/lk/dev/net/smc91c96/smc91c96_p.h b/src/bsp/lk/dev/net/smc91c96/smc91c96_p.h
new file mode 100644
index 0000000..9816d8f
--- /dev/null
+++ b/src/bsp/lk/dev/net/smc91c96/smc91c96_p.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2008 Travis Geiselbrecht
+ *
+ * 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.
+ */
+#ifndef __SMC91C96_P_H
+#define __SMC91C96_P_H
+
+// LAN91C96 stuffs
+
+/* registers */
+
+#define SMC_BSR   14
+
+/* bank 0 */
+#define SMC_TCR   0
+#define SMC_EPHSR 2
+#define SMC_RCR   4
+#define SMC_ECR   6
+#define SMC_MIR   8
+#define SMC_MCR   10
+
+/* bank 1 */
+#define SMC_CR    0
+#define SMC_BAR   2
+#define SMC_IAR0  4
+#define SMC_IAR1  5
+#define SMC_IAR2  6
+#define SMC_IAR3  7
+#define SMC_IAR4  8
+#define SMC_IAR5  9
+#define SMC_GPR   10
+#define SMC_CTR   12
+
+/* bank 2 */
+#define SMC_MMUCR 0
+#define SMC_AUTOTX 1
+#define SMC_PNR   2
+#define SMC_ARR   3
+#define SMC_FIFO  4
+#define SMC_PTR   6
+#define SMC_DATA0 8
+#define SMC_DATA1 10
+#define SMC_IST   12
+#define SMC_ACK   12
+#define SMC_MSK   13
+
+/* bank 3 */
+#define SMC_MT0   0
+#define SMC_MT1   1
+#define SMC_MT2   2
+#define SMC_MT3   3
+#define SMC_MT4   4
+#define SMC_MT5   5
+#define SMC_MT6   6
+#define SMC_MT7   7
+#define SMC_MGMT  8
+#define SMC_REV   10
+#define SMC_ERCV  12
+
+
+#endif
+
diff --git a/src/bsp/lk/dev/pmic/twl4030/include/dev/twl4030.h b/src/bsp/lk/dev/pmic/twl4030/include/dev/twl4030.h
new file mode 100644
index 0000000..f863221
--- /dev/null
+++ b/src/bsp/lk/dev/pmic/twl4030/include/dev/twl4030.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2008 Travis Geiselbrecht
+ *
+ * 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.
+ */
+#ifndef __DEV_TWL4030_H
+#define __DEV_TWL4030_H
+
+#include <sys/types.h>
+
+void twl4030_init(void);
+
+/* USB parts of the pmic */
+int twl4030_usb_reset(void);
+int twl4030_set_usb_pullup(bool pullup);
+int twl4030_init_hs(void);
+
+#endif
+
diff --git a/src/bsp/lk/dev/pmic/twl4030/rules.mk b/src/bsp/lk/dev/pmic/twl4030/rules.mk
new file mode 100644
index 0000000..a5646b2
--- /dev/null
+++ b/src/bsp/lk/dev/pmic/twl4030/rules.mk
@@ -0,0 +1,8 @@
+LOCAL_DIR := $(GET_LOCAL_DIR)
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_SRCS += \
+	$(LOCAL_DIR)/twl4030.c
+
+include make/module.mk
diff --git a/src/bsp/lk/dev/pmic/twl4030/twl4030.c b/src/bsp/lk/dev/pmic/twl4030/twl4030.c
new file mode 100644
index 0000000..5cc2625
--- /dev/null
+++ b/src/bsp/lk/dev/pmic/twl4030/twl4030.c
@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 2008 Travis Geiselbrecht
+ *
+ * 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 <debug.h>
+#include <trace.h>
+#include <sys/types.h>
+#include <dev/i2c.h>
+#include <dev/twl4030.h>
+#include "twl4030_hw.h"
+
+// XXX move to target specific setup
+#define TWL_I2C_BUS 0
+
+void twl4030_init(void)
+{
+}
+
+static int twl4030_usb_write(uint8_t address, uint8_t data)
+{
+	return i2c_write_reg(TWL_I2C_BUS, TWL_USB_ADDR, address, data);
+}
+
+static int twl4030_usb_read(uint8_t address)
+{
+	uint8_t data;
+
+	int err = i2c_read_reg(TWL_I2C_BUS, TWL_USB_ADDR, address, &data);
+	if (err < 0)
+		return err;
+
+	return data;
+}
+
+static int twl4030_usb_set_bits(uint8_t reg, uint8_t bits)
+{
+	return twl4030_usb_write(reg + 1, bits);
+}
+
+static int twl4030_usb_clear_bits(uint8_t reg, uint8_t bits)
+{
+	return twl4030_usb_write(reg + 2, bits);
+}
+
+static void twl4030_i2c_access(bool on)
+{
+	int val;
+
+	if ((val = twl4030_usb_read(PHY_CLK_CTRL)) >= 0) {
+		if (on) {
+			/* enable DPLL to access PHY registers over I2C */
+			val |= REQ_PHY_DPLL_CLK;
+			twl4030_usb_write(PHY_CLK_CTRL, (uint8_t)val);
+
+			while (!(twl4030_usb_read(PHY_CLK_CTRL_STS) & PHY_DPLL_CLK)) {
+				spin(10);
+			}
+			if (!(twl4030_usb_read(PHY_CLK_CTRL_STS) & PHY_DPLL_CLK))
+				printf("Timeout setting T2 HSUSB " "PHY DPLL clock\n");
+		} else {
+			/* let ULPI control the DPLL clock */
+			val &= ~REQ_PHY_DPLL_CLK;
+			twl4030_usb_write(PHY_CLK_CTRL, (uint8_t)val);
+		}
+	}
+	return;
+}
+
+int twl4030_usb_reset(void)
+{
+	TRACE_ENTRY;
+#if 0
+	twl4030_usb_clear_bits(OTG_CTRL, DMPULLDOWN | DPPULLDOWN);
+	twl4030_usb_clear_bits(USB_INT_EN_RISE, ~0);
+	twl4030_usb_clear_bits(USB_INT_EN_FALL, ~0);
+	twl4030_usb_clear_bits(MCPC_IO_CTRL, ~TXDTYP);
+	twl4030_usb_set_bits(MCPC_IO_CTRL, TXDTYP);
+	twl4030_usb_clear_bits(OTHER_FUNC_CTRL, (BDIS_ACON_EN | FIVEWIRE_MODE));
+	twl4030_usb_clear_bits(OTHER_IFC_CTRL, ~0);
+	twl4030_usb_clear_bits(OTHER_INT_EN_RISE, ~0);
+	twl4030_usb_clear_bits(OTHER_INT_EN_FALL, ~0);
+	twl4030_usb_clear_bits(OTHER_IFC_CTRL2, ~0);
+	twl4030_usb_clear_bits(REG_CTRL_EN, ULPI_I2C_CONFLICT_INTEN);
+	twl4030_usb_clear_bits(OTHER_FUNC_CTRL2, VBAT_TIMER_EN);
+#endif
+
+	/* Enable writing to power configuration registers */
+	i2c_write_reg(TWL_I2C_BUS, TWL_PM_RECEIVER_ADDR, PROTECT_KEY, 0xC0);
+	i2c_write_reg(TWL_I2C_BUS, TWL_PM_RECEIVER_ADDR, PROTECT_KEY, 0x0C);
+
+	/* put VUSB3V1 LDO in active state */
+	i2c_write_reg(TWL_I2C_BUS, TWL_PM_RECEIVER_ADDR, VUSB_DEDICATED2, 0);
+
+	/* input to VUSB3V1 LDO is from VBAT, not VBUS */
+	i2c_write_reg(TWL_I2C_BUS, TWL_PM_RECEIVER_ADDR, VUSB_DEDICATED1, 0x14);
+
+	/* turn on 3.1V regulator */
+	i2c_write_reg(TWL_I2C_BUS, TWL_PM_RECEIVER_ADDR, VUSB3V1_DEV_GRP, 0x20);
+	i2c_write_reg(TWL_I2C_BUS, TWL_PM_RECEIVER_ADDR, VUSB3V1_TYPE, 0);
+
+	/* turn on 1.5V regulator */
+	i2c_write_reg(TWL_I2C_BUS, TWL_PM_RECEIVER_ADDR, VUSB1V5_DEV_GRP, 0x20);
+	i2c_write_reg(TWL_I2C_BUS, TWL_PM_RECEIVER_ADDR, VUSB1V5_TYPE, 0);
+
+	/* turn on 1.8V regulator */
+	i2c_write_reg(TWL_I2C_BUS, TWL_PM_RECEIVER_ADDR, VUSB1V8_DEV_GRP, 0x20);
+	i2c_write_reg(TWL_I2C_BUS, TWL_PM_RECEIVER_ADDR, VUSB1V8_TYPE, 0);
+
+	/* disable access to power configuration registers */
+	i2c_write_reg(TWL_I2C_BUS, TWL_PM_RECEIVER_ADDR, PROTECT_KEY, 0);
+
+	/* turn on the phy */
+	uint8_t pwr = twl4030_usb_read(PHY_PWR_CTRL);
+	pwr &= ~PHYPWD;
+	twl4030_usb_write(PHY_PWR_CTRL, pwr);
+	twl4030_usb_write(PHY_CLK_CTRL,
+	                  twl4030_usb_read(PHY_CLK_CTRL) |
+	                  (CLOCKGATING_EN | CLK32K_EN));
+
+	/* set DPLL i2c access mode */
+	twl4030_i2c_access(true);
+	/* set ulpi mode */
+	twl4030_usb_clear_bits(IFC_CTRL, CARKITMODE);
+	twl4030_usb_set_bits(POWER_CTRL, OTG_ENAB);
+	twl4030_usb_write(FUNC_CTRL, XCVRSELECT_HS); // set high speed mode
+//	twl4030_usb_write(FUNC_CTRL, XCVRSELECT_FS); // set full speed mode
+	twl4030_i2c_access(false);
+
+	return 0;
+}
+
+int twl4030_init_hs(void)
+{
+	return 0;
+}
+
+int twl4030_set_usb_pullup(bool pullup)
+{
+	TRACE_ENTRY;
+
+	if (pullup) {
+		twl4030_usb_clear_bits(OTG_CTRL, DPPULLDOWN);
+		twl4030_usb_set_bits(FUNC_CTRL, TERMSELECT);
+	} else {
+		twl4030_usb_clear_bits(FUNC_CTRL, TERMSELECT);
+		twl4030_usb_set_bits(OTG_CTRL, DPPULLDOWN);
+	}
+
+	return 0;
+}
+
+
diff --git a/src/bsp/lk/dev/pmic/twl4030/twl4030_hw.h b/src/bsp/lk/dev/pmic/twl4030/twl4030_hw.h
new file mode 100644
index 0000000..1b37f93
--- /dev/null
+++ b/src/bsp/lk/dev/pmic/twl4030/twl4030_hw.h
@@ -0,0 +1,369 @@
+/*
+ * Copyright (c) 2008 Travis Geiselbrecht
+ *
+ * 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.
+ */
+#ifndef __TWL4030_HW_H
+#define __TWL4030_HW_H
+
+/* TWL i2c addresses */
+#define TWL_I2C_ADDR0 0x48
+#define TWL_I2C_ADDR1 0x49
+#define TWL_I2C_ADDR2 0x4a
+#define TWL_I2C_ADDR3 0x4b
+
+#define TWL_USB_ADDR    TWL_I2C_ADDR0
+#define TWL_INTBR_ADDR  TWL_I2C_ADDR1
+#define TWL_PM_RECEIVER_ADDR    TWL_I2C_ADDR3
+
+/* TWL registers */
+#define PROTECT_KEY     0x44
+#define VAUX1_DEV_GRP   0x72
+#define VAUX1_TYPE      0x73
+#define VAUX1_REMAP     0x74
+#define VAUX1_DEDICATED 0x75
+#define VAUX2_DEV_GRP   0x76
+#define VAUX2_TYPE      0x77
+#define VAUX2_REMAP     0x78
+#define VAUX2_DEDICATED 0x79
+#define VAUX3_DEV_GRP   0x7a
+#define VAUX3_TYPE      0x7b
+#define VAUX3_REMAP     0x7c
+#define VAUX3_DEDICATED 0x7d
+#define VAUX4_DEV_GRP   0x7e
+#define VAUX4_TYPE      0x7f
+#define VAUX4_REMAP     0x80
+#define VAUX4_DEDICATED 0x81
+#define VMMC1_DEV_GRP   0x82
+#define VMMC1_TYPE      0x83
+#define VMMC1_REMAP     0x84
+#define VMMC1_DEDICATED 0x85
+#define VMMC2_DEV_GRP   0x86
+#define VMMC2_TYPE      0x87
+#define VMMC2_REMAP     0x88
+#define VMMC2_DEDICATED 0x89
+#define VPLL1_DEV_GRP   0x8a
+#define VPLL1_TYPE      0x8b
+#define VPLL1_REMAP     0x8c
+#define VPLL1_DEDICATED 0x8d
+#define VPLL2_DEV_GRP   0x8e
+#define VPLL2_TYPE      0x8f
+#define VPLL2_REMAP     0x90
+#define VPLL2_DEDICATED 0x91
+#define VDAC_DEV_GRP        0x96
+#define VDAC_DEDICATED      0x99
+#define VDD2_DEV_GRP        0xBE
+#define VDD2_TYPE       0xBF
+#define VDD2_REMAP      0xC0
+#define VDD2_CFG        0xC1
+#define VSIM_DEV_GRP        0x92
+#define VSIM_TYPE       0x93
+#define VSIM_REMAP      0x94
+#define VSIM_DEDICATED      0x95
+#define PMBR1           0x92
+
+#define SECONDS_REG     0x1C
+#define MINUTES_REG     0x1D
+#define ALARM_SECONDS_REG   0x23
+#define ALARM_MINUTES_REG   0x24
+#define ALARM_HOURS_REG     0x25
+
+#define RTC_STATUS_REG      0x2A
+#define RTC_INTERRUPTS_REG  0x2B
+
+#define PWR_ISR1        0x2E
+#define PWR_IMR1        0x2F
+#define PWR_ISR2        0x30
+#define PWR_IMR2        0x31
+#define PWR_EDR1        0x33
+
+#define CFG_PWRANA2     0x3F
+#define RTC_INTERRUPTS_REG  0x2B
+#define STS_HW_CONDITIONS   0x45
+
+#define P1_SW_EVENTS        0x46
+#define P2_SW_EVENTS        0x47
+#define P3_SW_EVENTS        0x48
+
+#define VDD1_TRIM1      0x62
+#define VDD1_TRIM2      0x63
+#define VDD1_VFLOOR     0xBB
+#define VDD1_VROOF      0xBC
+#define PB_CFG          0x4A
+#define PB_WORD_MSB     0x4B
+#define PB_WORD_LSB     0x4C
+
+#define VSIM_REMAP      0x94
+#define VDAC_REMAP      0x98
+#define VINTANA1_DEV_GRP    0x9A
+#define VINTANA1_REMAP      0x9C
+#define VINTANA2_REMAP      0xA0
+#define VINTANA2_DEV_GRP    0x9E
+#define VINTDIG_DEV_GRP     0xA2
+#define VINTDIG_REMAP       0xA4
+#define VIO_DEV_GRP     0xA6
+#define VIO_REMAP       0xA8
+#define VDD1_REMAP      0xB2
+#define VDD2_REMAP      0xC0
+#define REGEN_REMAP     0xDC
+#define NRESPWRON_REMAP     0xDF
+#define CLKEN_REMAP     0xE2
+#define SYSEN_REMAP     0xE5
+#define HFCLKOUT_REMAP      0xE8
+#define HFCLKOUT_DEV_GRP    0xE6
+#define T32KCLKOUT_REMAP    0xEB
+#define TRITON_RESET_REMAP  0xEE
+#define MAINREF_REMAP       0xF1
+#define VIBRA_CTL       0x45
+
+
+#define VUSB1V5_DEV_GRP 0xCC
+#define VUSB1V5_TYPE    0xCD
+#define VUSB1V5_REMAP   0xCE
+#define VUSB1V8_DEV_GRP 0xCF
+#define VUSB1V8_TYPE    0xD0
+#define VUSB1V8_REMAP   0xD1
+#define VUSB3V1_DEV_GRP 0xD2
+#define VUSB3V1_TYPE    0xD3
+#define VUSB3V1_REMAP   0xD4
+#define VUSBCP_DEV_GRP  0xD5
+#define VUSBCP_TYPE     0xD6
+#define VUSBCP_REMAP    0xD7
+#define VUSB_DEDICATED1 0xD8
+#define VUSB_DEDICATED2 0xD9
+
+/* USB registers */
+#define VENDOR_ID_LO    0x0
+#define VENDOR_ID_HI    0x1
+#define PRODUCT_ID_LO   0x2
+#define PRODUCT_ID_HI   0x3
+#define FUNC_CTRL       0x4
+#define FUNC_CTRL_SET   0x5
+#define FUNC_CTRL_CLR   0x6
+#   define  SUSPENDM        (1 << 6)
+#   define  RESET           (1 << 5)
+#   define  OPMODE_MASK     (3 << 3) /* bits 3 and 4 */
+#   define  OPMODE_NORMAL       (0 << 3)
+#   define  OPMODE_NONDRIVING   (1 << 3)
+#   define  OPMODE_DISABLE_BIT_NRZI (2 << 3)
+#   define  TERMSELECT      (1 << 2)
+#   define  XCVRSELECT_MASK     (3 << 0) /* bits 0 and 1 */
+#   define  XCVRSELECT_HS       (0 << 0)
+#   define  XCVRSELECT_FS       (1 << 0)
+#   define  XCVRSELECT_LS       (2 << 0)
+#   define  XCVRSELECT_FS4LS    (3 << 0)
+#define IFC_CTRL        0x7
+#define IFC_CTRL_SET    0x8
+#define IFC_CTRL_CLR    0x9
+#   define  INTERFACE_PROTECT_DISABLE (1 << 7)
+#   define  AUTORESUME      (1 << 4)
+#   define  CLOCKSUSPENDM       (1 << 3)
+#   define  CARKITMODE      (1 << 2)
+#   define  FSLSSERIALMODE_3PIN (1 << 1)
+#define OTG_CTRL        0xa
+#define OTG_CTRL_SET    0xb
+#define OTG_CTRL_CLR    0xc
+#define  DRVVBUS         (1 << 5)
+#define  CHRGVBUS        (1 << 4)
+#define  DISCHRGVBUS     (1 << 3)
+#define  DMPULLDOWN      (1 << 2)
+#define  DPPULLDOWN      (1 << 1)
+#define  IDPULLUP        (1 << 0)
+#define USB_INT_EN_RISE     0xd
+#define USB_INT_EN_RISE_SET 0xe
+#define USB_INT_EN_RISE_CLR 0xf
+#define USB_INT_EN_FALL     0x10
+#define USB_INT_EN_FALL_SET 0x11
+#define USB_INT_EN_FALL_CLR 0x12
+#    define    HOSTDISCONNECT           (1 << 0)
+#define USB_INT_STS     0x13
+#define USB_INT_LATCH   0x14
+#define USB_DEBUG       0x15
+#define SCRATCH_REG     0x16
+#define SCRATCH_REG_SET 0x17
+#define SCRATCH_REG_CLR 0x18
+#define CARKIT_CTRL     0x19
+#define CARKIT_CTRL_SET 0x1a
+#define CARKIT_CTRL_CLR 0x1b
+#define        MICEN                    (1 << 6)
+#define        SPKRIGHTEN               (1 << 5)
+#define        SPKLEFTEN                (1 << 4)
+#define        RXDEN                    (1 << 3)
+#define        TXDEN                    (1 << 2)
+#define        IDGNDDRV                 (1 << 1)
+#define        CARKITPWR                (1 << 0)
+#define CARKIT_INT_DELAY    0x1c
+#define CARKIT_INT_EN       0x1d
+#define CARKIT_INT_EN_SET   0x1e
+#define CARKIT_INT_EN_CLR   0x1f
+#define CARKIT_INT_STS  0x20
+#define CARKIT_INT_LATCH    0x21
+#define CARKIT_PLS_CTRL     0x22
+#define CARKIT_PLS_CTRL_SET 0x23
+#define CARKIT_PLS_CTRL_CLR 0x24
+#    define    SPKRRIGHT_BIASEN         (1 << 3)
+#    define    SPKRLEFT_BIASEN          (1 << 2)
+#    define    RXPLSEN                  (1 << 1)
+#    define    TXPLSEN                  (1 << 0)
+#define TRANS_POS_WIDTH 0x25
+#define TRANS_NEG_WIDTH 0x26
+#define RCV_PLTY_RECOVERY   0x27
+#define MCPC_CTRL       0x30
+#define MCPC_CTRL_SET   0x31
+#define MCPC_CTRL_CLR   0x32
+#define        RTSOL                    (1 << 7)
+#define        EXTSWR                   (1 << 6)
+#define        EXTSWC                   (1 << 5)
+#define        VOICESW                  (1 << 4)
+#define        OUT64K                   (1 << 3)
+#define        RTSCTSSW                 (1 << 2)
+#define        HS_UART                  (1 << 0)
+#define MCPC_IO_CTRL 0x033
+#define MCPC_IO_CTRL_SET 0x034
+#define MCPC_IO_CTRL_CLR 0x035
+#define  MICBIASEN       (1<< 5)
+#define  CTS_NPU         (1 << 4)
+#define  RXD_PU          (1 << 3)
+#define  TXDTYP          (1 << 2)
+#define  CTSTYP          (1 << 1)
+#define  RTSTYP          (1 << 0)
+#define MCPC_CTRL2 0x036
+#define MCPC_CTRL2_SET 0x037
+#define MCPC_CTRL2_CLR 0x038
+#   define  MCPC_CK_EN  (1 << 0)
+#define OTHER_FUNC_CTRL 0x080
+#define OTHER_FUNC_CTRL_SET 0x081
+#define OTHER_FUNC_CTRL_CLR 0x082
+#define  BDIS_ACON_EN        (1<< 4)
+#define  FIVEWIRE_MODE       (1 << 2)
+#define OTHER_IFC_CTRL 0x083
+#define OTHER_IFC_CTRL_SET 0x084
+#define OTHER_IFC_CTRL_CLR 0x085
+#    define    OE_INT_EN                (1 << 6)
+#    define    CEA2011_MODE             (1 << 5)
+#    define    FSLSSERIALMODE_4PIN      (1 << 4)
+#    define    HIZ_ULPI_60MHZ_OUT       (1 << 3)
+#    define    HIZ_ULPI                 (1 << 2)
+#    define    ALT_INT_REROUTE          (1 << 0)
+#define OTHER_INT_EN_RISE 0x086
+#define OTHER_INT_EN_RISE_SET 0x087
+#define OTHER_INT_EN_RISE_CLR 0x088
+#define OTHER_INT_EN_FALL 0x089
+#define OTHER_INT_EN_FALL_SET 0x08A
+#define OTHER_INT_EN_FALL_CLR 0x08B
+#define OTHER_INT_STS 0x8C
+#define OTHER_INT_LATCH 0x8D
+#define ID_INT_EN_RISE 0x08E
+#define ID_INT_EN_RISE_SET 0x08F
+#define ID_INT_EN_RISE_CLR 0x090
+#define ID_INT_EN_FALL 0x091
+#define ID_INT_EN_FALL_SET 0x092
+#define ID_INT_EN_FALL_CLR 0x093
+#define ID_INT_STS 0x094
+#define ID_INT_LATCH 0x95
+#define ID_STATUS 0x96
+#define CARKIT_SM_1_INT_EN 0x097
+#define CARKIT_SM_1_INT_EN_SET 0x098
+#define CARKIT_SM_1_INT_EN_CLR 0x099
+#define CARKIT_SM_1_INT_STS 0x09A
+#define CARKIT_SM_1_INT_LATCH 0x9B
+#define CARKIT_SM_2_INT_EN 0x09C
+#define CARKIT_SM_2_INT_EN_SET 0x09D
+#define CARKIT_SM_2_INT_EN_CLR 0x09E
+#define CARKIT_SM_2_INT_STS 0x09F
+#define CARKIT_SM_2_INT_LATCH 0xA0
+#define CARKIT_SM_CTRL 0x0A1
+#define CARKIT_SM_CTRL_SET 0x0A2
+#define CARKIT_SM_CTRL_CLR 0x0A3
+#define CARKIT_SM_CMD 0x0A4
+#define CARKIT_SM_CMD_SET 0x0A5
+#define CARKIT_SM_CMD_CLR 0x0A6
+#define CARKIT_SM_CMD_STS 0xA7
+#define CARKIT_SM_STATUS 0xA8
+#define CARKIT_SM_NEXT_STATUS 0xA9
+#define CARKIT_SM_ERR_STATUS 0xAA
+#define CARKIT_SM_CTRL_STATE 0xAB
+#define POWER_CTRL 0xAC
+#define POWER_CTRL_SET 0xAD
+#define POWER_CTRL_CLR 0xAE
+#   define  OTG_ENAB        (1 << 5)
+#define OTHER_IFC_CTRL2 0xAF
+#define OTHER_IFC_CTRL2_SET 0xB0
+#define OTHER_IFC_CTRL2_CLR 0xB1
+#   define  ULPI_TXEN_POL   (1 << 3)
+#   define  ULPI_4PIN_2430  (1 << 2)
+#define REG_CTRL_EN 0xB2
+#define REG_CTRL_EN_SET 0xB3
+#define REG_CTRL_EN_CLR 0xB4
+#define REG_CTRL_ERROR 0xB5
+#define  ULPI_I2C_CONFLICT_INTEN (1 << 0)
+#define OTHER_FUNC_CTRL2 0xB8
+#define OTHER_FUNC_CTRL2_SET 0xB9
+#define OTHER_FUNC_CTRL2_CLR 0xBA
+#define  VBAT_TIMER_EN       (1 << 0)
+#define CARKIT_ANA_CTRL 0xBB
+#define CARKIT_ANA_CTRL_SET 0xBC
+#define CARKIT_ANA_CTRL_CLR 0xBD
+#define VBUS_DEBOUNCE 0xC0
+#define ID_DEBOUNCE 0xC1
+#define TPH_DP_CON_MIN 0xC2
+#define TPH_DP_CON_MAX 0xC3
+#define TCR_DP_CON_MIN 0xC4
+#define TCR_DP_CON_MAX 0xC5
+#define TPH_DP_PD_SHORT 0xC6
+#define TPH_CMD_DLY 0xC7
+#define TPH_DET_RST 0xC8
+#define TPH_AUD_BIAS 0xC9
+#define TCR_UART_DET_MIN 0xCA
+#define TCR_UART_DET_MAX 0xCB
+#define TPH_ID_INT_PW 0xCD
+#define TACC_ID_INT_WAIT 0xCE
+#define TACC_ID_INT_PW 0xCF
+#define TPH_CMD_WAIT 0xD0
+#define TPH_ACK_WAIT 0xD1
+#define TPH_DP_DISC_DET 0xD2
+#define VBAT_TIMER 0xD3
+#define CARKIT_4W_DEBUG 0xE0
+#define CARKIT_5W_DEBUG 0xE1
+#define CARKIT_5W_DEBUG 0xE1
+#define TEST_CTRL_CLR 0xEB
+#define TEST_CARKIT_SET 0xEC
+#define TEST_CARKIT_CLR 0xED
+#define TEST_POWER_SET 0xEE
+#define TEST_POWER_CLR 0xEF
+#define TEST_ULPI 0xF0
+#define TXVR_EN_TEST_SET 0xF2
+#define TXVR_EN_TEST_CLR 0xF3
+#define VBUS_EN_TEST 0xF4
+#define ID_EN_TEST 0xF5
+#define PSM_EN_TEST_SET 0xF6
+#define PSM_EN_TEST_CLR 0xF7
+#define PHY_TRIM_CTRL 0xFC
+#define PHY_PWR_CTRL 0xFD
+#   define  PHYPWD          (1 << 0)
+#define PHY_CLK_CTRL 0xFE
+#   define  CLOCKGATING_EN      (1 << 2)
+#   define  CLK32K_EN           (1 << 1)
+#   define  REQ_PHY_DPLL_CLK    (1 << 0)
+#define PHY_CLK_CTRL_STS 0xFF
+#   define  PHY_DPLL_CLK        (1 << 0)
+
+#endif
+
diff --git a/src/bsp/lk/dev/rules.mk b/src/bsp/lk/dev/rules.mk
new file mode 100644
index 0000000..e05ff66
--- /dev/null
+++ b/src/bsp/lk/dev/rules.mk
@@ -0,0 +1,17 @@
+LOCAL_DIR := $(GET_LOCAL_DIR)
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_SRCS += \
+	$(LOCAL_DIR)/dev.c \
+	$(LOCAL_DIR)/driver.c \
+	$(LOCAL_DIR)/class/block_api.c \
+	$(LOCAL_DIR)/class/i2c_api.c \
+	$(LOCAL_DIR)/class/spi_api.c \
+	$(LOCAL_DIR)/class/uart_api.c \
+	$(LOCAL_DIR)/class/fb_api.c \
+	$(LOCAL_DIR)/class/netif_api.c \
+
+EXTRA_LINKER_SCRIPTS += $(LOCAL_DIR)/devices.ld $(LOCAL_DIR)/drivers.ld
+
+include make/module.mk
diff --git a/src/bsp/lk/dev/timer/arm_cortex_a9/arm_cortex_a9_timer.c b/src/bsp/lk/dev/timer/arm_cortex_a9/arm_cortex_a9_timer.c
new file mode 100644
index 0000000..ed06d54
--- /dev/null
+++ b/src/bsp/lk/dev/timer/arm_cortex_a9/arm_cortex_a9_timer.c
@@ -0,0 +1,227 @@
+/*
+ * Copyright (c) 2014 Travis Geiselbrecht
+ *
+ * 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 <dev/timer/arm_cortex_a9.h>
+
+#include <debug.h>
+#include <sys/types.h>
+#include <err.h>
+#include <stdio.h>
+#include <assert.h>
+#include <trace.h>
+#include <lib/fixed_point.h>
+#include <kernel/thread.h>
+#include <kernel/spinlock.h>
+#include <platform.h>
+#include <platform/interrupts.h>
+#include <platform/timer.h>
+#include <lk/init.h>
+
+/* driver for cortex-a9's private timer */
+#define LOCAL_TRACE 0
+
+/* interrupts for cortex-a9 timer and watchdog */
+#define CPU_GLOB_TIMER_INT 27
+#define CPU_PRIV_TIMER_INT 29
+#define CPU_WATCHDOG_INT   30
+
+/* offsets into the per cpu private scu base */
+#define GLOBAL_TIMER_OFFSET (0x0200)
+#define PRIV_TIMER_OFFSET   (0x0600)
+
+#define TIMREG(reg)     (*REG32(scu_control_base + PRIV_TIMER_OFFSET + (reg)))
+
+#define TIMER_LOAD      (0x00)
+#define TIMER_COUNTER   (0x04)
+#define TIMER_CONTROL   (0x08)
+#define TIMER_ISR       (0x0c)
+#define WDOG_LOAD       (0x20)
+#define WDOG_COUNTER    (0x24)
+#define WDOG_CONTROL    (0x28)
+#define WDOG_ISR        (0x2c)
+
+#define GTIMREG(reg)    (*REG32(scu_control_base + GLOBAL_TIMER_OFFSET + (reg)))
+
+#define GTIMER_COUNT_LO (0x00)
+#define GTIMER_COUNT_HI (0x04)
+#define GTIMER_CONTROL  (0x08)
+#define GTIMER_ISR      (0x0c)
+#define GTIMER_COMPARE_LO (0x10)
+#define GTIMER_COMPARE_HI (0x14)
+#define GTIMER_INCREMENT (0x18)
+
+static platform_timer_callback t_callback;
+static addr_t scu_control_base;
+static spin_lock_t lock = SPIN_LOCK_INITIAL_VALUE;
+
+static lk_time_t periodic_interval;
+static lk_time_t oneshot_interval;
+static uint32_t timer_freq;
+static struct fp_32_64 timer_freq_msec_conversion;
+static struct fp_32_64 timer_freq_usec_conversion_inverse;
+static struct fp_32_64 timer_freq_msec_conversion_inverse;
+
+static void arm_cortex_a9_timer_init_percpu(uint level);
+
+uint64_t get_global_val(void)
+{
+    uint32_t lo, hi;
+
+retry:
+    hi = GTIMREG(GTIMER_COUNT_HI);
+    lo = GTIMREG(GTIMER_COUNT_LO);
+    if (GTIMREG(GTIMER_COUNT_HI) != hi)
+        goto retry;
+
+    return ((uint64_t)hi << 32 | lo);
+}
+
+lk_bigtime_t current_time_hires(void)
+{
+    lk_bigtime_t time;
+
+    time = u64_mul_u64_fp32_64(get_global_val(), timer_freq_usec_conversion_inverse);
+
+    return time;
+}
+
+lk_time_t current_time(void)
+{
+    lk_time_t time;
+
+    time = u32_mul_u64_fp32_64(get_global_val(), timer_freq_msec_conversion_inverse);
+
+    return time;
+}
+
+status_t platform_set_periodic_timer(platform_timer_callback callback, void *arg, lk_time_t interval)
+{
+    LTRACEF("callback %p, arg %p, interval %u\n", callback, arg, interval);
+
+    uint64_t ticks = u64_mul_u64_fp32_64(interval, timer_freq_msec_conversion);
+    if (unlikely(ticks == 0))
+        ticks = 1;
+    if (unlikely(ticks > 0xffffffff))
+        ticks = 0xffffffff;
+
+    spin_lock_saved_state_t state;
+    spin_lock_irqsave(&lock, state);
+
+    t_callback = callback;
+
+    periodic_interval = interval;
+
+    // disable timer
+    TIMREG(TIMER_CONTROL) = 0;
+
+    TIMREG(TIMER_LOAD) = ticks;
+    TIMREG(TIMER_CONTROL) = (1<<2) | (1<<1) | (1<<0); // irq enable, autoreload, enable
+
+    spin_unlock_irqrestore(&lock, state);
+
+    return NO_ERROR;
+}
+
+status_t platform_set_oneshot_timer (platform_timer_callback callback, void *arg, lk_time_t interval)
+{
+    LTRACEF("callback %p, arg %p, timeout %u\n", callback, arg, interval);
+
+    uint64_t ticks = u64_mul_u64_fp32_64(interval, timer_freq_msec_conversion);
+    if (unlikely(ticks == 0))
+        ticks = 1;
+    if (unlikely(ticks > 0xffffffff))
+        ticks = 0xffffffff;
+
+    spin_lock_saved_state_t state;
+    spin_lock_irqsave(&lock, state);
+
+    t_callback = callback;
+    oneshot_interval = interval;
+
+    // disable timer
+    TIMREG(TIMER_CONTROL) = 0;
+
+    TIMREG(TIMER_LOAD) = ticks;
+    TIMREG(TIMER_CONTROL) = (1<<2) | (1<<0) | (1<<0); // irq enable, oneshot, enable
+
+    spin_unlock_irqrestore(&lock, state);
+
+    return NO_ERROR;
+}
+
+void platform_stop_timer(void)
+{
+    LTRACE;
+
+    TIMREG(TIMER_CONTROL) = 0;
+}
+
+static enum handler_return platform_tick(void *arg)
+{
+    LTRACE;
+
+    TIMREG(TIMER_ISR) = 1; // ack the irq
+
+    if (t_callback) {
+        return t_callback(arg, current_time());
+    } else {
+        return INT_NO_RESCHEDULE;
+    }
+}
+
+void arm_cortex_a9_timer_init(addr_t _scu_control_base, uint32_t freq)
+{
+    scu_control_base = _scu_control_base;
+
+    arm_cortex_a9_timer_init_percpu(0);
+
+    /* save the timer frequency for later calculations */
+    timer_freq = freq;
+
+    /* precompute the conversion factor for global time to real time */
+    fp_32_64_div_32_32(&timer_freq_msec_conversion, timer_freq, 1000);
+    fp_32_64_div_32_32(&timer_freq_usec_conversion_inverse, 1000000, timer_freq);
+    fp_32_64_div_32_32(&timer_freq_msec_conversion_inverse, 1000, timer_freq);
+}
+
+static void arm_cortex_a9_timer_init_percpu(uint level)
+{
+    /* disable timer */
+    TIMREG(TIMER_CONTROL) = 0;
+
+    /* kill the watchdog */
+    TIMREG(WDOG_CONTROL) = 0;
+
+    /* ack any irqs that may be pending */
+    TIMREG(TIMER_ISR) = 1;
+
+    /* register the platform tick on each cpu */
+    register_int_handler(CPU_PRIV_TIMER_INT, &platform_tick, NULL);
+    unmask_interrupt(CPU_PRIV_TIMER_INT);
+}
+
+/* secondary cpu initialize the timer just before the kernel starts with interrupts enabled */
+LK_INIT_HOOK_FLAGS(arm_cortex_a9_timer_init_percpu,
+       arm_cortex_a9_timer_init_percpu,
+       LK_INIT_LEVEL_THREADING - 1, LK_INIT_FLAG_SECONDARY_CPUS);
+
+/* vim: set ts=4 sw=4 expandtab: */
diff --git a/src/bsp/lk/dev/timer/arm_cortex_a9/include/dev/timer/arm_cortex_a9.h b/src/bsp/lk/dev/timer/arm_cortex_a9/include/dev/timer/arm_cortex_a9.h
new file mode 100644
index 0000000..053e811
--- /dev/null
+++ b/src/bsp/lk/dev/timer/arm_cortex_a9/include/dev/timer/arm_cortex_a9.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2014 Travis Geiselbrecht
+ *
+ * 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
+
+#include <sys/types.h>
+
+void arm_cortex_a9_timer_init(addr_t scu_control_base, uint32_t freq);
diff --git a/src/bsp/lk/dev/timer/arm_cortex_a9/rules.mk b/src/bsp/lk/dev/timer/arm_cortex_a9/rules.mk
new file mode 100644
index 0000000..1345ddf
--- /dev/null
+++ b/src/bsp/lk/dev/timer/arm_cortex_a9/rules.mk
@@ -0,0 +1,14 @@
+LOCAL_DIR := $(GET_LOCAL_DIR)
+
+MODULE := $(LOCAL_DIR)
+
+GLOBAL_DEFINES += \
+	PLATFORM_HAS_DYNAMIC_TIMER=1
+
+MODULE_DEPS += \
+    lib/fixed_point
+
+MODULE_SRCS += \
+	$(LOCAL_DIR)/arm_cortex_a9_timer.c
+
+include make/module.mk
diff --git a/src/bsp/lk/dev/timer/arm_generic/arm_generic_timer.c b/src/bsp/lk/dev/timer/arm_generic/arm_generic_timer.c
new file mode 100644
index 0000000..c7b329e
--- /dev/null
+++ b/src/bsp/lk/dev/timer/arm_generic/arm_generic_timer.c
@@ -0,0 +1,378 @@
+/*
+ * Copyright (c) 2013, 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 <arch/ops.h>
+#include <assert.h>
+#include <lk/init.h>
+#include <platform.h>
+#include <platform/interrupts.h>
+#include <platform/timer.h>
+#include <trace.h>
+
+#define LOCAL_TRACE 0
+
+#include <lib/fixed_point.h>
+
+#if ARCH_ARM64
+
+/* CNTFRQ AArch64 register */
+#define TIMER_REG_CNTFRQ	cntfrq_el0
+
+/* CNTP AArch64 registers */
+#define TIMER_REG_CNTP_CTL	cntp_ctl_el0
+#define TIMER_REG_CNTP_CVAL	cntp_cval_el0
+#define TIMER_REG_CNTP_TVAL	cntp_tval_el0
+#define TIMER_REG_CNTPCT	cntpct_el0
+
+/* CNTPS AArch64 registers */
+#define TIMER_REG_CNTPS_CTL	cntps_ctl_el1
+#define TIMER_REG_CNTPS_CVAL	cntps_cval_el1
+#define TIMER_REG_CNTPS_TVAL	cntps_tval_el1
+#define TIMER_REG_CNTPSCT	cntpct_el0
+
+/* CNTV AArch64 registers */
+#define TIMER_REG_CNTV_CTL	cntv_ctl_el0
+#define TIMER_REG_CNTV_CVAL	cntv_cval_el0
+#define TIMER_REG_CNTV_TVAL	cntv_tval_el0
+#define TIMER_REG_CNTVCT	cntvct_el0
+
+#define READ_TIMER_REG32(reg) ARM64_READ_SYSREG(reg)
+#define READ_TIMER_REG64(reg) ARM64_READ_SYSREG(reg)
+#define WRITE_TIMER_REG32(reg, val) ARM64_WRITE_SYSREG(reg, (uint64_t)val)
+#define WRITE_TIMER_REG64(reg, val) ARM64_WRITE_SYSREG(reg, val)
+
+#else
+
+/* CNTFRQ AArch32 register */
+#define TIMER_REG_CNTFRQ	"c0, 0"
+
+/* CNTP AArch32 registers */
+#define TIMER_REG_CNTP_CTL	"c2, 1"
+#define TIMER_REG_CNTP_CVAL	"2"
+#define TIMER_REG_CNTP_TVAL	"c2, 0"
+#define TIMER_REG_CNTPCT	"0"
+
+/* CNTPS AArch32 registers are banked and accessed though CNTP */
+#define CNTPS CNTP
+
+/* CNTV AArch32 registers */
+#define TIMER_REG_CNTV_CTL	"c3, 1"
+#define TIMER_REG_CNTV_CVAL	"3"
+#define TIMER_REG_CNTV_TVAL	"c3, 0"
+#define TIMER_REG_CNTVCT	"1"
+
+#define READ_TIMER_REG32(reg) \
+({ \
+	uint32_t _val; \
+	__asm__ volatile("mrc p15, 0, %0, c14, " reg : "=r" (_val)); \
+	_val; \
+})
+
+#define READ_TIMER_REG64(reg) \
+({ \
+	uint64_t _val; \
+	__asm__ volatile("mrrc p15, " reg ", %0, %H0, c14" : "=r" (_val)); \
+	_val; \
+})
+
+#define WRITE_TIMER_REG32(reg, val) \
+({ \
+	__asm__ volatile("mcr p15, 0, %0, c14, " reg :: "r" (val)); \
+	ISB; \
+})
+
+#define WRITE_TIMER_REG64(reg, val) \
+({ \
+	__asm__ volatile("mcrr p15, " reg ", %0, %H0, c14" :: "r" (val)); \
+	ISB; \
+})
+
+#endif
+
+#ifndef TIMER_ARM_GENERIC_SELECTED
+#define TIMER_ARM_GENERIC_SELECTED CNTP
+#endif
+
+#define COMBINE3(a,b,c) a ## b ## c
+#define XCOMBINE3(a,b,c) COMBINE3(a, b, c)
+
+#define SELECTED_TIMER_REG(reg)	XCOMBINE3(TIMER_REG_, TIMER_ARM_GENERIC_SELECTED, reg)
+#define TIMER_REG_CTL		SELECTED_TIMER_REG(_CTL)
+#define TIMER_REG_CVAL		SELECTED_TIMER_REG(_CVAL)
+#define TIMER_REG_TVAL		SELECTED_TIMER_REG(_TVAL)
+#define TIMER_REG_CT		SELECTED_TIMER_REG(CT)
+
+
+static platform_timer_callback t_callback;
+static int timer_irq;
+
+struct fp_32_64 cntpct_per_ms;
+struct fp_32_64 ms_per_cntpct;
+struct fp_32_64 us_per_cntpct;
+
+static uint64_t lk_time_to_cntpct(lk_time_t lk_time)
+{
+	return u64_mul_u32_fp32_64(lk_time, cntpct_per_ms);
+}
+
+static lk_time_t cntpct_to_lk_time(uint64_t cntpct)
+{
+	return u32_mul_u64_fp32_64(cntpct, ms_per_cntpct);
+}
+
+static lk_bigtime_t cntpct_to_lk_bigtime(uint64_t cntpct)
+{
+	return u64_mul_u64_fp32_64(cntpct, us_per_cntpct);
+}
+
+static uint32_t read_cntfrq(void)
+{
+	uint32_t cntfrq;
+
+	cntfrq = READ_TIMER_REG32(TIMER_REG_CNTFRQ);
+	LTRACEF("cntfrq: 0x%08x, %u\n", cntfrq, cntfrq);
+	return cntfrq;
+}
+
+static uint32_t read_cntp_ctl(void)
+{
+	uint32_t cntp_ctl;
+
+	cntp_ctl = READ_TIMER_REG32(TIMER_REG_CTL);
+	return cntp_ctl;
+}
+
+static void write_cntp_ctl(uint32_t cntp_ctl)
+{
+	LTRACEF_LEVEL(3, "cntp_ctl: 0x%x %x\n", cntp_ctl, read_cntp_ctl());
+	WRITE_TIMER_REG32(TIMER_REG_CTL, cntp_ctl);
+}
+
+static void write_cntp_cval(uint64_t cntp_cval)
+{
+	LTRACEF_LEVEL(3, "cntp_cval: 0x%016llx, %llu\n", cntp_cval, cntp_cval);
+	WRITE_TIMER_REG64(TIMER_REG_CVAL, cntp_cval);
+}
+
+static void write_cntp_tval(int32_t cntp_tval)
+{
+	LTRACEF_LEVEL(3, "cntp_tval: 0x%08x, %d\n", cntp_tval, cntp_tval);
+	WRITE_TIMER_REG32(TIMER_REG_TVAL, cntp_tval);
+}
+
+static uint64_t read_cntpct(void)
+{
+	uint64_t cntpct;
+
+	cntpct = READ_TIMER_REG64(TIMER_REG_CT);
+	LTRACEF_LEVEL(3, "cntpct: 0x%016llx, %llu\n", cntpct, cntpct);
+	return cntpct;
+}
+
+static enum handler_return platform_tick(void *arg)
+{
+	write_cntp_ctl(0);
+	if (t_callback) {
+		return t_callback(arg, current_time());
+	} else {
+		return INT_NO_RESCHEDULE;
+	}
+}
+
+status_t platform_set_oneshot_timer(platform_timer_callback callback, void *arg, lk_time_t interval)
+{
+	uint64_t cntpct_interval = lk_time_to_cntpct(interval);
+
+	ASSERT(arg == NULL);
+
+	t_callback = callback;
+	if (cntpct_interval <= INT_MAX)
+		write_cntp_tval(cntpct_interval);
+	else
+		write_cntp_cval(read_cntpct() + cntpct_interval);
+	write_cntp_ctl(1);
+
+	return 0;
+}
+
+void platform_stop_timer(void)
+{
+	write_cntp_ctl(0);
+}
+
+lk_bigtime_t current_time_hires(void)
+{
+	return cntpct_to_lk_bigtime(read_cntpct());
+}
+
+lk_time_t current_time(void)
+{
+	return cntpct_to_lk_time(read_cntpct());
+}
+
+static uint32_t abs_int32(int32_t a)
+{
+	return (a > 0) ? a : -a;
+}
+
+static uint64_t abs_int64(int64_t a)
+{
+	return (a > 0) ? a : -a;
+}
+
+static void test_time_conversion_check_result(uint64_t a, uint64_t b, uint64_t limit, bool is32)
+{
+	if (a != b) {
+		uint64_t diff = is32 ? abs_int32(a - b) : abs_int64(a - b);
+		if (diff <= limit)
+			LTRACEF("ROUNDED by %llu (up to %llu allowed)\n", diff, limit);
+		else
+			TRACEF("FAIL, off by %llu\n", diff);
+	}
+}
+
+static void test_lk_time_to_cntpct(uint32_t cntfrq, lk_time_t lk_time)
+{
+	uint64_t cntpct = lk_time_to_cntpct(lk_time);
+	uint64_t expected_cntpct = ((uint64_t)cntfrq * lk_time + 500) / 1000;
+
+	test_time_conversion_check_result(cntpct, expected_cntpct, 1, false);
+	LTRACEF_LEVEL(2, "lk_time_to_cntpct(%u): got %llu, expect %llu\n", lk_time, cntpct, expected_cntpct);
+}
+
+static void test_cntpct_to_lk_time(uint32_t cntfrq, lk_time_t expected_lk_time, uint32_t wrap_count)
+{
+	lk_time_t lk_time;
+	uint64_t cntpct;
+
+	cntpct = (uint64_t)cntfrq * expected_lk_time / 1000;
+	if ((uint64_t)cntfrq * wrap_count > UINT_MAX)
+		cntpct += (((uint64_t)cntfrq << 32) / 1000) * wrap_count;
+	else
+		cntpct += (((uint64_t)(cntfrq * wrap_count) << 32) / 1000);
+	lk_time = cntpct_to_lk_time(cntpct);
+
+	test_time_conversion_check_result(lk_time, expected_lk_time, (1000 + cntfrq - 1) / cntfrq, true);
+	LTRACEF_LEVEL(2, "cntpct_to_lk_time(%llu): got %u, expect %u\n", cntpct, lk_time, expected_lk_time);
+}
+
+static void test_cntpct_to_lk_bigtime(uint32_t cntfrq, uint64_t expected_s)
+{
+	lk_bigtime_t expected_lk_bigtime = expected_s * 1000 * 1000;
+	uint64_t cntpct = (uint64_t)cntfrq * expected_s;
+	lk_bigtime_t lk_bigtime = cntpct_to_lk_bigtime(cntpct);
+
+	test_time_conversion_check_result(lk_bigtime, expected_lk_bigtime, (1000 * 1000 + cntfrq - 1) / cntfrq, false);
+	LTRACEF_LEVEL(2, "cntpct_to_lk_bigtime(%llu): got %llu, expect %llu\n", cntpct, lk_bigtime, expected_lk_bigtime);
+}
+
+static void test_time_conversions(uint32_t cntfrq)
+{
+	test_lk_time_to_cntpct(cntfrq, 0);
+	test_lk_time_to_cntpct(cntfrq, 1);
+	test_lk_time_to_cntpct(cntfrq, INT_MAX);
+	test_lk_time_to_cntpct(cntfrq, INT_MAX + 1U);
+	test_lk_time_to_cntpct(cntfrq, ~0);
+	test_cntpct_to_lk_time(cntfrq, 0, 0);
+	test_cntpct_to_lk_time(cntfrq, INT_MAX, 0);
+	test_cntpct_to_lk_time(cntfrq, INT_MAX + 1U, 0);
+	test_cntpct_to_lk_time(cntfrq, ~0, 0);
+	test_cntpct_to_lk_time(cntfrq, 0, 1);
+	test_cntpct_to_lk_time(cntfrq, 0, 7);
+	test_cntpct_to_lk_time(cntfrq, 0, 70);
+	test_cntpct_to_lk_time(cntfrq, 0, 700);
+	test_cntpct_to_lk_bigtime(cntfrq, 0);
+	test_cntpct_to_lk_bigtime(cntfrq, 1);
+	test_cntpct_to_lk_bigtime(cntfrq, 60 * 60 * 24);
+	test_cntpct_to_lk_bigtime(cntfrq, 60 * 60 * 24 * 365);
+	test_cntpct_to_lk_bigtime(cntfrq, 60 * 60 * 24 * (365 * 10 + 2));
+	test_cntpct_to_lk_bigtime(cntfrq, 60ULL * 60 * 24 * (365 * 100 + 2));
+}
+
+static void arm_generic_timer_init_conversion_factors(uint32_t cntfrq)
+{
+	fp_32_64_div_32_32(&cntpct_per_ms, cntfrq, 1000);
+	fp_32_64_div_32_32(&ms_per_cntpct, 1000, cntfrq);
+	fp_32_64_div_32_32(&us_per_cntpct, 1000 * 1000, cntfrq);
+	LTRACEF("cntpct_per_ms: %08x.%08x%08x\n", cntpct_per_ms.l0, cntpct_per_ms.l32, cntpct_per_ms.l64);
+	LTRACEF("ms_per_cntpct: %08x.%08x%08x\n", ms_per_cntpct.l0, ms_per_cntpct.l32, ms_per_cntpct.l64);
+	LTRACEF("us_per_cntpct: %08x.%08x%08x\n", us_per_cntpct.l0, us_per_cntpct.l32, us_per_cntpct.l64);
+}
+
+void arm_generic_timer_init(int irq, uint32_t freq_override)
+{
+	uint32_t cntfrq;
+
+	if (freq_override == 0) {
+		cntfrq = read_cntfrq();
+
+		if (!cntfrq) {
+			TRACEF("Failed to initialize timer, frequency is 0\n");
+			return;
+		}
+	} else {
+		cntfrq = freq_override;
+	}
+
+#if LOCAL_TRACE
+	LTRACEF("Test min cntfrq\n");
+	arm_generic_timer_init_conversion_factors(1);
+	test_time_conversions(1);
+	LTRACEF("Test max cntfrq\n");
+	arm_generic_timer_init_conversion_factors(~0);
+	test_time_conversions(~0);
+	LTRACEF("Set actual cntfrq\n");
+#endif
+	arm_generic_timer_init_conversion_factors(cntfrq);
+	test_time_conversions(cntfrq);
+
+	LTRACEF("register irq %d on cpu %d\n", irq, arch_curr_cpu_num());
+	register_int_handler(irq, &platform_tick, NULL);
+	unmask_interrupt(irq);
+
+	timer_irq = irq;
+}
+
+static void arm_generic_timer_init_secondary_cpu(uint level)
+{
+	LTRACEF("register irq %d on cpu %d\n", timer_irq, arch_curr_cpu_num());
+	register_int_handler(timer_irq, &platform_tick, NULL);
+	unmask_interrupt(timer_irq);
+}
+
+/* secondary cpu initialize the timer just before the kernel starts with interrupts enabled */
+LK_INIT_HOOK_FLAGS(arm_generic_timer_init_secondary_cpu,
+		   arm_generic_timer_init_secondary_cpu,
+		   LK_INIT_LEVEL_THREADING - 1, LK_INIT_FLAG_SECONDARY_CPUS);
+
+static void arm_generic_timer_resume_cpu(uint level)
+{
+	/* Always trigger a timer interrupt on each cpu for now */
+	write_cntp_tval(0);
+	write_cntp_ctl(1);
+}
+
+LK_INIT_HOOK_FLAGS(arm_generic_timer_resume_cpu, arm_generic_timer_resume_cpu,
+		LK_INIT_LEVEL_PLATFORM, LK_INIT_FLAG_CPU_RESUME);
+
+/* vim: set noexpandtab: */
diff --git a/src/bsp/lk/dev/timer/arm_generic/include/dev/timer/arm_generic.h b/src/bsp/lk/dev/timer/arm_generic/include/dev/timer/arm_generic.h
new file mode 100644
index 0000000..368d43c
--- /dev/null
+++ b/src/bsp/lk/dev/timer/arm_generic/include/dev/timer/arm_generic.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2013, 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.
+ */
+#ifndef __DEV_TIMER_ARM_GENERIC_H
+#define __DEV_TIMER_ARM_GENERIC_H
+
+#include <sys/types.h>
+
+/* if freq_override != 0, use that as the operating frequency instead of CNTFRQ register */
+void arm_generic_timer_init(int irq, uint32_t freq_override);
+
+#endif
+
diff --git a/src/bsp/lk/dev/timer/arm_generic/rules.mk b/src/bsp/lk/dev/timer/arm_generic/rules.mk
new file mode 100644
index 0000000..7331312
--- /dev/null
+++ b/src/bsp/lk/dev/timer/arm_generic/rules.mk
@@ -0,0 +1,14 @@
+LOCAL_DIR := $(GET_LOCAL_DIR)
+
+MODULE := $(LOCAL_DIR)
+
+GLOBAL_DEFINES += \
+	PLATFORM_HAS_DYNAMIC_TIMER=1
+
+MODULE_SRCS += \
+	$(LOCAL_DIR)/arm_generic_timer.c
+
+MODULE_DEPS += \
+	lib/fixed_point
+
+include make/module.mk
diff --git a/src/bsp/lk/dev/timer/or1k_ticktimer/include/dev/timer/or1k_ticktimer.h b/src/bsp/lk/dev/timer/or1k_ticktimer/include/dev/timer/or1k_ticktimer.h
new file mode 100644
index 0000000..55e51ae
--- /dev/null
+++ b/src/bsp/lk/dev/timer/or1k_ticktimer/include/dev/timer/or1k_ticktimer.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2015 Stefan Kristiansson
+ *
+ * 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
+
+#include <sys/types.h>
+
+void or1k_ticktimer_init(uint32_t freq);
diff --git a/src/bsp/lk/dev/timer/or1k_ticktimer/or1k_ticktimer.c b/src/bsp/lk/dev/timer/or1k_ticktimer/or1k_ticktimer.c
new file mode 100644
index 0000000..1dc64e6
--- /dev/null
+++ b/src/bsp/lk/dev/timer/or1k_ticktimer/or1k_ticktimer.c
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2015 Stefan Kristiansson
+ *
+ * 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 <trace.h>
+#include <err.h>
+#include <kernel/thread.h>
+#include <platform/timer.h>
+#include <platform/debug.h>
+#include <sys/types.h>
+#include <arch/or1k.h>
+
+#define LOCAL_TRACE 0
+
+static platform_timer_callback timer_cb;
+static void *timer_arg;
+
+static uint32_t timer_freq;
+
+static volatile uint64_t ticks = 0;
+
+status_t platform_set_periodic_timer(platform_timer_callback callback, void *arg, lk_time_t interval)
+{
+    LTRACEF("cb %p, arg %p, interval %ld\n", callback, arg, interval);
+
+    uint32_t ttmr = (uint64_t)timer_freq * interval / 1000;
+    LTRACEF("count 0x%x\n", ttmr);
+
+    timer_cb = callback;
+    timer_arg = arg;
+
+    /* disable timer before doing changes */
+    mtspr(OR1K_SPR_TICK_TTMR_ADDR, 0);
+    /* reset timer counter */
+    mtspr(OR1K_SPR_TICK_TTCR_ADDR, 0);
+    /* enable timer with given interval in 'restart' mode */
+    ttmr = OR1K_SPR_TICK_TTMR_MODE_SET(ttmr | OR1K_SPR_TICK_TTMR_IE_MASK,
+                                       OR1K_SPR_TICK_TTMR_MODE_RESTART);
+    mtspr(OR1K_SPR_TICK_TTMR_ADDR, ttmr);
+
+    return NO_ERROR;
+}
+
+lk_bigtime_t current_time_hires(void)
+{
+    return (lk_bigtime_t)ticks * 10000;
+}
+
+lk_time_t current_time(void)
+{
+    return (lk_time_t)ticks * 10;
+}
+
+enum handler_return platform_tick(void)
+{
+    ticks++;
+
+    /* clear pending interrupt flag */
+    mtspr(OR1K_SPR_TICK_TTMR_ADDR,
+          mfspr(OR1K_SPR_TICK_TTMR_ADDR) & ~(OR1K_SPR_TICK_TTMR_IP_MASK));
+
+    return timer_cb(timer_arg, ticks * 10);
+}
+
+void or1k_ticktimer_init(uint32_t freq)
+{
+    timer_freq = freq;
+    /* disable timer */
+    mtspr(OR1K_SPR_TICK_TTMR_ADDR, 0);
+    /* reset timer counter */
+    mtspr(OR1K_SPR_TICK_TTCR_ADDR, 0);
+    /* clear pending interrupt flag */
+    mtspr(OR1K_SPR_TICK_TTMR_ADDR,
+          mfspr(OR1K_SPR_TICK_TTMR_ADDR) & ~(OR1K_SPR_TICK_TTMR_IP_MASK));
+}
diff --git a/src/bsp/lk/dev/timer/or1k_ticktimer/rules.mk b/src/bsp/lk/dev/timer/or1k_ticktimer/rules.mk
new file mode 100644
index 0000000..5525f65
--- /dev/null
+++ b/src/bsp/lk/dev/timer/or1k_ticktimer/rules.mk
@@ -0,0 +1,11 @@
+LOCAL_DIR := $(GET_LOCAL_DIR)
+
+MODULE := $(LOCAL_DIR)
+
+#GLOBAL_DEFINES += \
+#	PLATFORM_HAS_DYNAMIC_TIMER=1
+
+MODULE_SRCS += \
+	$(LOCAL_DIR)/or1k_ticktimer.c
+
+include make/module.mk
diff --git a/src/bsp/lk/dev/usb/rules.mk b/src/bsp/lk/dev/usb/rules.mk
new file mode 100644
index 0000000..a66753d
--- /dev/null
+++ b/src/bsp/lk/dev/usb/rules.mk
@@ -0,0 +1,8 @@
+LOCAL_DIR := $(GET_LOCAL_DIR)
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_SRCS += \
+	$(LOCAL_DIR)/usb.c
+
+include make/module.mk
diff --git a/src/bsp/lk/dev/usb/usb.c b/src/bsp/lk/dev/usb/usb.c
new file mode 100644
index 0000000..5c8948a
--- /dev/null
+++ b/src/bsp/lk/dev/usb/usb.c
@@ -0,0 +1,395 @@
+/*
+ * Copyright (c) 2008-2015 Travis Geiselbrecht
+ *
+ * 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 <debug.h>
+#include <trace.h>
+#include <err.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <list.h>
+#include <dev/usbc.h>
+#include <dev/usb.h>
+#include <lk/init.h>
+
+#define LOCAL_TRACE 0
+
+#define MAX_STRINGS 8
+static struct {
+    bool active;
+    uint8_t active_config;
+
+    usb_config *config;
+
+    struct list_node cb_list;
+
+    usb_string strings[MAX_STRINGS];
+} usb;
+
+typedef struct {
+    struct list_node node;
+    usb_callback_t cb;
+    void *cookie;
+} usb_callback_container_t;
+
+static void usb_do_callbacks(usb_callback_op_t op, const union usb_callback_args *args);
+
+static void append_desc_data(usb_descriptor *desc, const void *dat, size_t len)
+{
+    uint8_t *ptr = malloc(desc->len + len);
+
+    memcpy(ptr, desc->desc, desc->len);
+    memcpy(ptr + desc->len, dat, len);
+
+    /* free the old buffer if it wasn't marked static */
+    if ((desc->flags & USB_DESC_FLAG_STATIC) == 0)
+        free(desc->desc);
+    desc->flags &= ~USB_DESC_FLAG_STATIC;
+
+    desc->desc = ptr;
+    desc->len += len;
+}
+
+/* returns the interface number assigned */
+static int usb_append_interface(usb_descriptor *desc, const uint8_t *int_descr, size_t len)
+{
+    uint8_t *ptr = malloc(len);
+    int interface_num;
+
+    // create a temporary copy of the interface
+    memcpy(ptr, int_descr, len);
+
+    // find the last interface used
+    interface_num = ((uint8_t *)desc->desc)[4]; // current interface
+
+    // patch our interface descriptor with the new id
+    ptr[2] = interface_num;
+
+    // append it to our config desriptor
+    append_desc_data(desc, ptr, len);
+    free(ptr);
+
+    // patch the total length of the config descriptor and set the number of interfaces
+    ((uint16_t *)desc->desc)[1] += len;
+    interface_num++;
+    ((uint8_t *)desc->desc)[4] = interface_num;
+
+    return interface_num - 1;
+}
+
+int usb_append_interface_highspeed(const uint8_t *int_descr, size_t len)
+{
+    return usb_append_interface(&usb.config->highspeed.config, int_descr, len);
+}
+
+int usb_append_interface_lowspeed(const uint8_t *int_descr, size_t len)
+{
+    return usb_append_interface(&usb.config->lowspeed.config, int_descr, len);
+}
+
+void usb_set_string_descriptor(usb_descriptor *desc, const char *string)
+{
+    int len = strlen(string);
+    ushort *data;
+    int datalen = len * 2 + 2;
+
+    data = malloc(datalen);
+
+    /* write length field */
+    data[0] = 0x0300 + datalen;
+
+    /* copy the string into the uint16_t based usb string */
+    int i;
+    for (i = 0; i < len; i++) {
+        data[i + 1] = string[i];
+    }
+
+    desc->desc = (void *)data;
+    desc->len = datalen;
+}
+
+static void set_usb_id(uint16_t vendor, uint16_t product)
+{
+    // patch the current configuration to with the vendor/product id
+    ((uint16_t *)usb.config->lowspeed.device.desc)[4] = vendor;
+    ((uint16_t *)usb.config->lowspeed.device.desc)[5] = product;
+
+    ((uint16_t *)usb.config->highspeed.device.desc)[4] = vendor;
+    ((uint16_t *)usb.config->highspeed.device.desc)[5] = product;
+}
+
+status_t usb_add_string(const char *string, uint8_t id)
+{
+    uint i;
+    size_t len = strlen(string);
+
+    uint16_t *strbuf = malloc(len * 2 + 2);
+    if (!strbuf)
+        return ERR_NO_MEMORY;
+
+    /* build the usb string descriptor */
+    strbuf[0] = 0x300 | (len * 2 + 2);
+    for (i = 0; i < len; i++) {
+        strbuf[i + 1] = (uint16_t)string[i];
+    }
+
+    /* find a slot to put it */
+    for (i = 0; i < MAX_STRINGS; i++) {
+        if (usb.strings[i].id == 0) {
+            usb.strings[i].string.desc = strbuf;
+            usb.strings[i].string.len = len * 2 + 2;
+            usb.strings[i].id = id;
+            return NO_ERROR;
+        }
+    }
+
+    /* couldn't find a spot */
+    free(strbuf);
+    return ERR_NO_MEMORY;
+}
+
+static void usb_set_active_config(uint8_t config)
+{
+    if (config != usb.active_config) {
+        usb.active_config = config;
+        if (usb.active_config != 0) {
+            printf("usb online\n");
+            usb_do_callbacks(USB_CB_ONLINE, NULL);
+        } else {
+            printf("usb offline\n");
+            usb_do_callbacks(USB_CB_OFFLINE, NULL);
+        }
+    }
+}
+
+status_t usb_register_callback(usb_callback_t cb, void *cookie)
+{
+    DEBUG_ASSERT(cb);
+
+    usb_callback_container_t *c = malloc(sizeof(usb_callback_container_t));
+    if (!c)
+        return ERR_NO_MEMORY;
+
+    c->cb = cb;
+    c->cookie = cookie;
+    list_add_tail(&usb.cb_list, &c->node);
+
+    return NO_ERROR;
+}
+
+static void usb_do_callbacks(usb_callback_op_t op, const union usb_callback_args *args)
+{
+    usb_callback_container_t *c;
+    list_for_every_entry(&usb.cb_list, c, usb_callback_container_t, node) {
+        c->cb(c->cookie, op, args);
+    }
+}
+
+status_t usbc_callback(usb_callback_op_t op, const union usb_callback_args *args)
+{
+    LTRACEF("op %d, args %p\n", op, args);
+
+    /* start looking for specific things to handle */
+    if (op == USB_CB_SETUP_MSG) {
+        bool setup_handled = false;
+        const struct usb_setup *setup = args->setup;
+        DEBUG_ASSERT(setup);
+        LTRACEF("SETUP: req_type=%#x req=%#x value=%#x index=%#x len=%#x\n",
+                setup->request_type, setup->request, setup->value, setup->index, setup->length);
+
+        if ((setup->request_type & TYPE_MASK) == TYPE_STANDARD) {
+            switch (setup->request) {
+                case SET_ADDRESS:
+                    LTRACEF("SET_ADDRESS 0x%x\n", setup->value);
+                    usbc_ep0_ack();
+                    usbc_set_address(setup->value);
+                    setup_handled = true;
+                    break;
+                case SET_FEATURE:
+                case CLEAR_FEATURE:
+                    LTRACEF("SET/CLEAR_FEATURE, feature 0x%x\n", setup->value);
+                    usbc_ep0_ack();
+                    setup_handled = true;
+                    break;
+                case SET_DESCRIPTOR:
+                    LTRACEF("SET_DESCRIPTOR\n");
+                    usbc_ep0_stall();
+                    setup_handled = true;
+                    break;
+                case GET_DESCRIPTOR: {
+                    if ((setup->request_type & RECIP_MASK) == RECIP_DEVICE) {
+                        /* handle device descriptor fetches */
+
+                        /* Get the right descriptors based on current speed */
+                        const struct usb_descriptor_speed *speed;
+                        if (usbc_is_highspeed()) {
+                            speed = &usb.config->highspeed;
+                        } else {
+                            speed = &usb.config->lowspeed;
+                        }
+
+                        switch (setup->value) {
+                            case 0x100: /* device */
+                                LTRACEF("got GET_DESCRIPTOR, device descriptor\n");
+                                usbc_ep0_send(speed->device.desc, speed->device.len,
+                                              setup->length);
+                                break;
+                            case 0x200:    /* CONFIGURATION */
+                                LTRACEF("got GET_DESCRIPTOR, config descriptor\n");
+                                usbc_ep0_send(speed->config.desc, speed->config.len,
+                                              setup->length);
+                                break;
+                            case 0x300:    /* Language ID */
+                                LTRACEF("got GET_DESCRIPTOR, language id\n");
+                                usbc_ep0_send(usb.config->langid.desc,
+                                              usb.config->langid.len, setup->length);
+                                break;
+                            case (0x301)...(0x3ff): {
+                                /* string descriptor, search our list for a match */
+                                uint i;
+                                bool found = false;
+                                uint8_t id = setup->value & 0xff;
+                                for (i = 0; i < MAX_STRINGS; i++) {
+                                    if (usb.strings[i].id == id) {
+                                        usbc_ep0_send(usb.strings[i].string.desc,
+                                                      usb.strings[i].string.len,
+                                                      setup->length);
+                                        found = true;
+                                        break;
+                                    }
+                                }
+                                if (!found) {
+                                    /* couldn't find one, stall */
+                                    usbc_ep0_stall();
+                                }
+                                break;
+                            }
+                            case 0x600:    /* DEVICE QUALIFIER */
+                                LTRACEF("got GET_DESCRIPTOR, device qualifier\n");
+                                usbc_ep0_send(speed->device_qual.desc,
+                                              speed->device_qual.len, setup->length);
+                                break;
+                            case 0xa00:
+                                /* we aint got one of these */
+                                LTRACEF("got GET_DESCRIPTOR, debug descriptor\n");
+                                usbc_ep0_stall();
+                                break;
+                            default:
+                                LTRACEF("unhandled descriptor %#x\n", setup->value);
+                                // stall
+                                break;
+                        }
+                        setup_handled = true;
+                    }
+                    break;
+                }
+
+                case SET_CONFIGURATION:
+                    LTRACEF("SET_CONFIGURATION %d\n", setup->value);
+                    usbc_ep0_ack();
+                    usb_set_active_config(setup->value);
+                    break;
+
+                case GET_CONFIGURATION:
+                    LTRACEF("GET_CONFIGURATION\n");
+                    usbc_ep0_send(&usb.active_config, 1, setup->length);
+                    break;
+
+                case SET_INTERFACE:
+                    LTRACEF("SET_INTERFACE %d\n", setup->value);
+                    usbc_ep0_ack();
+                    break;
+
+                case GET_INTERFACE: {
+                    static uint8_t i = 1;
+                    LTRACEF("GET_INTERFACE\n");
+                    usbc_ep0_send(&i, 1, setup->length);
+                    break;
+                }
+
+                case GET_STATUS: {
+                    static uint16_t i = 1; // self powered
+                    LTRACEF("GET_STATUS\n");
+                    usbc_ep0_send(&i, 2, setup->length);
+                    break;
+                }
+                default:
+                    LTRACEF("unhandled standard request 0x%x\n", setup->request);
+            }
+        } else {
+            LTRACEF("unhandled nonstandard request 0x%x\n", setup->request);
+        }
+
+        if (!setup_handled) {
+            usb_do_callbacks(op, args);
+        }
+    } else if (op == USB_CB_RESET) {
+        usb_do_callbacks(op, args);
+
+        usb.active_config = 0;
+        usb_do_callbacks(USB_CB_OFFLINE, args);
+    } else {
+        // other non setup messages, pass them down to anyone else
+        usb_do_callbacks(op, args);
+    }
+
+    return NO_ERROR;
+}
+
+status_t usb_setup(usb_config *config)
+{
+    DEBUG_ASSERT(config);
+    DEBUG_ASSERT(usb.active == false);
+
+    usb.config = config;
+
+    return NO_ERROR;
+}
+
+status_t usb_start(void)
+{
+    DEBUG_ASSERT(usb.config);
+    DEBUG_ASSERT(usb.active == false);
+
+    // go online
+    usbc_set_active(true);
+    usb.active = true;
+
+    return NO_ERROR;
+}
+
+status_t usb_stop(void)
+{
+    DEBUG_ASSERT(usb.active == true);
+
+    usb.active = false;
+    usbc_set_active(false);
+
+    return NO_ERROR;
+}
+
+static void usb_init(uint level)
+{
+    list_initialize(&usb.cb_list);
+}
+
+LK_INIT_HOOK(usb, usb_init, LK_INIT_LEVEL_THREADING);
diff --git a/src/bsp/lk/dev/virtio/block/include/dev/virtio/block.h b/src/bsp/lk/dev/virtio/block/include/dev/virtio/block.h
new file mode 100644
index 0000000..042121d
--- /dev/null
+++ b/src/bsp/lk/dev/virtio/block/include/dev/virtio/block.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2014 Travis Geiselbrecht
+ *
+ * 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
+
+#include <compiler.h>
+#include <sys/types.h>
+#include <dev/virtio.h>
+
+status_t virtio_block_init(struct virtio_device *dev, uint32_t host_features) __NONNULL();
+
+ssize_t virtio_block_read_write(struct virtio_device *dev, void *buf, off_t offset, size_t len, bool write) __NONNULL();
+
diff --git a/src/bsp/lk/dev/virtio/block/rules.mk b/src/bsp/lk/dev/virtio/block/rules.mk
new file mode 100644
index 0000000..f744bcb
--- /dev/null
+++ b/src/bsp/lk/dev/virtio/block/rules.mk
@@ -0,0 +1,13 @@
+LOCAL_DIR := $(GET_LOCAL_DIR)
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_SRCS += \
+	$(LOCAL_DIR)/virtio-block.c \
+
+MODULE_DEPS += \
+	dev/virtio \
+	lib/bio
+
+
+include make/module.mk
diff --git a/src/bsp/lk/dev/virtio/block/virtio-block.c b/src/bsp/lk/dev/virtio/block/virtio-block.c
new file mode 100644
index 0000000..61db486
--- /dev/null
+++ b/src/bsp/lk/dev/virtio/block/virtio-block.c
@@ -0,0 +1,340 @@
+/*
+ * Copyright (c) 2014-2015 Travis Geiselbrecht
+ *
+ * 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 <dev/virtio/block.h>
+
+#include <debug.h>
+#include <assert.h>
+#include <trace.h>
+#include <compiler.h>
+#include <list.h>
+#include <err.h>
+#include <kernel/thread.h>
+#include <kernel/event.h>
+#include <kernel/mutex.h>
+#include <kernel/vm.h>
+#include <lib/bio.h>
+
+#define LOCAL_TRACE 0
+
+struct virtio_blk_config {
+    uint64_t capacity;
+    uint32_t size_max;
+    uint32_t seg_max;
+    struct virtio_blk_geometry {
+        uint16_t cylinders;
+        uint8_t heads;
+        uint8_t sectors;
+    } geometry;
+    uint32_t blk_size;
+} __PACKED;
+
+struct virtio_blk_req {
+    uint32_t type;
+    uint32_t ioprio;
+    uint64_t sector;
+} __PACKED;
+
+#define VIRTIO_BLK_F_BARRIER  (1<<0)
+#define VIRTIO_BLK_F_SIZE_MAX (1<<1)
+#define VIRTIO_BLK_F_SEG_MAX  (1<<2)
+#define VIRTIO_BLK_F_GEOMETRY (1<<4)
+#define VIRTIO_BLK_F_RO       (1<<5)
+#define VIRTIO_BLK_F_BLK_SIZE (1<<6)
+#define VIRTIO_BLK_F_SCSI     (1<<7)
+#define VIRTIO_BLK_F_FLUSH    (1<<9)
+#define VIRTIO_BLK_F_TOPOLOGY (1<<10)
+#define VIRTIO_BLK_F_CONFIG_WCE (1<<11)
+
+#define VIRTIO_BLK_T_IN         0
+#define VIRTIO_BLK_T_OUT        1
+#define VIRTIO_BLK_T_FLUSH      4
+
+#define VIRTIO_BLK_S_OK         0
+#define VIRTIO_BLK_S_IOERR      1
+#define VIRTIO_BLK_S_UNSUPP     2
+
+static enum handler_return virtio_block_irq_driver_callback(struct virtio_device *dev, uint ring, const struct vring_used_elem *e);
+static ssize_t virtio_bdev_read_block(struct bdev *bdev, void *buf, bnum_t block, uint count);
+static ssize_t virtio_bdev_write_block(struct bdev *bdev, const void *buf, bnum_t block, uint count);
+
+struct virtio_block_dev {
+    struct virtio_device *dev;
+
+    mutex_t lock;
+    event_t io_event;
+
+    /* bio block device */
+    bdev_t bdev;
+
+    /* one blk_req structure for io, not crossing a page boundary */
+    struct virtio_blk_req *blk_req;
+    paddr_t blk_req_phys;
+
+    /* one uint8_t response word */
+    uint8_t blk_response;
+    paddr_t blk_response_phys;
+};
+
+status_t virtio_block_init(struct virtio_device *dev, uint32_t host_features)
+{
+    LTRACEF("dev %p, host_features 0x%x\n", dev, host_features);
+
+    /* allocate a new block device */
+    struct virtio_block_dev *bdev = malloc(sizeof(struct virtio_block_dev));
+    if (!bdev)
+        return ERR_NO_MEMORY;
+
+    mutex_init(&bdev->lock);
+    event_init(&bdev->io_event, false, EVENT_FLAG_AUTOUNSIGNAL);
+
+    bdev->dev = dev;
+    dev->priv = bdev;
+
+    bdev->blk_req = memalign(sizeof(struct virtio_blk_req), sizeof(struct virtio_blk_req));
+#if WITH_KERNEL_VM
+    arch_mmu_query((vaddr_t)bdev->blk_req, &bdev->blk_req_phys, NULL);
+#else
+    bdev->blk_freq_phys = (uint64_t)(uintptr_t)bdev->blk_req;
+#endif
+    LTRACEF("blk_req structure at %p (0x%lx phys)\n", bdev->blk_req, bdev->blk_req_phys);
+
+#if WITH_KERNEL_VM
+    arch_mmu_query((vaddr_t)&bdev->blk_response, &bdev->blk_response_phys, NULL);
+#else
+    bdev->blk_response_phys = (uint64_t)(uintptr_t)&bdev->blk_response;
+#endif
+
+    /* make sure the device is reset */
+    virtio_reset_device(dev);
+
+    volatile struct virtio_blk_config *config = (struct virtio_blk_config *)dev->config_ptr;
+
+    LTRACEF("capacity 0x%llx\n", config->capacity);
+    LTRACEF("size_max 0x%x\n", config->size_max);
+    LTRACEF("seg_max  0x%x\n", config->seg_max);
+    LTRACEF("blk_size 0x%x\n", config->blk_size);
+
+    /* ack and set the driver status bit */
+    virtio_status_acknowledge_driver(dev);
+
+    // XXX check features bits and ack/nak them
+
+    /* allocate a virtio ring */
+    virtio_alloc_ring(dev, 0, 256);
+
+    /* set our irq handler */
+    dev->irq_driver_callback = &virtio_block_irq_driver_callback;
+
+    /* set DRIVER_OK */
+    virtio_status_driver_ok(dev);
+
+    /* construct the block device */
+    static uint8_t found_index = 0;
+    char buf[16];
+    snprintf(buf, sizeof(buf), "virtio%u", found_index++);
+    bio_initialize_bdev(&bdev->bdev, buf,
+                        config->blk_size, config->capacity,
+                        0, NULL, BIO_FLAGS_NONE);
+
+    /* override our block device hooks */
+    bdev->bdev.read_block = &virtio_bdev_read_block;
+    bdev->bdev.write_block = &virtio_bdev_write_block;
+
+    bio_register_device(&bdev->bdev);
+
+    printf("found virtio block device of size %lld\n", config->capacity * config->blk_size);
+
+    return NO_ERROR;
+}
+
+static enum handler_return virtio_block_irq_driver_callback(struct virtio_device *dev, uint ring, const struct vring_used_elem *e)
+{
+    struct virtio_block_dev *bdev = (struct virtio_block_dev *)dev->priv;
+
+    LTRACEF("dev %p, ring %u, e %p, id %u, len %u\n", dev, ring, e, e->id, e->len);
+
+    /* parse our descriptor chain, add back to the free queue */
+    uint16_t i = e->id;
+    for (;;) {
+        int next;
+        struct vring_desc *desc = virtio_desc_index_to_desc(dev, ring, i);
+
+        //virtio_dump_desc(desc);
+
+        if (desc->flags & VRING_DESC_F_NEXT) {
+            next = desc->next;
+        } else {
+            /* end of chain */
+            next = -1;
+        }
+
+        virtio_free_desc(dev, ring, i);
+
+        if (next < 0)
+            break;
+        i = next;
+    }
+
+    /* signal our event */
+    event_signal(&bdev->io_event, false);
+
+    return INT_RESCHEDULE;
+}
+
+ssize_t virtio_block_read_write(struct virtio_device *dev, void *buf, off_t offset, size_t len, bool write)
+{
+    struct virtio_block_dev *bdev = (struct virtio_block_dev *)dev->priv;
+
+    uint16_t i;
+    struct vring_desc *desc;
+    paddr_t pa;
+    vaddr_t va = (vaddr_t)buf;
+
+    LTRACEF("dev %p, buf %p, offset 0x%llx, len %zu\n", dev, buf, offset, len);
+
+    mutex_acquire(&bdev->lock);
+
+    /* set up the request */
+    bdev->blk_req->type = write ? VIRTIO_BLK_T_OUT : VIRTIO_BLK_T_IN;
+    bdev->blk_req->ioprio = 0;
+    bdev->blk_req->sector = offset / 512;
+    LTRACEF("blk_req type %u ioprio %u sector %llu\n",
+            bdev->blk_req->type, bdev->blk_req->ioprio, bdev->blk_req->sector);
+
+    /* put together a transfer */
+    desc = virtio_alloc_desc_chain(dev, 0, 3, &i);
+    LTRACEF("after alloc chain desc %p, i %u\n", desc, i);
+
+    // XXX not cache safe.
+    // At the moment only tested on arm qemu, which doesn't emulate cache.
+
+    /* set up the descriptor pointing to the head */
+    desc->addr = bdev->blk_req_phys;
+    desc->len = sizeof(struct virtio_blk_req);
+    desc->flags |= VRING_DESC_F_NEXT;
+
+    /* set up the descriptor pointing to the buffer */
+    desc = virtio_desc_index_to_desc(dev, 0, desc->next);
+#if WITH_KERNEL_VM
+    /* translate the first buffer */
+    arch_mmu_query(va, &pa, NULL);
+    desc->addr = (uint64_t)pa;
+    /* desc->len is filled in below */
+#else
+    desc->addr = (uint64_t)(uintptr_t)buf;
+    desc->len = len;
+#endif
+    desc->flags |= write ? 0 : VRING_DESC_F_WRITE; /* mark buffer as write-only if its a block read */
+    desc->flags |= VRING_DESC_F_NEXT;
+
+#if WITH_KERNEL_VM
+    /* see if we need to add more descriptors due to scatter gather */
+    paddr_t next_pa = PAGE_ALIGN(pa + 1);
+    desc->len = MIN(next_pa - pa, len);
+    LTRACEF("first descriptor va 0x%lx desc->addr 0x%llx desc->len %u\n", va, desc->addr, desc->len);
+    len -= desc->len;
+    while (len > 0) {
+        /* amount of source buffer handled by this iteration of the loop */
+        size_t len_tohandle = MIN(len, PAGE_SIZE);
+
+        /* translate the next page in the buffer */
+        va = PAGE_ALIGN(va + 1);
+        arch_mmu_query(va, &pa, NULL);
+        LTRACEF("va now 0x%lx, pa 0x%lx, next_pa 0x%lx, remaining len %zu\n", va, pa, next_pa, len);
+
+        /* is the new translated physical address contiguous to the last one? */
+        if (next_pa == pa) {
+            LTRACEF("extending last one by %zu bytes\n", len_tohandle);
+            desc->len += len_tohandle;
+        } else {
+            uint16_t next_i = virtio_alloc_desc(dev, 0);
+            struct vring_desc *next_desc = virtio_desc_index_to_desc(dev, 0, next_i);
+            DEBUG_ASSERT(next_desc);
+
+            LTRACEF("doesn't extend, need new desc, allocated desc %i (%p)\n", next_i, next_desc);
+
+            /* fill this descriptor in and put it after the last one but before the response descriptor */
+            next_desc->addr = (uint64_t)pa;
+            next_desc->len = len_tohandle;
+            next_desc->flags = write ? 0 : VRING_DESC_F_WRITE; /* mark buffer as write-only if its a block read */
+            next_desc->flags |= VRING_DESC_F_NEXT;
+            next_desc->next = desc->next;
+            desc->next = next_i;
+
+            desc = next_desc;
+        }
+        len -= len_tohandle;
+        next_pa += PAGE_SIZE;
+    }
+#endif
+
+    /* set up the descriptor pointing to the response */
+    desc = virtio_desc_index_to_desc(dev, 0, desc->next);
+    desc->addr = bdev->blk_response_phys;
+    desc->len = 1;
+    desc->flags = VRING_DESC_F_WRITE;
+
+    /* submit the transfer */
+    virtio_submit_chain(dev, 0, i);
+
+    /* kick it off */
+    virtio_kick(dev, 0);
+
+    /* wait for the transfer to complete */
+    event_wait(&bdev->io_event);
+
+    LTRACEF("status 0x%hhx\n", bdev->blk_response);
+
+    mutex_release(&bdev->lock);
+
+    return len;
+}
+
+static ssize_t virtio_bdev_read_block(struct bdev *bdev, void *buf, bnum_t block, uint count)
+{
+    struct virtio_block_dev *dev = containerof(bdev, struct virtio_block_dev, bdev);
+
+    LTRACEF("dev %p, buf %p, block 0x%x, count %u\n", bdev, buf, block, count);
+
+    if (virtio_block_read_write(dev->dev, buf, (off_t)block * dev->bdev.block_size,
+                                count * dev->bdev.block_size, false) == 0) {
+        return count * dev->bdev.block_size;
+    } else {
+        return ERR_IO;
+    }
+}
+
+static ssize_t virtio_bdev_write_block(struct bdev *bdev, const void *buf, bnum_t block, uint count)
+{
+    struct virtio_block_dev *dev = containerof(bdev, struct virtio_block_dev, bdev);
+
+    LTRACEF("dev %p, buf %p, block 0x%x, count %u\n", bdev, buf, block, count);
+
+    if (virtio_block_read_write(dev->dev, (void *)buf, (off_t)block * dev->bdev.block_size,
+                                count * dev->bdev.block_size, true) == 0) {
+        return count * dev->bdev.block_size;
+    } else {
+        return ERR_IO;
+    }
+}
+
diff --git a/src/bsp/lk/dev/virtio/gpu/include/dev/virtio/gpu.h b/src/bsp/lk/dev/virtio/gpu/include/dev/virtio/gpu.h
new file mode 100644
index 0000000..2300697
--- /dev/null
+++ b/src/bsp/lk/dev/virtio/gpu/include/dev/virtio/gpu.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2015 Travis Geiselbrecht
+ *
+ * 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
+
+#include <compiler.h>
+#include <sys/types.h>
+#include <dev/virtio.h>
+
+status_t virtio_gpu_init(struct virtio_device *dev, uint32_t host_features) __NONNULL();
+
+status_t virtio_gpu_start(struct virtio_device *dev) __NONNULL();
+
diff --git a/src/bsp/lk/dev/virtio/gpu/rules.mk b/src/bsp/lk/dev/virtio/gpu/rules.mk
new file mode 100644
index 0000000..8210208
--- /dev/null
+++ b/src/bsp/lk/dev/virtio/gpu/rules.mk
@@ -0,0 +1,12 @@
+LOCAL_DIR := $(GET_LOCAL_DIR)
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_SRCS += \
+	$(LOCAL_DIR)/virtio-gpu.c \
+
+MODULE_DEPS += \
+	dev/virtio \
+	lib/gfx
+
+include make/module.mk
diff --git a/src/bsp/lk/dev/virtio/gpu/virtio-gpu.c b/src/bsp/lk/dev/virtio/gpu/virtio-gpu.c
new file mode 100644
index 0000000..d9b24c7
--- /dev/null
+++ b/src/bsp/lk/dev/virtio/gpu/virtio-gpu.c
@@ -0,0 +1,577 @@
+/*
+ * Copyright (c) 2014-2015 Travis Geiselbrecht
+ *
+ * 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 <dev/virtio/gpu.h>
+
+#include <debug.h>
+#include <assert.h>
+#include <trace.h>
+#include <compiler.h>
+#include <list.h>
+#include <err.h>
+#include <string.h>
+#include <kernel/thread.h>
+#include <kernel/event.h>
+#include <kernel/mutex.h>
+#include <kernel/vm.h>
+#include <lib/gfx.h>
+#include <dev/display.h>
+
+#include "virtio_gpu.h"
+
+#define LOCAL_TRACE 0
+
+static enum handler_return virtio_gpu_irq_driver_callback(struct virtio_device *dev, uint ring, const struct vring_used_elem *e);
+static enum handler_return virtio_gpu_config_change_callback(struct virtio_device *dev);
+static int virtio_gpu_flush_thread(void *arg);
+
+struct virtio_gpu_dev {
+    struct virtio_device *dev;
+
+    mutex_t lock;
+    event_t io_event;
+
+    void *gpu_request;
+    paddr_t gpu_request_phys;
+
+    /* a saved copy of the display */
+    struct virtio_gpu_display_one pmode;
+    int pmode_id;
+
+    /* resource id that is set as scanout */
+    uint32_t display_resource_id;
+
+    /* next resource id */
+    uint32_t next_resource_id;
+
+    event_t flush_event;
+
+    /* framebuffer */
+    void *fb;
+};
+
+static struct virtio_gpu_dev *the_gdev;
+
+static status_t send_command_response(struct virtio_gpu_dev *gdev, const void *cmd, size_t cmd_len, void **_res, size_t res_len)
+{
+    DEBUG_ASSERT(gdev);
+    DEBUG_ASSERT(cmd);
+    DEBUG_ASSERT(_res);
+    DEBUG_ASSERT(cmd_len + res_len < PAGE_SIZE);
+
+    LTRACEF("gdev %p, cmd %p, cmd_len %zu, res %p, res_len %zu\n", gdev, cmd, cmd_len, _res, res_len);
+
+    uint16_t i;
+    struct vring_desc *desc = virtio_alloc_desc_chain(gdev->dev, 0, 2, &i);
+    DEBUG_ASSERT(desc);
+
+    memcpy(gdev->gpu_request, cmd, cmd_len);
+
+    desc->addr = gdev->gpu_request_phys;
+    desc->len = cmd_len;
+    desc->flags |= VRING_DESC_F_NEXT;
+
+    /* set the second descriptor to the response with the write bit set */
+    desc = virtio_desc_index_to_desc(gdev->dev, 0, desc->next);
+    DEBUG_ASSERT(desc);
+
+    void *res = (void *)((uint8_t *)gdev->gpu_request + cmd_len);
+    *_res = res;
+    paddr_t res_phys = gdev->gpu_request_phys + cmd_len;
+    memset(res, 0, res_len);
+
+    desc->addr = res_phys;
+    desc->len = res_len;
+    desc->flags = VRING_DESC_F_WRITE;
+
+    /* submit the transfer */
+    virtio_submit_chain(gdev->dev, 0, i);
+
+    /* kick it off */
+    virtio_kick(gdev->dev, 0);
+
+    /* wait for result */
+    event_wait(&gdev->io_event);
+
+    return NO_ERROR;
+}
+
+static status_t get_display_info(struct virtio_gpu_dev *gdev)
+{
+    status_t err;
+
+    LTRACEF("gdev %p\n", gdev);
+
+    DEBUG_ASSERT(gdev);
+
+    /* grab a lock to keep this single message at a time */
+    mutex_acquire(&gdev->lock);
+
+    /* construct the get display info message */
+    struct virtio_gpu_ctrl_hdr req;
+    memset(&req, 0, sizeof(req));
+    req.type = VIRTIO_GPU_CMD_GET_DISPLAY_INFO;
+
+    /* send the message and get a response */
+    struct virtio_gpu_resp_display_info *info;
+    err = send_command_response(gdev, &req, sizeof(req), (void **)&info, sizeof(*info));
+    DEBUG_ASSERT(err == NO_ERROR);
+    if (err < NO_ERROR) {
+        mutex_release(&gdev->lock);
+        return ERR_NOT_FOUND;
+    }
+
+    /* we got response */
+    if (info->hdr.type != VIRTIO_GPU_RESP_OK_DISPLAY_INFO) {
+        mutex_release(&gdev->lock);
+        return ERR_NOT_FOUND;
+    }
+
+    LTRACEF("response:\n");
+    for (uint i = 0; i < VIRTIO_GPU_MAX_SCANOUTS; i++) {
+        if (info->pmodes[i].enabled) {
+            LTRACEF("%u: x %u y %u w %u h %u flags 0x%x\n", i,
+                    info->pmodes[i].r.x, info->pmodes[i].r.y, info->pmodes[i].r.width, info->pmodes[i].r.height,
+                    info->pmodes[i].flags);
+            if (gdev->pmode_id < 0) {
+                /* save the first valid pmode we see */
+                memcpy(&gdev->pmode, &info->pmodes[i], sizeof(gdev->pmode));
+                gdev->pmode_id = i;
+            }
+        }
+    }
+
+    /* release the lock */
+    mutex_release(&gdev->lock);
+
+    return NO_ERROR;
+}
+
+static status_t allocate_2d_resource(struct virtio_gpu_dev *gdev, uint32_t *resource_id, uint32_t width, uint32_t height)
+{
+    status_t err;
+
+    LTRACEF("gdev %p\n", gdev);
+
+    DEBUG_ASSERT(gdev);
+    DEBUG_ASSERT(resource_id);
+
+    /* grab a lock to keep this single message at a time */
+    mutex_acquire(&gdev->lock);
+
+    /* construct the request */
+    struct virtio_gpu_resource_create_2d req;
+    memset(&req, 0, sizeof(req));
+
+    req.hdr.type = VIRTIO_GPU_CMD_RESOURCE_CREATE_2D;
+    req.resource_id = gdev->next_resource_id++;
+    *resource_id = req.resource_id;
+    req.format = VIRTIO_GPU_FORMAT_B8G8R8X8_UNORM;
+    req.width = width;
+    req.height = height;
+
+    /* send the command and get a response */
+    struct virtio_gpu_ctrl_hdr *res;
+    err = send_command_response(gdev, &req, sizeof(req), (void **)&res, sizeof(*res));
+    DEBUG_ASSERT(err == NO_ERROR);
+
+    /* see if we got a valid response */
+    LTRACEF("response type 0x%x\n", res->type);
+    err = (res->type == VIRTIO_GPU_RESP_OK_NODATA) ? NO_ERROR : ERR_NO_MEMORY;
+
+    /* release the lock */
+    mutex_release(&gdev->lock);
+
+    return err;
+}
+
+static status_t attach_backing(struct virtio_gpu_dev *gdev, uint32_t resource_id, void *ptr, size_t buf_len)
+{
+    status_t err;
+
+    LTRACEF("gdev %p, resource_id %u, ptr %p, buf_len %zu\n", gdev, resource_id, ptr, buf_len);
+
+    DEBUG_ASSERT(gdev);
+    DEBUG_ASSERT(ptr);
+
+    /* grab a lock to keep this single message at a time */
+    mutex_acquire(&gdev->lock);
+
+    /* construct the request */
+    struct {
+        struct virtio_gpu_resource_attach_backing req;
+        struct virtio_gpu_mem_entry mem;
+    } req;
+    memset(&req, 0, sizeof(req));
+
+    req.req.hdr.type = VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING;
+    req.req.resource_id = resource_id;
+    req.req.nr_entries = 1;
+
+    paddr_t pa;
+    arch_mmu_query((vaddr_t)ptr, &pa, NULL);
+    req.mem.addr = pa;
+    req.mem.length = buf_len;
+
+    /* send the command and get a response */
+    struct virtio_gpu_ctrl_hdr *res;
+    err = send_command_response(gdev, &req, sizeof(req), (void **)&res, sizeof(*res));
+    DEBUG_ASSERT(err == NO_ERROR);
+
+    /* see if we got a valid response */
+    LTRACEF("response type 0x%x\n", res->type);
+    err = (res->type == VIRTIO_GPU_RESP_OK_NODATA) ? NO_ERROR : ERR_NO_MEMORY;
+
+    /* release the lock */
+    mutex_release(&gdev->lock);
+
+    return err;
+}
+
+static status_t set_scanout(struct virtio_gpu_dev *gdev, uint32_t scanout_id, uint32_t resource_id, uint32_t width, uint32_t height)
+{
+    status_t err;
+
+    LTRACEF("gdev %p, scanout_id %u, resource_id %u, width %u, height %u\n", gdev, scanout_id, resource_id, width, height);
+
+    /* grab a lock to keep this single message at a time */
+    mutex_acquire(&gdev->lock);
+
+    /* construct the request */
+    struct virtio_gpu_set_scanout req;
+    memset(&req, 0, sizeof(req));
+
+    req.hdr.type = VIRTIO_GPU_CMD_SET_SCANOUT;
+    req.r.x = req.r.y = 0;
+    req.r.width = width;
+    req.r.height = height;
+    req.scanout_id = scanout_id;
+    req.resource_id = resource_id;
+
+    /* send the command and get a response */
+    struct virtio_gpu_ctrl_hdr *res;
+    err = send_command_response(gdev, &req, sizeof(req), (void **)&res, sizeof(*res));
+    DEBUG_ASSERT(err == NO_ERROR);
+
+    /* see if we got a valid response */
+    LTRACEF("response type 0x%x\n", res->type);
+    err = (res->type == VIRTIO_GPU_RESP_OK_NODATA) ? NO_ERROR : ERR_NO_MEMORY;
+
+    /* release the lock */
+    mutex_release(&gdev->lock);
+
+    return err;
+}
+
+static status_t flush_resource(struct virtio_gpu_dev *gdev, uint32_t resource_id, uint32_t width, uint32_t height)
+{
+    status_t err;
+
+    LTRACEF("gdev %p, resource_id %u, width %u, height %u\n", gdev, resource_id, width, height);
+
+    /* grab a lock to keep this single message at a time */
+    mutex_acquire(&gdev->lock);
+
+    /* construct the request */
+    struct virtio_gpu_resource_flush req;
+    memset(&req, 0, sizeof(req));
+
+    req.hdr.type = VIRTIO_GPU_CMD_RESOURCE_FLUSH;
+    req.r.x = req.r.y = 0;
+    req.r.width = width;
+    req.r.height = height;
+    req.resource_id = resource_id;
+
+    /* send the command and get a response */
+    struct virtio_gpu_ctrl_hdr *res;
+    err = send_command_response(gdev, &req, sizeof(req), (void **)&res, sizeof(*res));
+    DEBUG_ASSERT(err == NO_ERROR);
+
+    /* see if we got a valid response */
+    LTRACEF("response type 0x%x\n", res->type);
+    err = (res->type == VIRTIO_GPU_RESP_OK_NODATA) ? NO_ERROR : ERR_NO_MEMORY;
+
+    /* release the lock */
+    mutex_release(&gdev->lock);
+
+    return err;
+}
+
+static status_t transfer_to_host_2d(struct virtio_gpu_dev *gdev, uint32_t resource_id, uint32_t width, uint32_t height)
+{
+    status_t err;
+
+    LTRACEF("gdev %p, resource_id %u, width %u, height %u\n", gdev, resource_id, width, height);
+
+    /* grab a lock to keep this single message at a time */
+    mutex_acquire(&gdev->lock);
+
+    /* construct the request */
+    struct virtio_gpu_transfer_to_host_2d req;
+    memset(&req, 0, sizeof(req));
+
+    req.hdr.type = VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D;
+    req.r.x = req.r.y = 0;
+    req.r.width = width;
+    req.r.height = height;
+    req.offset = 0;
+    req.resource_id = resource_id;
+
+    /* send the command and get a response */
+    struct virtio_gpu_ctrl_hdr *res;
+    err = send_command_response(gdev, &req, sizeof(req), (void **)&res, sizeof(*res));
+    DEBUG_ASSERT(err == NO_ERROR);
+
+    /* see if we got a valid response */
+    LTRACEF("response type 0x%x\n", res->type);
+    err = (res->type == VIRTIO_GPU_RESP_OK_NODATA) ? NO_ERROR : ERR_NO_MEMORY;
+
+    /* release the lock */
+    mutex_release(&gdev->lock);
+
+    return err;
+}
+
+status_t virtio_gpu_start(struct virtio_device *dev)
+{
+    status_t err;
+
+    LTRACEF("dev %p\n", dev);
+
+    struct virtio_gpu_dev *gdev = (struct virtio_gpu_dev *)dev->priv;
+
+    /* get the display info and see if we find a valid pmode */
+    err = get_display_info(gdev);
+    if (err < 0) {
+        LTRACEF("failed to get display info\n");
+        return err;
+    }
+
+    if (gdev->pmode_id < 0) {
+        LTRACEF("we failed to find a pmode, exiting\n");
+        return ERR_NOT_FOUND;
+    }
+
+    /* allocate a resource */
+    err = allocate_2d_resource(gdev, &gdev->display_resource_id, gdev->pmode.r.width, gdev->pmode.r.height);
+    if (err < 0) {
+        LTRACEF("failed to allocate 2d resource\n");
+        return err;
+    }
+
+    /* attach a backing store to the resource */
+    size_t len = gdev->pmode.r.width * gdev->pmode.r.height * 4;
+    gdev->fb = pmm_alloc_kpages(ROUNDUP(len, PAGE_SIZE) / PAGE_SIZE, NULL);
+    if (!gdev->fb) {
+        TRACEF("failed to allocate framebuffer, wanted 0x%zx bytes\n", len);
+        return ERR_NO_MEMORY;
+    }
+
+    printf("virtio-gpu: framebuffer at %p, 0x%zx bytes\n", gdev->fb, len);
+
+    err = attach_backing(gdev, gdev->display_resource_id, gdev->fb, len);
+    if (err < 0) {
+        LTRACEF("failed to attach backing store\n");
+        return err;
+    }
+
+    /* attach this resource as a scanout */
+    err = set_scanout(gdev, gdev->pmode_id, gdev->display_resource_id, gdev->pmode.r.width, gdev->pmode.r.height);
+    if (err < 0) {
+        LTRACEF("failed to set scanout\n");
+        return err;
+    }
+
+    /* create the flush thread */
+    thread_t *t;
+    t = thread_create("virtio gpu flusher", &virtio_gpu_flush_thread, (void *)gdev, HIGH_PRIORITY, DEFAULT_STACK_SIZE);
+    thread_detach_and_resume(t);
+
+    /* kick it once */
+    event_signal(&gdev->flush_event, true);
+
+    LTRACE_EXIT;
+
+    return NO_ERROR;
+}
+
+
+static void dump_gpu_config(const volatile struct virtio_gpu_config *config)
+{
+    LTRACEF("events_read 0x%x\n", config->events_read);
+    LTRACEF("events_clear 0x%x\n", config->events_clear);
+    LTRACEF("num_scanouts 0x%x\n", config->num_scanouts);
+    LTRACEF("reserved 0x%x\n", config->reserved);
+}
+
+status_t virtio_gpu_init(struct virtio_device *dev, uint32_t host_features)
+{
+    LTRACEF("dev %p, host_features 0x%x\n", dev, host_features);
+
+    /* allocate a new gpu device */
+    struct virtio_gpu_dev *gdev = malloc(sizeof(struct virtio_gpu_dev));
+    if (!gdev)
+        return ERR_NO_MEMORY;
+
+    mutex_init(&gdev->lock);
+    event_init(&gdev->io_event, false, EVENT_FLAG_AUTOUNSIGNAL);
+    event_init(&gdev->flush_event, false, EVENT_FLAG_AUTOUNSIGNAL);
+
+    gdev->dev = dev;
+    dev->priv = gdev;
+
+    gdev->pmode_id = -1;
+    gdev->next_resource_id = 1;
+
+    /* allocate memory for a gpu request */
+#if WITH_KERNEL_VM
+    gdev->gpu_request = pmm_alloc_kpage();
+    gdev->gpu_request_phys = kvaddr_to_paddr(gdev->gpu_request);
+#else
+    gdev->gpu_request = malloc(sizeof(struct virtio_gpu_resp_display_info)); // XXX get size better
+    gdev->gpu_request_phys = (paddr_t)gdev->gpu_request;
+#endif
+
+    /* make sure the device is reset */
+    virtio_reset_device(dev);
+
+    volatile struct virtio_gpu_config *config = (struct virtio_gpu_config *)dev->config_ptr;
+    dump_gpu_config(config);
+
+    /* ack and set the driver status bit */
+    virtio_status_acknowledge_driver(dev);
+
+    // XXX check features bits and ack/nak them
+
+    /* allocate a virtio ring */
+    virtio_alloc_ring(dev, 0, 16);
+
+    /* set our irq handler */
+    dev->irq_driver_callback = &virtio_gpu_irq_driver_callback;
+    dev->config_change_callback = &virtio_gpu_config_change_callback;
+
+    /* set DRIVER_OK */
+    virtio_status_driver_ok(dev);
+
+    /* save the main device we've found */
+    the_gdev = gdev;
+
+    printf("found virtio gpu device\n");
+
+    return NO_ERROR;
+}
+
+static enum handler_return virtio_gpu_irq_driver_callback(struct virtio_device *dev, uint ring, const struct vring_used_elem *e)
+{
+    struct virtio_gpu_dev *gdev = (struct virtio_gpu_dev *)dev->priv;
+
+    LTRACEF("dev %p, ring %u, e %p, id %u, len %u\n", dev, ring, e, e->id, e->len);
+
+    /* parse our descriptor chain, add back to the free queue */
+    uint16_t i = e->id;
+    for (;;) {
+        int next;
+        struct vring_desc *desc = virtio_desc_index_to_desc(dev, ring, i);
+
+        //virtio_dump_desc(desc);
+
+        if (desc->flags & VRING_DESC_F_NEXT) {
+            next = desc->next;
+        } else {
+            /* end of chain */
+            next = -1;
+        }
+
+        virtio_free_desc(dev, ring, i);
+
+        if (next < 0)
+            break;
+        i = next;
+    }
+
+    /* signal our event */
+    event_signal(&gdev->io_event, false);
+
+    return INT_RESCHEDULE;
+}
+
+static enum handler_return virtio_gpu_config_change_callback(struct virtio_device *dev)
+{
+    struct virtio_gpu_dev *gdev = (struct virtio_gpu_dev *)dev->priv;
+
+    LTRACEF("gdev %p\n", gdev);
+
+    volatile struct virtio_gpu_config *config = (struct virtio_gpu_config *)dev->config_ptr;
+    dump_gpu_config(config);
+
+    return INT_RESCHEDULE;
+}
+
+static int virtio_gpu_flush_thread(void *arg)
+{
+    struct virtio_gpu_dev *gdev = (struct virtio_gpu_dev *)arg;
+    status_t err;
+
+    for (;;) {
+        event_wait(&gdev->flush_event);
+
+        /* transfer to host 2d */
+        err = transfer_to_host_2d(gdev, gdev->display_resource_id, gdev->pmode.r.width, gdev->pmode.r.height);
+        if (err < 0) {
+            LTRACEF("failed to flush resource\n");
+            continue;
+        }
+
+        /* resource flush */
+        err = flush_resource(gdev, gdev->display_resource_id, gdev->pmode.r.width, gdev->pmode.r.height);
+        if (err < 0) {
+            LTRACEF("failed to flush resource\n");
+            continue;
+        }
+    }
+
+    return 0;
+}
+
+void virtio_gpu_gfx_flush(uint starty, uint endy)
+{
+    event_signal(&the_gdev->flush_event, !arch_ints_disabled());
+}
+
+status_t display_get_info(struct display_info *info)
+{
+    memset(info, 0, sizeof(*info));
+
+    if (!the_gdev)
+        return ERR_NOT_FOUND;
+
+    info->framebuffer = the_gdev->fb;
+    info->format = GFX_FORMAT_RGB_x888;
+    info->width = the_gdev->pmode.r.width;
+    info->height = the_gdev->pmode.r.height;
+    info->stride = info->width;
+    info->flush = virtio_gpu_gfx_flush;
+
+    return NO_ERROR;
+}
+
+
diff --git a/src/bsp/lk/dev/virtio/gpu/virtio_gpu.h b/src/bsp/lk/dev/virtio/gpu/virtio_gpu.h
new file mode 100644
index 0000000..19ed6d2
--- /dev/null
+++ b/src/bsp/lk/dev/virtio/gpu/virtio_gpu.h
@@ -0,0 +1,208 @@
+/*
+ * Virtio GPU Device
+ *
+ * Copyright Red Hat, Inc. 2013-2014
+ *
+ * Authors:
+ *     Dave Airlie <airlied@redhat.com>
+ *     Gerd Hoffmann <kraxel@redhat.com>
+ *
+ * This header is BSD licensed so anyone can use the definitions
+ * to implement compatible drivers/servers:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of IBM nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL IBM OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/* taken from qemu sources */
+
+#ifndef VIRTIO_GPU_HW_H
+#define VIRTIO_GPU_HW_H
+
+#include <stdint.h>
+
+enum virtio_gpu_ctrl_type {
+    VIRTIO_GPU_UNDEFINED = 0,
+
+    /* 2d commands */
+    VIRTIO_GPU_CMD_GET_DISPLAY_INFO = 0x0100,
+    VIRTIO_GPU_CMD_RESOURCE_CREATE_2D,
+    VIRTIO_GPU_CMD_RESOURCE_UNREF,
+    VIRTIO_GPU_CMD_SET_SCANOUT,
+    VIRTIO_GPU_CMD_RESOURCE_FLUSH,
+    VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D,
+    VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING,
+    VIRTIO_GPU_CMD_RESOURCE_DETACH_BACKING,
+
+    /* cursor commands */
+    VIRTIO_GPU_CMD_UPDATE_CURSOR = 0x0300,
+    VIRTIO_GPU_CMD_MOVE_CURSOR,
+
+    /* success responses */
+    VIRTIO_GPU_RESP_OK_NODATA = 0x1100,
+    VIRTIO_GPU_RESP_OK_DISPLAY_INFO,
+
+    /* error responses */
+    VIRTIO_GPU_RESP_ERR_UNSPEC = 0x1200,
+    VIRTIO_GPU_RESP_ERR_OUT_OF_MEMORY,
+    VIRTIO_GPU_RESP_ERR_INVALID_SCANOUT_ID,
+    VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID,
+    VIRTIO_GPU_RESP_ERR_INVALID_CONTEXT_ID,
+    VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER,
+};
+
+#define VIRTIO_GPU_FLAG_FENCE (1 << 0)
+
+struct virtio_gpu_ctrl_hdr {
+    uint32_t type;
+    uint32_t flags;
+    uint64_t fence_id;
+    uint32_t ctx_id;
+    uint32_t padding;
+};
+
+/* data passed in the cursor vq */
+
+struct virtio_gpu_cursor_pos {
+    uint32_t scanout_id;
+    uint32_t x;
+    uint32_t y;
+    uint32_t padding;
+};
+
+/* VIRTIO_GPU_CMD_UPDATE_CURSOR, VIRTIO_GPU_CMD_MOVE_CURSOR */
+struct virtio_gpu_update_cursor {
+    struct virtio_gpu_ctrl_hdr hdr;
+    struct virtio_gpu_cursor_pos pos;  /* update & move */
+    uint32_t resource_id;           /* update only */
+    uint32_t hot_x;                 /* update only */
+    uint32_t hot_y;                 /* update only */
+    uint32_t padding;
+};
+
+/* data passed in the control vq, 2d related */
+
+struct virtio_gpu_rect {
+    uint32_t x;
+    uint32_t y;
+    uint32_t width;
+    uint32_t height;
+};
+
+/* VIRTIO_GPU_CMD_RESOURCE_UNREF */
+struct virtio_gpu_resource_unref {
+    struct virtio_gpu_ctrl_hdr hdr;
+    uint32_t resource_id;
+    uint32_t padding;
+};
+
+/* VIRTIO_GPU_CMD_RESOURCE_CREATE_2D: create a 2d resource with a format */
+struct virtio_gpu_resource_create_2d {
+    struct virtio_gpu_ctrl_hdr hdr;
+    uint32_t resource_id;
+    uint32_t format;
+    uint32_t width;
+    uint32_t height;
+};
+
+/* VIRTIO_GPU_CMD_SET_SCANOUT */
+struct virtio_gpu_set_scanout {
+    struct virtio_gpu_ctrl_hdr hdr;
+    struct virtio_gpu_rect r;
+    uint32_t scanout_id;
+    uint32_t resource_id;
+};
+
+/* VIRTIO_GPU_CMD_RESOURCE_FLUSH */
+struct virtio_gpu_resource_flush {
+    struct virtio_gpu_ctrl_hdr hdr;
+    struct virtio_gpu_rect r;
+    uint32_t resource_id;
+    uint32_t padding;
+};
+
+/* VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D: simple transfer to_host */
+struct virtio_gpu_transfer_to_host_2d {
+    struct virtio_gpu_ctrl_hdr hdr;
+    struct virtio_gpu_rect r;
+    uint64_t offset;
+    uint32_t resource_id;
+    uint32_t padding;
+};
+
+struct virtio_gpu_mem_entry {
+    uint64_t addr;
+    uint32_t length;
+    uint32_t padding;
+};
+
+/* VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING */
+struct virtio_gpu_resource_attach_backing {
+    struct virtio_gpu_ctrl_hdr hdr;
+    uint32_t resource_id;
+    uint32_t nr_entries;
+};
+
+/* VIRTIO_GPU_CMD_RESOURCE_DETACH_BACKING */
+struct virtio_gpu_resource_detach_backing {
+    struct virtio_gpu_ctrl_hdr hdr;
+    uint32_t resource_id;
+    uint32_t padding;
+};
+
+/* VIRTIO_GPU_RESP_OK_DISPLAY_INFO */
+#define VIRTIO_GPU_MAX_SCANOUTS 16
+struct virtio_gpu_resp_display_info {
+    struct virtio_gpu_ctrl_hdr hdr;
+    struct virtio_gpu_display_one {
+        struct virtio_gpu_rect r;
+        uint32_t enabled;
+        uint32_t flags;
+    } pmodes[VIRTIO_GPU_MAX_SCANOUTS];
+};
+
+#define VIRTIO_GPU_EVENT_DISPLAY (1 << 0)
+
+struct virtio_gpu_config {
+    uint32_t events_read;
+    uint32_t events_clear;
+    uint32_t num_scanouts;
+    uint32_t reserved;
+};
+
+/* simple formats for fbcon/X use */
+enum virtio_gpu_formats {
+    VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM  = 1,
+    VIRTIO_GPU_FORMAT_B8G8R8X8_UNORM  = 2,
+    VIRTIO_GPU_FORMAT_A8R8G8B8_UNORM  = 3,
+    VIRTIO_GPU_FORMAT_X8R8G8B8_UNORM  = 4,
+
+    VIRTIO_GPU_FORMAT_R8G8B8A8_UNORM  = 67,
+    VIRTIO_GPU_FORMAT_X8B8G8R8_UNORM  = 68,
+
+    VIRTIO_GPU_FORMAT_A8B8G8R8_UNORM  = 121,
+    VIRTIO_GPU_FORMAT_R8G8B8X8_UNORM  = 134,
+};
+
+#endif
diff --git a/src/bsp/lk/dev/virtio/include/dev/virtio.h b/src/bsp/lk/dev/virtio/include/dev/virtio.h
new file mode 100644
index 0000000..3403602
--- /dev/null
+++ b/src/bsp/lk/dev/virtio/include/dev/virtio.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2014 Travis Geiselbrecht
+ *
+ * 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
+
+#include <compiler.h>
+#include <assert.h>
+#include <list.h>
+#include <sys/types.h>
+#include <dev/virtio/virtio_ring.h>
+
+/* detect a virtio mmio hardware block
+ * returns number of devices found */
+int virtio_mmio_detect(void *ptr, uint count, const uint irqs[]);
+
+#define MAX_VIRTIO_RINGS 4
+
+struct virtio_mmio_config;
+
+struct virtio_device {
+    bool valid;
+
+    uint index;
+    uint irq;
+
+    volatile struct virtio_mmio_config *mmio_config;
+    void *config_ptr;
+
+    void *priv; /* a place for the driver to put private data */
+
+    enum handler_return (*irq_driver_callback)(struct virtio_device *dev, uint ring, const struct vring_used_elem *e);
+    enum handler_return (*config_change_callback)(struct virtio_device *dev);
+
+    /* virtio rings */
+    uint32_t active_rings_bitmap;
+    struct vring ring[MAX_VIRTIO_RINGS];
+};
+
+void virtio_reset_device(struct virtio_device *dev);
+void virtio_status_acknowledge_driver(struct virtio_device *dev);
+void virtio_status_driver_ok(struct virtio_device *dev);
+
+/* api used by devices to interact with the virtio bus */
+status_t virtio_alloc_ring(struct virtio_device *dev, uint index, uint16_t len) __NONNULL();
+
+/* add a descriptor at index desc_index to the free list on ring_index */
+void virtio_free_desc(struct virtio_device *dev, uint ring_index, uint16_t desc_index);
+
+/* allocate a descriptor off the free list, 0xffff is error */
+uint16_t virtio_alloc_desc(struct virtio_device *dev, uint ring_index);
+
+/* allocate a descriptor chain the free list */
+struct vring_desc *virtio_alloc_desc_chain(struct virtio_device *dev, uint ring_index, size_t count, uint16_t *start_index);
+
+static inline struct vring_desc *virtio_desc_index_to_desc(struct virtio_device *dev, uint ring_index, uint16_t desc_index)
+{
+    DEBUG_ASSERT(desc_index != 0xffff);
+    return &dev->ring[ring_index].desc[desc_index];
+}
+
+void virtio_dump_desc(const struct vring_desc *desc);
+
+/* submit a chain to the avail list */
+void virtio_submit_chain(struct virtio_device *dev, uint ring_index, uint16_t desc_index);
+
+void virtio_kick(struct virtio_device *dev, uint ring_idnex);
+
+
diff --git a/src/bsp/lk/dev/virtio/include/dev/virtio/virtio_ring.h b/src/bsp/lk/dev/virtio/include/dev/virtio/virtio_ring.h
new file mode 100644
index 0000000..bf68bf9
--- /dev/null
+++ b/src/bsp/lk/dev/virtio/include/dev/virtio/virtio_ring.h
@@ -0,0 +1,176 @@
+/* taken from linux source 3.15 at include/uapi/linux/virtio_ring.h */
+#ifndef _UAPI_LINUX_VIRTIO_RING_H
+#define _UAPI_LINUX_VIRTIO_RING_H
+/* An interface for efficient virtio implementation, currently for use by KVM
+ * and lguest, but hopefully others soon.  Do NOT change this since it will
+ * break existing servers and clients.
+ *
+ * This header is BSD licensed so anyone can use the definitions to implement
+ * compatible drivers/servers.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of IBM nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL IBM OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * Copyright Rusty Russell IBM Corporation 2007. */
+#include <stdint.h>
+#include <pow2.h>
+
+/* This marks a buffer as continuing via the next field. */
+#define VRING_DESC_F_NEXT   1
+/* This marks a buffer as write-only (otherwise read-only). */
+#define VRING_DESC_F_WRITE  2
+/* This means the buffer contains a list of buffer descriptors. */
+#define VRING_DESC_F_INDIRECT   4
+
+/* The Host uses this in used->flags to advise the Guest: don't kick me when
+ * you add a buffer.  It's unreliable, so it's simply an optimization.  Guest
+ * will still kick if it's out of buffers. */
+#define VRING_USED_F_NO_NOTIFY  1
+/* The Guest uses this in avail->flags to advise the Host: don't interrupt me
+ * when you consume a buffer.  It's unreliable, so it's simply an
+ * optimization.  */
+#define VRING_AVAIL_F_NO_INTERRUPT  1
+
+/* We support indirect buffer descriptors */
+#define VIRTIO_RING_F_INDIRECT_DESC 28
+
+/* The Guest publishes the used index for which it expects an interrupt
+ * at the end of the avail ring. Host should ignore the avail->flags field. */
+/* The Host publishes the avail index for which it expects a kick
+ * at the end of the used ring. Guest should ignore the used->flags field. */
+#define VIRTIO_RING_F_EVENT_IDX     29
+
+/* Virtio ring descriptors: 16 bytes.  These can chain together via "next". */
+struct vring_desc {
+    /* Address (guest-physical). */
+    uint64_t addr;
+    /* Length. */
+    uint32_t len;
+    /* The flags as indicated above. */
+    uint16_t flags;
+    /* We chain unused descriptors via this, too */
+    uint16_t next;
+};
+
+struct vring_avail {
+    uint16_t flags;
+    uint16_t idx;
+    uint16_t ring[];
+};
+
+/* u32 is used here for ids for padding reasons. */
+struct vring_used_elem {
+    /* Index of start of used descriptor chain. */
+    uint32_t id;
+    /* Total length of the descriptor chain which was used (written to) */
+    uint32_t len;
+};
+
+struct vring_used {
+    uint16_t flags;
+    uint16_t idx;
+    struct vring_used_elem ring[];
+};
+
+struct vring {
+    uint32_t num;
+    uint32_t num_mask;
+
+    uint16_t free_list; /* head of a free list of descriptors per ring. 0xffff is NULL */
+    uint16_t free_count;
+
+    uint16_t last_used;
+
+    struct vring_desc *desc;
+
+    struct vring_avail *avail;
+
+    struct vring_used *used;
+};
+
+/* The standard layout for the ring is a continuous chunk of memory which looks
+ * like this.  We assume num is a power of 2.
+ *
+ * struct vring
+ * {
+ *  // The actual descriptors (16 bytes each)
+ *  struct vring_desc desc[num];
+ *
+ *  // A ring of available descriptor heads with free-running index.
+ *  uint16_t avail_flags;
+ *  uint16_t avail_idx;
+ *  uint16_t available[num];
+ *  uint16_t used_event_idx;
+ *
+ *  // Padding to the next align boundary.
+ *  char pad[];
+ *
+ *  // A ring of used descriptor heads with free-running index.
+ *  uint16_t used_flags;
+ *  uint16_t used_idx;
+ *  struct vring_used_elem used[num];
+ *  uint16_t avail_event_idx;
+ * };
+ */
+/* We publish the used event index at the end of the available ring, and vice
+ * versa. They are at the end for backwards compatibility. */
+#define vring_used_event(vr) ((vr)->avail->ring[(vr)->num])
+#define vring_avail_event(vr) (*(uint16_t *)&(vr)->used->ring[(vr)->num])
+
+static inline void vring_init(struct vring *vr, unsigned int num, void *p,
+                  unsigned long align)
+{
+    vr->num = num;
+    vr->num_mask = (1 << log2_uint(num)) - 1;
+    vr->free_list = 0xffff;
+    vr->free_count = 0;
+    vr->last_used = 0;
+    vr->desc = p;
+    vr->avail = p + num*sizeof(struct vring_desc);
+    vr->used = (void *)(((unsigned long)&vr->avail->ring[num] + sizeof(uint16_t)
+        + align-1) & ~(align - 1));
+}
+
+static inline unsigned vring_size(unsigned int num, unsigned long align)
+{
+    return ((sizeof(struct vring_desc) * num + sizeof(uint16_t) * (3 + num)
+         + align - 1) & ~(align - 1))
+        + sizeof(uint16_t) * 3 + sizeof(struct vring_used_elem) * num;
+}
+
+/* The following is used with USED_EVENT_IDX and AVAIL_EVENT_IDX */
+/* Assuming a given event_idx value from the other size, if
+ * we have just incremented index from old to new_idx,
+ * should we trigger an event? */
+static inline int vring_need_event(uint16_t event_idx, uint16_t new_idx, uint16_t old)
+{
+    /* Note: Xen has similar logic for notification hold-off
+     * in include/xen/interface/io/ring.h with req_event and req_prod
+     * corresponding to event_idx + 1 and new_idx respectively.
+     * Note also that req_event and req_prod in Xen start at 1,
+     * event indexes in virtio start at 0. */
+    return (uint16_t)(new_idx - event_idx - 1) < (uint16_t)(new_idx - old);
+}
+
+#endif /* _UAPI_LINUX_VIRTIO_RING_H */
+
diff --git a/src/bsp/lk/dev/virtio/net/include/dev/virtio/net.h b/src/bsp/lk/dev/virtio/net/include/dev/virtio/net.h
new file mode 100644
index 0000000..48ac6a0
--- /dev/null
+++ b/src/bsp/lk/dev/virtio/net/include/dev/virtio/net.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2015 Travis Geiselbrecht
+ *
+ * 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
+
+#include <compiler.h>
+#include <sys/types.h>
+#include <dev/virtio.h>
+
+status_t virtio_net_init(struct virtio_device *dev, uint32_t host_features) __NONNULL();
+status_t virtio_net_start(void);
+
+/* return the count of virtio interfaces found */
+int virtio_net_found(void);
+
+status_t virtio_net_get_mac_addr(uint8_t mac_addr[6]);
+
+struct pktbuf;
+extern status_t virtio_net_send_minip_pkt(struct pktbuf *p);
+
diff --git a/src/bsp/lk/dev/virtio/net/rules.mk b/src/bsp/lk/dev/virtio/net/rules.mk
new file mode 100644
index 0000000..2f782d2
--- /dev/null
+++ b/src/bsp/lk/dev/virtio/net/rules.mk
@@ -0,0 +1,12 @@
+LOCAL_DIR := $(GET_LOCAL_DIR)
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_SRCS += \
+	$(LOCAL_DIR)/virtio-net.c
+
+MODULE_DEPS += \
+	dev/virtio \
+	lib/minip
+
+include make/module.mk
diff --git a/src/bsp/lk/dev/virtio/net/virtio-net.c b/src/bsp/lk/dev/virtio/net/virtio-net.c
new file mode 100644
index 0000000..2d0102d
--- /dev/null
+++ b/src/bsp/lk/dev/virtio/net/virtio-net.c
@@ -0,0 +1,473 @@
+/*
+ * Copyright (c) 2015 Travis Geiselbrecht
+ *
+ * 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 <dev/virtio/net.h>
+
+#include <debug.h>
+#include <assert.h>
+#include <trace.h>
+#include <compiler.h>
+#include <list.h>
+#include <string.h>
+#include <err.h>
+#include <kernel/thread.h>
+#include <kernel/event.h>
+#include <kernel/spinlock.h>
+#include <kernel/vm.h>
+#include <lib/pktbuf.h>
+#include <lib/minip.h>
+
+#define LOCAL_TRACE 0
+
+struct virtio_net_config {
+    uint8_t mac[6];
+    uint16_t status;
+    uint16_t max_virtqueue_pairs;
+} __PACKED;
+
+struct virtio_net_hdr {
+    uint8_t  flags;
+    uint8_t  gso_type;
+    uint16_t hdr_len;
+    uint16_t gso_size;
+    uint16_t csum_start;
+    uint16_t csum_offset;
+    uint16_t num_buffers; // unused in tx
+} __PACKED;
+
+#define VIRTIO_NET_F_CSUM                   (1<<0)
+#define VIRTIO_NET_F_GUEST_CSUM             (1<<1)
+#define VIRTIO_NET_F_CTRL_GUEST_OFFLOADS    (1<<2)
+#define VIRTIO_NET_F_MAC                    (1<<5)
+#define VIRTIO_NET_F_GSO                    (1<<6)
+#define VIRTIO_NET_F_GUEST_TSO4             (1<<7)
+#define VIRTIO_NET_F_GUEST_TSO6             (1<<8)
+#define VIRTIO_NET_F_GUEST_ECN              (1<<9)
+#define VIRTIO_NET_F_GUEST_UFO              (1<<10)
+#define VIRTIO_NET_F_HOST_TSO4              (1<<11)
+#define VIRTIO_NET_F_HOST_TSO6              (1<<12)
+#define VIRTIO_NET_F_HOST_ECN               (1<<13)
+#define VIRTIO_NET_F_HOST_UFO               (1<<14)
+#define VIRTIO_NET_F_MRG_RXBUF              (1<<15)
+#define VIRTIO_NET_F_STATUS                 (1<<16)
+#define VIRTIO_NET_F_CTRL_VQ                (1<<17)
+#define VIRTIO_NET_F_CTRL_RX                (1<<18)
+#define VIRTIO_NET_F_CTRL_VLAN              (1<<19)
+#define VIRTIO_NET_F_GUEST_ANNOUNCE         (1<<21)
+#define VIRTIO_NET_F_MQ                     (1<<22)
+#define VIRTIO_NET_F_CTRL_MAC_ADDR          (1<<23)
+
+#define VIRTIO_NET_S_LINK_UP                (1<<0)
+#define VIRTIO_NET_S_ANNOUNCE               (1<<1)
+
+#define TX_RING_SIZE 16
+#define RX_RING_SIZE 16
+
+#define RING_RX 0
+#define RING_TX 1
+
+#define VIRTIO_NET_MSS 1514
+
+struct virtio_net_dev {
+    struct virtio_device *dev;
+    bool started;
+
+    struct virtio_net_config *config;
+
+    spin_lock_t lock;
+    event_t rx_event;
+
+    /* list of active tx/rx packets to be freed at irq time */
+    pktbuf_t *pending_tx_packet[TX_RING_SIZE];
+    pktbuf_t *pending_rx_packet[RX_RING_SIZE];
+
+    uint tx_pending_count;
+    struct list_node completed_rx_queue;
+};
+
+static enum handler_return virtio_net_irq_driver_callback(struct virtio_device *dev, uint ring, const struct vring_used_elem *e);
+static int virtio_net_rx_worker(void *arg);
+static status_t virtio_net_queue_rx(struct virtio_net_dev *ndev, pktbuf_t *p);
+
+// XXX remove need for this
+static struct virtio_net_dev *the_ndev;
+
+static void dump_feature_bits(uint32_t feature)
+{
+    printf("virtio-net host features (0x%x):", feature);
+    if (feature & VIRTIO_NET_F_CSUM) printf(" CSUM");
+    if (feature & VIRTIO_NET_F_GUEST_CSUM) printf(" GUEST_CSUM");
+    if (feature & VIRTIO_NET_F_CTRL_GUEST_OFFLOADS) printf(" CTRL_GUEST_OFFLOADS");
+    if (feature & VIRTIO_NET_F_MAC) printf(" MAC");
+    if (feature & VIRTIO_NET_F_GSO) printf(" GSO");
+    if (feature & VIRTIO_NET_F_GUEST_TSO4) printf(" GUEST_TSO4");
+    if (feature & VIRTIO_NET_F_GUEST_TSO6) printf(" GUEST_TSO6");
+    if (feature & VIRTIO_NET_F_GUEST_ECN) printf(" GUEST_ECN");
+    if (feature & VIRTIO_NET_F_GUEST_UFO) printf(" GUEST_UFO");
+    if (feature & VIRTIO_NET_F_HOST_TSO4) printf(" HOST_TSO4");
+    if (feature & VIRTIO_NET_F_HOST_TSO6) printf(" HOST_TSO6");
+    if (feature & VIRTIO_NET_F_HOST_ECN) printf(" HOST_ECN");
+    if (feature & VIRTIO_NET_F_HOST_UFO) printf(" HOST_UFO");
+    if (feature & VIRTIO_NET_F_MRG_RXBUF) printf(" MRG_RXBUF");
+    if (feature & VIRTIO_NET_F_STATUS) printf(" STATUS");
+    if (feature & VIRTIO_NET_F_CTRL_VQ) printf(" CTRL_VQ");
+    if (feature & VIRTIO_NET_F_CTRL_RX) printf(" CTRL_RX");
+    if (feature & VIRTIO_NET_F_CTRL_VLAN) printf(" CTRL_VLAN");
+    if (feature & VIRTIO_NET_F_GUEST_ANNOUNCE) printf(" GUEST_ANNOUNCE");
+    if (feature & VIRTIO_NET_F_MQ) printf(" MQ");
+    if (feature & VIRTIO_NET_F_CTRL_MAC_ADDR) printf(" CTRL_MAC_ADDR");
+    printf("\n");
+}
+
+status_t virtio_net_init(struct virtio_device *dev, uint32_t host_features)
+{
+    LTRACEF("dev %p, host_features 0x%x\n", dev, host_features);
+
+    /* allocate a new net device */
+    struct virtio_net_dev *ndev = calloc(1, sizeof(struct virtio_net_dev));
+    if (!ndev)
+        return ERR_NO_MEMORY;
+
+    ndev->dev = dev;
+    dev->priv = ndev;
+    ndev->started = false;
+
+    ndev->lock = SPIN_LOCK_INITIAL_VALUE;
+    event_init(&ndev->rx_event, false, EVENT_FLAG_AUTOUNSIGNAL);
+    list_initialize(&ndev->completed_rx_queue);
+
+    ndev->config = (struct virtio_net_config *)dev->config_ptr;
+
+    /* ack and set the driver status bit */
+    virtio_status_acknowledge_driver(dev);
+
+    // XXX check features bits and ack/nak them
+    dump_feature_bits(host_features);
+
+    /* set our irq handler */
+    dev->irq_driver_callback = &virtio_net_irq_driver_callback;
+
+    /* set DRIVER_OK */
+    virtio_status_driver_ok(dev);
+
+    /* allocate a pair of virtio rings */
+    virtio_alloc_ring(dev, RING_RX, RX_RING_SIZE); // rx
+    virtio_alloc_ring(dev, RING_TX, TX_RING_SIZE); // tx
+
+    the_ndev = ndev;
+
+    return NO_ERROR;
+}
+
+status_t virtio_net_start(void)
+{
+    if (the_ndev->started)
+        return ERR_ALREADY_STARTED;
+
+    the_ndev->started = true;
+
+    /* start the rx worker thread */
+    thread_resume(thread_create("virtio_net_rx", &virtio_net_rx_worker, (void *)the_ndev, HIGH_PRIORITY, DEFAULT_STACK_SIZE));
+
+    /* queue up a bunch of rxes */
+    for (uint i = 0; i < RX_RING_SIZE - 1; i++) {
+        pktbuf_t *p = pktbuf_alloc();
+        if (p) {
+            virtio_net_queue_rx(the_ndev, p);
+        }
+    }
+
+    return NO_ERROR;
+}
+
+static status_t virtio_net_queue_tx_pktbuf(struct virtio_net_dev *ndev, pktbuf_t *p2)
+{
+    struct virtio_device *vdev = ndev->dev;
+
+    uint16_t i;
+    pktbuf_t *p;
+
+    DEBUG_ASSERT(ndev);
+
+    p = pktbuf_alloc();
+    if (!p)
+        return ERR_NO_MEMORY;
+
+    /* point our header to the base of the first pktbuf */
+    struct virtio_net_hdr *hdr = pktbuf_append(p, sizeof(struct virtio_net_hdr) - 2);
+    memset(hdr, 0, p->dlen);
+
+    spin_lock_saved_state_t state;
+    spin_lock_irqsave(&ndev->lock, state);
+
+    /* only queue if we have enough tx descriptors */
+    if (ndev->tx_pending_count + 2 > TX_RING_SIZE)
+        goto nodesc;
+
+    /* allocate a chain of descriptors for our transfer */
+    struct vring_desc *desc = virtio_alloc_desc_chain(vdev, RING_TX, 2, &i);
+    if (!desc) {
+        spin_unlock_irqrestore(&ndev->lock, state);
+
+nodesc:
+        TRACEF("out of virtio tx descriptors, tx_pending_count %u\n", ndev->tx_pending_count);
+        pktbuf_free(p, true);
+
+        return ERR_NO_MEMORY;
+    }
+
+    ndev->tx_pending_count += 2;
+
+    /* save a pointer to our pktbufs for the irq handler to free */
+    LTRACEF("saving pointer to pkt in index %u and %u\n", i, desc->next);
+    DEBUG_ASSERT(ndev->pending_tx_packet[i] == NULL);
+    DEBUG_ASSERT(ndev->pending_tx_packet[desc->next] == NULL);
+    ndev->pending_tx_packet[i] = p;
+    ndev->pending_tx_packet[desc->next] = p2;
+
+    /* set up the descriptor pointing to the header */
+    desc->addr = pktbuf_data_phys(p);
+    desc->len = p->dlen;
+    desc->flags |= VRING_DESC_F_NEXT;
+
+    /* set up the descriptor pointing to the buffer */
+    desc = virtio_desc_index_to_desc(vdev, RING_TX, desc->next);
+    desc->addr = pktbuf_data_phys(p2);
+    desc->len = p2->dlen;
+    desc->flags = 0;
+
+    /* submit the transfer */
+    virtio_submit_chain(vdev, RING_TX, i);
+
+    /* kick it off */
+    virtio_kick(vdev, RING_TX);
+
+    spin_unlock_irqrestore(&ndev->lock, state);
+
+    return NO_ERROR;
+}
+
+/* variant of the above function that copies the buffer into a pktbuf before sending */
+static status_t virtio_net_queue_tx(struct virtio_net_dev *ndev, const void *buf, size_t len)
+{
+    DEBUG_ASSERT(ndev);
+    DEBUG_ASSERT(buf);
+
+    pktbuf_t *p = pktbuf_alloc();
+    if (!p)
+        return ERR_NO_MEMORY;
+
+    /* copy the outgoing packet into the pktbuf */
+    p->data = p->buffer;
+    p->dlen = len;
+    memcpy(p->data, buf, len);
+
+    /* call through to the variant of the function that takes a pre-populated pktbuf */
+    status_t err = virtio_net_queue_tx_pktbuf(ndev, p);
+    if (err < 0) {
+        pktbuf_free(p, true);
+    }
+
+    return err;
+}
+
+static status_t virtio_net_queue_rx(struct virtio_net_dev *ndev, pktbuf_t *p)
+{
+    struct virtio_device *vdev = ndev->dev;
+
+    DEBUG_ASSERT(ndev);
+    DEBUG_ASSERT(p);
+
+    /* point our header to the base of the pktbuf */
+    p->data = p->buffer;
+    struct virtio_net_hdr *hdr = (struct virtio_net_hdr *)p->data;
+    memset(hdr, 0, sizeof(struct virtio_net_hdr) - 2);
+
+    p->dlen = sizeof(struct virtio_net_hdr) - 2 + VIRTIO_NET_MSS;
+
+    spin_lock_saved_state_t state;
+    spin_lock_irqsave(&ndev->lock, state);
+
+    /* allocate a chain of descriptors for our transfer */
+    uint16_t i;
+    struct vring_desc *desc = virtio_alloc_desc_chain(vdev, RING_RX, 1, &i);
+    DEBUG_ASSERT(desc); /* shouldn't be possible not to have a descriptor ready */
+
+    /* save a pointer to our pktbufs for the irq handler to use */
+    DEBUG_ASSERT(ndev->pending_rx_packet[i] == NULL);
+    ndev->pending_rx_packet[i] = p;
+
+    /* set up the descriptor pointing to the header */
+    desc->addr = pktbuf_data_phys(p);
+    desc->len = p->dlen;
+    desc->flags = VRING_DESC_F_WRITE;
+
+    /* submit the transfer */
+    virtio_submit_chain(vdev, RING_RX, i);
+
+    /* kick it off */
+    virtio_kick(vdev, RING_RX);
+
+    spin_unlock_irqrestore(&ndev->lock, state);
+
+    return NO_ERROR;
+}
+
+static enum handler_return virtio_net_irq_driver_callback(struct virtio_device *dev, uint ring, const struct vring_used_elem *e)
+{
+    struct virtio_net_dev *ndev = (struct virtio_net_dev *)dev->priv;
+
+    LTRACEF("dev %p, ring %u, e %p, id %u, len %u\n", dev, ring, e, e->id, e->len);
+
+    spin_lock(&ndev->lock);
+
+    /* parse our descriptor chain, add back to the free queue */
+    uint16_t i = e->id;
+    for (;;) {
+        int next;
+        struct vring_desc *desc = virtio_desc_index_to_desc(dev, ring, i);
+
+        if (desc->flags & VRING_DESC_F_NEXT) {
+            next = desc->next;
+        } else {
+            /* end of chain */
+            next = -1;
+        }
+
+        virtio_free_desc(dev, ring, i);
+
+        if (ring == RING_RX) {
+            /* put the freed rx buffer in a queue */
+            pktbuf_t *p = ndev->pending_rx_packet[i];
+            ndev->pending_rx_packet[i] = NULL;
+
+            DEBUG_ASSERT(p);
+            LTRACEF("rx pktbuf %p filled\n", p);
+
+            /* trim the pktbuf according to the written length in the used element descriptor */
+            if (e->len > (sizeof(struct virtio_net_hdr) - 2 + VIRTIO_NET_MSS)) {
+                TRACEF("bad used len on RX %u\n", e->len);
+                p->dlen = 0;
+            } else {
+                p->dlen = e->len;
+            }
+
+            list_add_tail(&ndev->completed_rx_queue, &p->list);
+        } else { // ring == RING_TX
+            /* free the pktbuf associated with the tx packet we just consumed */
+            pktbuf_t *p = ndev->pending_tx_packet[i];
+            ndev->pending_tx_packet[i] = NULL;
+            ndev->tx_pending_count--;
+
+            DEBUG_ASSERT(p);
+            LTRACEF("freeing pktbuf %p\n", p);
+
+            pktbuf_free(p, false);
+        }
+
+        if (next < 0)
+            break;
+        i = next;
+    }
+
+    spin_unlock(&ndev->lock);
+
+    /* if rx ring, signal our event */
+    if (ring == 0) {
+        event_signal(&ndev->rx_event, false);
+    }
+
+    return INT_RESCHEDULE;
+}
+
+static int virtio_net_rx_worker(void *arg)
+{
+    struct virtio_net_dev *ndev = (struct virtio_net_dev *)arg;
+
+    for (;;) {
+        event_wait(&ndev->rx_event);
+
+        /* pull some packets from the received queue */
+        for (;;) {
+            spin_lock_saved_state_t state;
+            spin_lock_irqsave(&ndev->lock, state);
+
+            pktbuf_t *p = list_remove_head_type(&ndev->completed_rx_queue, pktbuf_t, list);
+
+            spin_unlock_irqrestore(&ndev->lock, state);
+
+            if (!p)
+                break; /* nothing left in the queue, go back to waiting */
+
+            LTRACEF("got packet len %u\n", p->dlen);
+
+            /* process our packet */
+            struct virtio_net_hdr *hdr = pktbuf_consume(p, sizeof(struct virtio_net_hdr) - 2);
+            if (hdr) {
+                /* call up into the stack */
+                minip_rx_driver_callback(p);
+            }
+
+            /* requeue the pktbuf in the rx queue */
+            virtio_net_queue_rx(ndev, p);
+        }
+    }
+    return 0;
+}
+
+int virtio_net_found(void)
+{
+    return the_ndev ? 1 : 0;
+}
+
+status_t virtio_net_get_mac_addr(uint8_t mac_addr[6])
+{
+    if (!the_ndev)
+        return ERR_NOT_FOUND;
+
+    memcpy(mac_addr, the_ndev->config->mac, 6);
+
+    return NO_ERROR;
+}
+
+status_t virtio_net_send_minip_pkt(pktbuf_t *p)
+{
+    LTRACEF("p %p, dlen %u, flags 0x%x\n", p, p->dlen, p->flags);
+
+    DEBUG_ASSERT(p && p->dlen);
+
+    if ((p->flags & PKTBUF_FLAG_EOF) == 0) {
+        /* can't handle multi part packets yet */
+        PANIC_UNIMPLEMENTED;
+
+        return ERR_NOT_IMPLEMENTED;
+    }
+
+    /* hand the pktbuf off to the nic, it owns the pktbuf from now on out unless it fails */
+    status_t err = virtio_net_queue_tx_pktbuf(the_ndev, p);
+    if (err < 0) {
+        pktbuf_free(p, true);
+    }
+
+    return err;
+}
+
diff --git a/src/bsp/lk/dev/virtio/rules.mk b/src/bsp/lk/dev/virtio/rules.mk
new file mode 100644
index 0000000..22c8c0b
--- /dev/null
+++ b/src/bsp/lk/dev/virtio/rules.mk
@@ -0,0 +1,8 @@
+LOCAL_DIR := $(GET_LOCAL_DIR)
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_SRCS += \
+	$(LOCAL_DIR)/virtio.c
+
+include make/module.mk
diff --git a/src/bsp/lk/dev/virtio/virtio.c b/src/bsp/lk/dev/virtio/virtio.c
new file mode 100644
index 0000000..5449834
--- /dev/null
+++ b/src/bsp/lk/dev/virtio/virtio.c
@@ -0,0 +1,414 @@
+/*
+ * Copyright (c) 2014-2015 Travis Geiselbrecht
+ *
+ * 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 <dev/virtio.h>
+#include <dev/virtio/virtio_ring.h>
+
+#include <debug.h>
+#include <assert.h>
+#include <trace.h>
+#include <compiler.h>
+#include <list.h>
+#include <err.h>
+#include <stdlib.h>
+#include <string.h>
+#include <pow2.h>
+#include <lk/init.h>
+#include <kernel/thread.h>
+#include <kernel/vm.h>
+#include <platform/interrupts.h>
+
+#include "virtio_priv.h"
+
+#if WITH_DEV_VIRTIO_BLOCK
+#include <dev/virtio/block.h>
+#endif
+#if WITH_DEV_VIRTIO_NET
+#include <dev/virtio/net.h>
+#endif
+#if WITH_DEV_VIRTIO_GPU
+#include <dev/virtio/gpu.h>
+#endif
+
+#define LOCAL_TRACE 0
+
+static struct virtio_device *devices;
+
+static void dump_mmio_config(const volatile struct virtio_mmio_config *mmio)
+{
+    printf("mmio at %p\n", mmio);
+    printf("\tmagic 0x%x\n", mmio->magic);
+    printf("\tversion 0x%x\n", mmio->version);
+    printf("\tdevice_id 0x%x\n", mmio->device_id);
+    printf("\tvendor_id 0x%x\n", mmio->vendor_id);
+    printf("\thost_features 0x%x\n", mmio->host_features);
+    printf("\tguest_page_size %u\n", mmio->guest_page_size);
+    printf("\tqnum %u\n", mmio->queue_num);
+    printf("\tqnum_max %u\n", mmio->queue_num_max);
+    printf("\tqnum_align %u\n", mmio->queue_align);
+    printf("\tqnum_pfn %u\n", mmio->queue_pfn);
+    printf("\tstatus 0x%x\n", mmio->status);
+}
+
+void virtio_dump_desc(const struct vring_desc *desc)
+{
+    printf("vring descriptor %p\n", desc);
+    printf("\taddr  0x%llx\n", desc->addr);
+    printf("\tlen   0x%x\n", desc->len);
+    printf("\tflags 0x%hhx\n", desc->flags);
+    printf("\tnext  0x%hhx\n", desc->next);
+}
+
+static enum handler_return virtio_mmio_irq(void *arg)
+{
+    struct virtio_device *dev = (struct virtio_device *)arg;
+    LTRACEF("dev %p, index %u\n", dev, dev->index);
+
+    uint32_t irq_status = dev->mmio_config->interrupt_status;
+    LTRACEF("status 0x%x\n", irq_status);
+
+    enum handler_return ret = INT_NO_RESCHEDULE;
+    if (irq_status & 0x1) { /* used ring update */
+        // XXX is this safe?
+        dev->mmio_config->interrupt_ack = 0x1;
+
+        /* cycle through all the active rings */
+        for (uint r = 0; r < MAX_VIRTIO_RINGS; r++) {
+            if ((dev->active_rings_bitmap & (1<<r)) == 0)
+                continue;
+
+            struct vring *ring = &dev->ring[r];
+            LTRACEF("ring %u: used flags 0x%hhx idx 0x%hhx last_used %u\n", r, ring->used->flags, ring->used->idx, ring->last_used);
+
+            uint cur_idx = ring->used->idx;
+            for (uint i = ring->last_used; i != (cur_idx & ring->num_mask); i = (i + 1) & ring->num_mask) {
+                LTRACEF("looking at idx %u\n", i);
+
+                // process chain
+                struct vring_used_elem *used_elem = &ring->used->ring[i];
+                LTRACEF("id %u, len %u\n", used_elem->id, used_elem->len);
+
+                DEBUG_ASSERT(dev->irq_driver_callback);
+                ret |= dev->irq_driver_callback(dev, r, used_elem);
+
+                ring->last_used = (ring->last_used + 1) & ring->num_mask;
+            }
+        }
+    }
+    if (irq_status & 0x2) { /* config change */
+        dev->mmio_config->interrupt_ack = 0x2;
+
+        if (dev->config_change_callback) {
+            ret |= dev->config_change_callback(dev);
+        }
+    }
+
+    LTRACEF("exiting irq\n");
+
+    return ret;
+}
+
+int virtio_mmio_detect(void *ptr, uint count, const uint irqs[])
+{
+    LTRACEF("ptr %p, count %u\n", ptr, count);
+
+    DEBUG_ASSERT(ptr);
+    DEBUG_ASSERT(irqs);
+    DEBUG_ASSERT(!devices);
+
+    /* allocate an array big enough to hold a list of devices */
+    devices = calloc(count, sizeof(struct virtio_device));
+    if (!devices)
+        return ERR_NO_MEMORY;
+
+    int found = 0;
+    for (uint i = 0; i < count; i++) {
+        volatile struct virtio_mmio_config *mmio = (struct virtio_mmio_config *)((uint8_t *)ptr + i * 0x200);
+        struct virtio_device *dev = &devices[i];
+
+        dev->index = i;
+        dev->irq = irqs[i];
+
+        mask_interrupt(irqs[i]);
+        register_int_handler(irqs[i], &virtio_mmio_irq, (void *)dev);
+
+        LTRACEF("looking at magic 0x%x version 0x%x did 0x%x vid 0x%x\n",
+                mmio->magic, mmio->version, mmio->device_id, mmio->vendor_id);
+
+        if (mmio->magic != VIRTIO_MMIO_MAGIC) {
+            continue;
+        }
+
+#if LOCAL_TRACE
+        if (mmio->device_id != 0) {
+            dump_mmio_config(mmio);
+        }
+#endif
+
+#if WITH_DEV_VIRTIO_BLOCK
+        if (mmio->device_id == 2) { // block device
+            LTRACEF("found block device\n");
+
+            dev->mmio_config = mmio;
+            dev->config_ptr = (void *)mmio->config;
+
+            status_t err = virtio_block_init(dev, mmio->host_features);
+            if (err >= 0) {
+                // good device
+                dev->valid = true;
+
+                if (dev->irq_driver_callback)
+                    unmask_interrupt(dev->irq);
+
+                // XXX quick test code, remove
+#if 0
+                uint8_t buf[512];
+                memset(buf, 0x99, sizeof(buf));
+                virtio_block_read_write(dev, buf, 0, sizeof(buf), false);
+                hexdump8_ex(buf, sizeof(buf), 0);
+
+                buf[0]++;
+                virtio_block_read_write(dev, buf, 0, sizeof(buf), true);
+
+                virtio_block_read_write(dev, buf, 0, sizeof(buf), false);
+                hexdump8_ex(buf, sizeof(buf), 0);
+#endif
+            }
+
+        }
+#endif // WITH_DEV_VIRTIO_BLOCK
+#if WITH_DEV_VIRTIO_NET
+        if (mmio->device_id == 1) { // network device
+            LTRACEF("found net device\n");
+
+            dev->mmio_config = mmio;
+            dev->config_ptr = (void *)mmio->config;
+
+            status_t err = virtio_net_init(dev, mmio->host_features);
+            if (err >= 0) {
+                // good device
+                dev->valid = true;
+
+                if (dev->irq_driver_callback)
+                    unmask_interrupt(dev->irq);
+            }
+        }
+#endif // WITH_DEV_VIRTIO_NET
+#if WITH_DEV_VIRTIO_GPU
+        if (mmio->device_id == 0x10) { // virtio-gpu
+            LTRACEF("found gpu device\n");
+
+            dev->mmio_config = mmio;
+            dev->config_ptr = (void *)mmio->config;
+
+            status_t err = virtio_gpu_init(dev, mmio->host_features);
+            if (err >= 0) {
+                // good device
+                dev->valid = true;
+
+                if (dev->irq_driver_callback)
+                    unmask_interrupt(dev->irq);
+
+                virtio_gpu_start(dev);
+            }
+        }
+#endif // WITH_DEV_VIRTIO_GPU
+
+        if (dev->valid)
+            found++;
+    }
+
+    return found;
+}
+
+void virtio_free_desc(struct virtio_device *dev, uint ring_index, uint16_t desc_index)
+{
+    LTRACEF("dev %p ring %u index %u free_count %u\n", dev, ring_index, desc_index, dev->ring[ring_index].free_count);
+    dev->ring[ring_index].desc[desc_index].next = dev->ring[ring_index].free_list;
+    dev->ring[ring_index].free_list = desc_index;
+    dev->ring[ring_index].free_count++;
+}
+
+uint16_t virtio_alloc_desc(struct virtio_device *dev, uint ring_index)
+{
+    if (dev->ring[ring_index].free_count == 0)
+        return 0xffff;
+
+    DEBUG_ASSERT(dev->ring[ring_index].free_list != 0xffff);
+
+    uint16_t i = dev->ring[ring_index].free_list;
+    struct vring_desc *desc = &dev->ring[ring_index].desc[i];
+    dev->ring[ring_index].free_list = desc->next;
+
+    dev->ring[ring_index].free_count--;
+
+    return i;
+}
+
+struct vring_desc *virtio_alloc_desc_chain(struct virtio_device *dev, uint ring_index, size_t count, uint16_t *start_index)
+{
+    if (dev->ring[ring_index].free_count < count)
+        return NULL;
+
+    /* start popping entries off the chain */
+    struct vring_desc *last = 0;
+    uint16_t last_index = 0;
+    while (count > 0) {
+        uint16_t i = dev->ring[ring_index].free_list;
+        struct vring_desc *desc = &dev->ring[ring_index].desc[i];
+
+        dev->ring[ring_index].free_list = desc->next;
+        dev->ring[ring_index].free_count--;
+
+        if (last) {
+            desc->flags = VRING_DESC_F_NEXT;
+            desc->next = last_index;
+        } else {
+            // first one
+            desc->flags = 0;
+            desc->next = 0;
+        }
+        last = desc;
+        last_index = i;
+        count--;
+    }
+
+    if (start_index)
+        *start_index = last_index;
+
+    return last;
+}
+
+void virtio_submit_chain(struct virtio_device *dev, uint ring_index, uint16_t desc_index)
+{
+    LTRACEF("dev %p, ring %u, desc %u\n", dev, ring_index, desc_index);
+
+    /* add the chain to the available list */
+    struct vring_avail *avail = dev->ring[ring_index].avail;
+
+    avail->ring[avail->idx & dev->ring[ring_index].num_mask] = desc_index;
+    DSB;
+    avail->idx++;
+
+#if LOCAL_TRACE
+    hexdump(avail, 16);
+#endif
+}
+
+void virtio_kick(struct virtio_device *dev, uint ring_index)
+{
+    LTRACEF("dev %p, ring %u\n", dev, ring_index);
+
+    dev->mmio_config->queue_notify = ring_index;
+    DSB;
+}
+
+status_t virtio_alloc_ring(struct virtio_device *dev, uint index, uint16_t len)
+{
+    LTRACEF("dev %p, index %u, len %u\n", dev, index, len);
+
+    DEBUG_ASSERT(dev);
+    DEBUG_ASSERT(len > 0 && ispow2(len));
+    DEBUG_ASSERT(index < MAX_VIRTIO_RINGS);
+
+    if (len == 0 || !ispow2(len))
+        return ERR_INVALID_ARGS;
+
+    struct vring *ring = &dev->ring[index];
+
+    /* allocate a ring */
+    size_t size = vring_size(len, PAGE_SIZE);
+    LTRACEF("need %zu bytes\n", size);
+
+#if WITH_KERNEL_VM
+    void *vptr;
+    status_t err = vmm_alloc_contiguous(vmm_get_kernel_aspace(), "virtio_ring", size, &vptr, 0, 0, ARCH_MMU_FLAG_UNCACHED_DEVICE);
+    if (err < 0)
+        return ERR_NO_MEMORY;
+
+    LTRACEF("allocated virtio_ring at va %p\n", vptr);
+
+    /* compute the physical address */
+    paddr_t pa;
+    err = arch_mmu_query((vaddr_t)vptr, &pa, NULL);
+    if (err < 0) {
+        return ERR_NO_MEMORY;
+    }
+
+    LTRACEF("virtio_ring at pa 0x%lx\n", pa);
+#else
+    void *vptr = memalign(PAGE_SIZE, size);
+    if (!vptr)
+        return ERR_NO_MEMORY;
+
+    LTRACEF("ptr %p\n", vptr);
+    memset(vptr, 0, size);
+
+    /* compute the physical address */
+    paddr_t pa = (paddr_t)vptr;
+#endif
+
+    /* initialize the ring */
+    vring_init(ring, len, vptr, PAGE_SIZE);
+    dev->ring[index].free_list = 0xffff;
+    dev->ring[index].free_count = 0;
+
+    /* add all the descriptors to the free list */
+    for (uint i = 0; i < len; i++) {
+        virtio_free_desc(dev, index, i);
+    }
+
+    /* register the ring with the device */
+    DEBUG_ASSERT(dev->mmio_config);
+    dev->mmio_config->guest_page_size = PAGE_SIZE;
+    dev->mmio_config->queue_sel = index;
+    dev->mmio_config->queue_num = len;
+    dev->mmio_config->queue_align = PAGE_SIZE;
+    dev->mmio_config->queue_pfn = pa / PAGE_SIZE;
+
+    /* mark the ring active */
+    dev->active_rings_bitmap |= (1 << index);
+
+    return NO_ERROR;
+}
+
+void virtio_reset_device(struct virtio_device *dev)
+{
+    dev->mmio_config->status = 0;
+}
+
+void virtio_status_acknowledge_driver(struct virtio_device *dev)
+{
+    dev->mmio_config->status |= VIRTIO_STATUS_ACKNOWLEDGE | VIRTIO_STATUS_DRIVER;
+}
+
+void virtio_status_driver_ok(struct virtio_device *dev)
+{
+    dev->mmio_config->status |= VIRTIO_STATUS_DRIVER_OK;
+}
+
+void virtio_init(uint level)
+{
+}
+
+LK_INIT_HOOK(virtio, &virtio_init, LK_INIT_LEVEL_THREADING);
+
diff --git a/src/bsp/lk/dev/virtio/virtio_priv.h b/src/bsp/lk/dev/virtio/virtio_priv.h
new file mode 100644
index 0000000..28437c8
--- /dev/null
+++ b/src/bsp/lk/dev/virtio/virtio_priv.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2014 Travis Geiselbrecht
+ *
+ * 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
+
+#include <compiler.h>
+#include <stdint.h>
+
+struct virtio_mmio_config {
+/* 0x00 */  uint32_t magic;
+            uint32_t version;
+            uint32_t device_id;
+            uint32_t vendor_id;
+/* 0x10 */  uint32_t host_features;
+            uint32_t host_features_sel;
+            uint32_t __reserved0[2];
+/* 0x20 */  uint32_t guest_features;
+            uint32_t guest_features_sel;
+            uint32_t guest_page_size;
+            uint32_t __reserved1[1];
+/* 0x30 */  uint32_t queue_sel;
+            uint32_t queue_num_max;
+            uint32_t queue_num;
+            uint32_t queue_align;
+/* 0x40 */  uint32_t queue_pfn;
+            uint32_t __reserved2[3];
+/* 0x50 */  uint32_t queue_notify;
+            uint32_t __reserved3[3];
+/* 0x60 */  uint32_t interrupt_status;
+            uint32_t interrupt_ack;
+            uint32_t __reserved4[2];
+/* 0x70 */  uint32_t status;
+            uint8_t __reserved5[0x8c];
+/* 0x100 */ uint32_t config[0];
+};
+
+STATIC_ASSERT(sizeof(struct virtio_mmio_config) == 0x100);
+
+#define VIRTIO_MMIO_MAGIC 0x74726976 // 'virt'
+
+#define VIRTIO_STATUS_ACKNOWLEDGE (1<<0)
+#define VIRTIO_STATUS_DRIVER      (1<<1)
+#define VIRTIO_STATUS_DRIVER_OK   (1<<2)
+#define VIRTIO_STATUS_FEATURES_OK (1<<3)
+#define VIRTIO_STATUS_DEVICE_NEEDS_RESET (1<<6)
+#define VIRTIO_STATUS_FAILED      (1<<7)