[Feature]add MT2731_MP2_MR2_SVN388 baseline version

Change-Id: Ief04314834b31e27effab435d3ca8ba33b499059
diff --git a/src/bsp/lk/app/tests/benchmarks.c b/src/bsp/lk/app/tests/benchmarks.c
new file mode 100644
index 0000000..30de163
--- /dev/null
+++ b/src/bsp/lk/app/tests/benchmarks.c
@@ -0,0 +1,251 @@
+/*
+ * Copyright (c) 2008-2012 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 <stdio.h>
+#include <rand.h>
+#include <err.h>
+#include <stdlib.h>
+#include <string.h>
+#include <app/tests.h>
+#include <kernel/thread.h>
+#include <kernel/mutex.h>
+#include <kernel/semaphore.h>
+#include <kernel/event.h>
+#include <platform.h>
+
+const size_t BUFSIZE = (1024*1024);
+const uint ITER = 1024;
+
+__NO_INLINE static void bench_set_overhead(void)
+{
+    uint32_t *buf = malloc(BUFSIZE);
+
+    uint count = arch_cycle_count();
+    for (uint i = 0; i < ITER; i++) {
+        __asm__ volatile("");
+    }
+    count = arch_cycle_count() - count;
+
+    printf("took %u cycles overhead to loop %u times\n",
+           count, ITER);
+
+    free(buf);
+}
+
+__NO_INLINE static void bench_memset(void)
+{
+    void *buf = malloc(BUFSIZE);
+
+    uint count = arch_cycle_count();
+    for (uint i = 0; i < ITER; i++) {
+        memset(buf, 0, BUFSIZE);
+    }
+    count = arch_cycle_count() - count;
+
+    printf("took %u cycles to memset a buffer of size %u %d times (%u bytes), %f bytes/cycle\n",
+           count, BUFSIZE, ITER, BUFSIZE * ITER, (BUFSIZE * ITER) / (float)count);
+
+    free(buf);
+}
+
+#define bench_cset(type) \
+__NO_INLINE static void bench_cset_##type(void) \
+{ \
+    type *buf = malloc(BUFSIZE); \
+ \
+    uint count = arch_cycle_count(); \
+    for (uint i = 0; i < ITER; i++) { \
+        for (uint j = 0; j < BUFSIZE / sizeof(*buf); j++) { \
+            buf[j] = 0; \
+        } \
+    } \
+    count = arch_cycle_count() - count; \
+ \
+    printf("took %u cycles to manually clear a buffer using wordsize %d of size %u %d times (%u bytes), %f bytes/cycle\n", \
+           count, sizeof(*buf), BUFSIZE, ITER, BUFSIZE * ITER, (BUFSIZE * ITER) / (float)count); \
+ \
+    free(buf); \
+}
+
+bench_cset(uint8_t)
+bench_cset(uint16_t)
+bench_cset(uint32_t)
+bench_cset(uint64_t)
+
+__NO_INLINE static void bench_cset_wide(void)
+{
+    uint32_t *buf = malloc(BUFSIZE);
+
+    uint count = arch_cycle_count();
+    for (uint i = 0; i < ITER; i++) {
+        for (uint j = 0; j < BUFSIZE / sizeof(*buf) / 8; j++) {
+            buf[j*8] = 0;
+            buf[j*8+1] = 0;
+            buf[j*8+2] = 0;
+            buf[j*8+3] = 0;
+            buf[j*8+4] = 0;
+            buf[j*8+5] = 0;
+            buf[j*8+6] = 0;
+            buf[j*8+7] = 0;
+        }
+    }
+    count = arch_cycle_count() - count;
+
+    printf("took %u cycles to manually clear a buffer of size %u %d times 8 words at a time (%u bytes), %f bytes/cycle\n",
+           count, BUFSIZE, ITER, BUFSIZE * ITER, (BUFSIZE * ITER) / (float)count);
+
+    free(buf);
+}
+
+__NO_INLINE static void bench_memcpy(void)
+{
+    uint8_t *buf = calloc(1, BUFSIZE);
+
+    uint count = arch_cycle_count();
+    for (uint i = 0; i < ITER; i++) {
+        memcpy(buf, buf + BUFSIZE / 2, BUFSIZE / 2);
+    }
+    count = arch_cycle_count() - count;
+
+    printf("took %u cycles to memcpy a buffer of size %u %d times (%u source bytes), %f source bytes/cycle\n",
+           count, BUFSIZE / 2, ITER, BUFSIZE / 2 * ITER, (BUFSIZE / 2 * ITER) / (float)count);
+
+    free(buf);
+}
+
+#if ARCH_ARM
+__NO_INLINE static void arm_bench_cset_stm(void)
+{
+    uint32_t *buf = malloc(BUFSIZE);
+
+    uint count = arch_cycle_count();
+    for (uint i = 0; i < ITER; i++) {
+        for (uint j = 0; j < BUFSIZE / sizeof(*buf) / 8; j++) {
+            __asm__ volatile(
+                "stm    %0, {r0-r7};"
+                :: "r" (&buf[j*8])
+            );
+        }
+    }
+    count = arch_cycle_count() - count;
+
+    printf("took %u cycles to manually clear a buffer of size %u %d times 8 words at a time using stm (%u bytes), %f bytes/cycle\n",
+           count, BUFSIZE, ITER, BUFSIZE * ITER, (BUFSIZE * ITER) / (float)count);
+
+    free(buf);
+}
+
+#if       (__CORTEX_M >= 0x03)
+__NO_INLINE static void arm_bench_multi_issue(void)
+{
+    uint32_t cycles;
+    uint32_t a = 0, b = 0, c = 0, d = 0, e = 0, f = 0, g = 0, h = 0;
+#define ITER 1000000
+    uint count = ITER;
+    cycles = arch_cycle_count();
+    while (count--) {
+        asm volatile ("");
+        asm volatile ("add %0, %0, %0" : "=r" (a) : "r" (a));
+        asm volatile ("add %0, %0, %0" : "=r" (b) : "r" (b));
+        asm volatile ("and %0, %0, %0" : "=r" (c) : "r" (c));
+        asm volatile ("mov %0, %0" : "=r" (d) : "r" (d));
+        asm volatile ("orr %0, %0, %0" : "=r" (e) : "r" (e));
+        asm volatile ("add %0, %0, %0" : "=r" (f) : "r" (f));
+        asm volatile ("and %0, %0, %0" : "=r" (g) : "r" (g));
+        asm volatile ("mov %0, %0" : "=r" (h) : "r" (h));
+    }
+    cycles = arch_cycle_count() - cycles;
+
+    printf("took %u cycles to issue 8 integer ops (%f cycles/iteration)\n", cycles, (float)cycles / ITER);
+#undef ITER
+}
+#endif // __CORTEX_M
+#endif // ARCH_ARM
+
+#if WITH_LIB_LIBM
+#include <math.h>
+
+__NO_INLINE static void bench_sincos(void)
+{
+    printf("touching the floating point unit\n");
+    __UNUSED volatile double _hole = sin(0);
+
+    uint count = arch_cycle_count();
+    __UNUSED double a = sin(2.0);
+    count = arch_cycle_count() - count;
+    printf("took %u cycles for sin()\n", count);
+
+    count = arch_cycle_count();
+    a = cos(2.0);
+    count = arch_cycle_count() - count;
+    printf("took %u cycles for cos()\n", count);
+
+    count = arch_cycle_count();
+    a = sinf(2.0);
+    count = arch_cycle_count() - count;
+    printf("took %u cycles for sinf()\n", count);
+
+    count = arch_cycle_count();
+    a = cosf(2.0);
+    count = arch_cycle_count() - count;
+    printf("took %u cycles for cosf()\n", count);
+
+    count = arch_cycle_count();
+    a = sqrt(1234567.0);
+    count = arch_cycle_count() - count;
+    printf("took %u cycles for sqrt()\n", count);
+
+    count = arch_cycle_count();
+    a = sqrtf(1234567.0f);
+    count = arch_cycle_count() - count;
+    printf("took %u cycles for sqrtf()\n", count);
+}
+
+#endif // WITH_LIB_LIBM
+
+int benchmarks(int argc, const cmd_args *argv)
+{
+    bench_set_overhead();
+    bench_memset();
+    bench_memcpy();
+
+    bench_cset_uint8_t();
+    bench_cset_uint16_t();
+    bench_cset_uint32_t();
+    bench_cset_uint64_t();
+    bench_cset_wide();
+
+#if ARCH_ARM
+    arm_bench_cset_stm();
+
+#if       (__CORTEX_M >= 0x03)
+    arm_bench_multi_issue();
+#endif
+#endif
+#if WITH_LIB_LIBM
+    bench_sincos();
+#endif
+
+    return NO_ERROR;
+}
+
diff --git a/src/bsp/lk/app/tests/cache_tests.c b/src/bsp/lk/app/tests/cache_tests.c
new file mode 100644
index 0000000..46c8a93
--- /dev/null
+++ b/src/bsp/lk/app/tests/cache_tests.c
@@ -0,0 +1,88 @@
+/*
+ * 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.
+ */
+#if ARM_WITH_CACHE
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <arch.h>
+#include <arch/ops.h>
+#include <lib/console.h>
+#include <platform.h>
+
+static void bench_cache(size_t bufsize, uint8_t* buf)
+{
+    lk_bigtime_t t;
+    bool do_free;
+
+    if (buf == 0) {
+        buf = memalign(PAGE_SIZE, bufsize);
+        do_free = true;
+    } else {
+        do_free = false;
+    }
+
+    printf("buf %p, size %zu\n", buf, bufsize);
+
+    if (!buf)
+        return;
+
+    t = current_time_hires();
+    arch_clean_cache_range((addr_t)buf, bufsize);
+    t = current_time_hires() - t;
+
+    printf("took %llu usecs to clean %d bytes (cold)\n", t, bufsize);
+
+    memset(buf, 0x99, bufsize);
+
+    t = current_time_hires();
+    arch_clean_cache_range((addr_t)buf, bufsize);
+    t = current_time_hires() - t;
+
+    if (do_free)
+        free(buf);
+
+    printf("took %llu usecs to clean %d bytes (hot)\n", t, bufsize);
+}
+
+static int cache_tests(int argc, const cmd_args *argv)
+{
+    uint8_t* buf;
+    buf = (uint8_t *)((argc > 1) ? argv[1].u : 0UL);
+
+    printf("testing cache\n");
+
+    bench_cache(2*1024, buf);
+    bench_cache(64*1024, buf);
+    bench_cache(256*1024, buf);
+    bench_cache(1*1024*1024, buf);
+    bench_cache(8*1024*1024, buf);
+    return 0;
+}
+
+STATIC_COMMAND_START
+STATIC_COMMAND("cache_tests", "test/bench the cpu cache", &cache_tests)
+STATIC_COMMAND_END(cache_tests);
+
+#endif
diff --git a/src/bsp/lk/app/tests/clock_tests.c b/src/bsp/lk/app/tests/clock_tests.c
new file mode 100644
index 0000000..b5e7f74
--- /dev/null
+++ b/src/bsp/lk/app/tests/clock_tests.c
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2012 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 <stdio.h>
+#include <rand.h>
+#include <err.h>
+#include <app/tests.h>
+#include <kernel/thread.h>
+#include <kernel/mutex.h>
+#include <kernel/semaphore.h>
+#include <kernel/event.h>
+#include <platform.h>
+
+int clock_tests(int argc, const cmd_args *argv)
+{
+	uint32_t c;
+	lk_time_t t;
+	lk_bigtime_t t2;
+
+	thread_sleep(100);
+	c = arch_cycle_count();
+	t = current_time();
+	c = arch_cycle_count() - c;
+	printf("%u cycles per current_time()\n", c);
+
+	thread_sleep(100);
+	c = arch_cycle_count();
+	t2 = current_time_hires();
+	c = arch_cycle_count() - c;
+	printf("%u cycles per current_time_hires()\n", c);
+
+	printf("making sure time never goes backwards\n");
+	{
+		printf("testing current_time()\n");
+		lk_time_t start = current_time();
+		lk_time_t last = start;
+		for (;;) {
+			t = current_time();
+			//printf("%lu %lu\n", last, t);
+			if (TIME_LT(t, last)) {
+				printf("WARNING: time ran backwards: %lu < %lu\n", t, last);
+				last = t;
+				continue;
+			}
+			last = t;
+			if (last - start > 5000)
+				break;
+		}
+	}
+	{
+		printf("testing current_time_hires()\n");
+		lk_bigtime_t start = current_time_hires();
+		lk_bigtime_t last = start;
+		for (;;) {
+			t2 = current_time_hires();
+			//printf("%llu %llu\n", last, t2);
+			if (t2 < last) {
+				printf("WARNING: time ran backwards: %llu < %llu\n", t2, last);
+				last = t2;
+				continue;
+			}
+			last = t2;
+			if (last - start > 5000000)
+				break;
+		}
+	}
+
+	printf("making sure current_time() and current_time_hires() are always the same base\n");
+	{
+		lk_time_t start = current_time();
+		for (;;) {
+			t = current_time();
+			t2 = current_time_hires();
+			if (t > ((t2 + 500) / 1000)) {
+				printf("WARNING: current_time() ahead of current_time_hires() %lu %llu\n", t, t2);
+			}
+			if (t - start > 5000)
+				break;
+		}
+	}
+
+	printf("counting to 5, in one second intervals\n");
+	for (int i = 0; i < 5; i++) {
+		thread_sleep(1000);
+		printf("%d\n", i + 1);
+	}
+
+	printf("measuring cpu clock against current_time_hires()\n");
+	for (int i = 0; i < 5; i++) {
+		uint cycles = arch_cycle_count();
+		lk_bigtime_t start = current_time_hires();
+		while ((current_time_hires() - start) < 1000000)
+			;
+		cycles = arch_cycle_count() - cycles;
+		printf("%u cycles per second\n", cycles);
+	}
+
+	return NO_ERROR;
+}
+
+// vim: set noexpandtab:
diff --git a/src/bsp/lk/app/tests/fibo.c b/src/bsp/lk/app/tests/fibo.c
new file mode 100644
index 0000000..3c3320f
--- /dev/null
+++ b/src/bsp/lk/app/tests/fibo.c
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2012 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 <stdio.h>
+#include <rand.h>
+#include <err.h>
+#include <app/tests.h>
+#include <kernel/thread.h>
+#include <kernel/mutex.h>
+#include <kernel/semaphore.h>
+#include <kernel/event.h>
+#include <platform.h>
+
+static int fibo_thread(void *argv)
+{
+	long fibo = (intptr_t)argv;
+
+	thread_t *t[2];
+
+	if (fibo == 0)
+		return 0;
+	if (fibo == 1)
+		return 1;
+
+	char name[32];
+	snprintf(name, sizeof(name), "fibo %lu", fibo - 1);
+	t[0] = thread_create(name, &fibo_thread, (void *)(fibo - 1), DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
+	if (!t[0]) {
+		printf("error creating thread for fibo %d\n", fibo-1);
+		return 0;
+	}
+	snprintf(name, sizeof(name), "fibo %lu", fibo - 2);
+	t[1] = thread_create(name, &fibo_thread, (void *)(fibo - 2), DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
+	if (!t[1]) {
+		printf("error creating thread for fibo %d\n", fibo-2);
+		thread_resume(t[0]);
+		thread_join(t[0], NULL, INFINITE_TIME);
+		return 0;
+	}
+
+	thread_resume(t[0]);
+	thread_resume(t[1]);
+
+	int retcode0, retcode1;
+
+	thread_join(t[0], &retcode0, INFINITE_TIME);
+	thread_join(t[1], &retcode1, INFINITE_TIME);
+
+	return retcode0 + retcode1;
+}
+
+int fibo(int argc, const cmd_args *argv)
+{
+
+	if (argc < 2) {
+		printf("not enough args\n");
+		return -1;
+	}
+
+	lk_time_t tim = current_time();
+
+	thread_t *t = thread_create("fibo", &fibo_thread, (void *)(uintptr_t)argv[1].u, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
+	thread_resume(t);
+
+	int retcode;
+	thread_join(t, &retcode, INFINITE_TIME);
+
+	tim = current_time() - tim;
+
+	printf("fibo %d\n", retcode);
+	printf("took %u msecs to calculate\n", tim);
+
+	return NO_ERROR;
+}
+
+// vim: set noexpandtab:
+
diff --git a/src/bsp/lk/app/tests/float.c b/src/bsp/lk/app/tests/float.c
new file mode 100755
index 0000000..5d3d8c6
--- /dev/null
+++ b/src/bsp/lk/app/tests/float.c
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2013-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.
+ */
+#if ARM_WITH_VFP || ARCH_ARM64 || X86_WITH_FPU
+
+#include <stdio.h>
+#include <rand.h>
+#include <err.h>
+#include <lib/console.h>
+#include <app/tests.h>
+#include <kernel/thread.h>
+#include <kernel/mutex.h>
+#include <kernel/semaphore.h>
+#include <kernel/event.h>
+#include <platform.h>
+
+extern void float_vfp_arm_instruction_test(void);
+extern void float_vfp_thumb_instruction_test(void);
+extern void float_neon_arm_instruction_test(void);
+extern void float_neon_thumb_instruction_test(void);
+
+/* optimize this function to cause it to try to use a lot of registers */
+__OPTIMIZE("O3")
+static int float_thread(void *arg)
+{
+    double *val = arg;
+    uint i, j;
+
+    double a[16];
+
+    /* do a bunch of work with floating point to test context switching */
+    a[0] = *val;
+    for (i = 1; i < countof(a); i++) {
+        a[i] = a[i-1] * 1.01;
+    }
+
+    for (i = 0; i < 1000000; i++) {
+        a[0] += i;
+        for (j = 1; j < countof(a); j++) {
+            a[j] += a[j-1] * 0.00001;
+        }
+    }
+
+    *val = a[countof(a) - 1];
+
+    return 1;
+}
+
+#if ARCH_ARM
+static void arm_float_instruction_trap_test(void)
+{
+    printf("testing fpu trap\n");
+
+#if !ARM_ONLY_THUMB
+    float_vfp_arm_instruction_test();
+    float_neon_arm_instruction_test();
+#endif
+    float_vfp_thumb_instruction_test();
+    float_neon_thumb_instruction_test();
+
+    printf("if we got here, we probably decoded everything properly\n");
+}
+#endif
+
+static void float_tests(void)
+{
+    printf("floating point test:\n");
+
+    /* test lazy fpu load on separate thread */
+    thread_t *t[8];
+    double val[countof(t)];
+
+    printf("creating %u floating point threads\n", countof(t));
+    for (uint i = 0; i < countof(t); i++) {
+        val[i] = i;
+        t[i] = thread_create("float", &float_thread, &val[i], LOW_PRIORITY, DEFAULT_STACK_SIZE);
+        thread_resume(t[i]);
+    }
+
+    int res;
+    for (uint i = 0; i < countof(t); i++) {
+        thread_join(t[i], &res, INFINITE_TIME);
+        printf("float thread %u returns %d, val %f\n", i, res, val[i]);
+    }
+    printf("the above values should be close\n");
+
+#if ARCH_ARM
+    /* test all the instruction traps */
+    arm_float_instruction_trap_test();
+#endif
+}
+
+STATIC_COMMAND_START
+STATIC_COMMAND("float_tests", "floating point test", (console_cmd)&float_tests)
+STATIC_COMMAND_END(float_tests);
+
+#endif // ARM_WITH_VFP || ARCH_ARM64
diff --git a/src/bsp/lk/app/tests/float_instructions.S b/src/bsp/lk/app/tests/float_instructions.S
new file mode 100644
index 0000000..297fcbd
--- /dev/null
+++ b/src/bsp/lk/app/tests/float_instructions.S
@@ -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.
+ */
+#include <asm.h>
+
+#if ARM_WITH_VFP
+
+.fpu neon
+.syntax unified
+
+.macro disable, scratchreg
+    vmrs  \scratchreg, fpexc
+    bic   \scratchreg, #(1<<30)
+    vmsr  fpexc, \scratchreg
+.endm
+
+.macro vfp_instructions
+    disable r12
+    vadd.f32 s0, s0, s0
+
+    disable r12
+    vadd.f64 d0, d0, d0
+
+    disable r12
+    ldr     r0, =float_test_scratch
+    vldr    s0, [r0]
+.endm
+
+.macro neon_instructions
+    disable r12
+    vadd.f32 q0, q0, q0
+
+    disable r12
+    ldr     r0, =float_test_scratch
+    vld1.f32 { q0 }, [r0]
+
+    disable r12
+    vmov    s0, r0
+.endm
+
+#if !ARM_ONLY_THUMB
+.arm
+
+FUNCTION(float_vfp_arm_instruction_test)
+    vfp_instructions
+    bx  lr
+
+FUNCTION(float_neon_arm_instruction_test)
+    neon_instructions
+    bx  lr
+#endif
+
+.thumb
+
+FUNCTION(float_vfp_thumb_instruction_test)
+    vfp_instructions
+    bx  lr
+
+FUNCTION(float_neon_thumb_instruction_test)
+    neon_instructions
+    bx  lr
+
+.data
+LOCAL_DATA(float_test_scratch)
+    .word 0
+    .word 0
+
+#endif // ARM_WITH_VFP
diff --git a/src/bsp/lk/app/tests/float_print_host.c b/src/bsp/lk/app/tests/float_print_host.c
new file mode 100644
index 0000000..1176876
--- /dev/null
+++ b/src/bsp/lk/app/tests/float_print_host.c
@@ -0,0 +1,17 @@
+#include <stdio.h>
+
+#define countof(a) (sizeof(a) / sizeof((a)[0]))
+
+#include "float_test_vec.c"
+
+int main(void)
+{
+    printf("floating point printf tests\n");
+
+    for (size_t i = 0; i < float_test_vec_size; i++) {
+        PRINT_FLOAT;
+    }
+
+    return 0;
+}
+
diff --git a/src/bsp/lk/app/tests/float_test_vec.c b/src/bsp/lk/app/tests/float_test_vec.c
new file mode 100644
index 0000000..d7d77c1
--- /dev/null
+++ b/src/bsp/lk/app/tests/float_test_vec.c
@@ -0,0 +1,81 @@
+/*
+ * 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 <stdint.h>
+
+union double_int {
+    double d;
+    uint64_t i;
+};
+
+static const union double_int float_test_vec[] = {
+    { .d = -2.0 },
+    { .d = -1.0 },
+    { .d = -0.5 },
+    { .d = -0.0 },
+    { .d = 0.0 },
+    { .d = 0.01 },
+    { .d = 0.1 },
+    { .d = 0.2 },
+    { .d = 0.25 },
+    { .d = 0.5 },
+    { .d = 0.75 },
+    { .d = 1.0 },
+    { .d = 2.0 },
+    { .d = 3.0 },
+    { .d = 10.0 },
+    { .d = 100.0 },
+    { .d = 123456.0 },
+    { .d = -123456.0 },
+    { .d = 546.5645644531f },
+    { .d = -546.5645644531f },
+    { .d = 0.12345 },
+    { .d = 0.0000012345 },
+    { .d = 0.0000019999 },
+    { .d = 0.0000015 },
+    { .i = 0x4005bf0a8b145649ULL }, // e
+    { .i = 0x400921fb54442d18ULL }, // pi
+    { .i = 0x43f0000000000000ULL }, // 2^64
+    { .i = 0x7fefffffffffffffULL }, // largest normalized
+    { .i = 0x0010000000000000ULL }, // least positive normalized
+    { .i = 0x0000000000000001ULL }, // smallest possible denorm
+    { .i = 0x000fffffffffffffULL }, // largest possible denorm
+    { .i = 0x7ff0000000000001ULL }, // smallest SNAn
+    { .i = 0x7ff7ffffffffffffULL }, // largest SNAn
+    { .i = 0x7ff8000000000000ULL }, // smallest QNAn
+    { .i = 0x7fffffffffffffffULL }, // largest QNAn
+    { .i = 0xfff0000000000000ULL }, // -infinity
+    { .i = 0x7ff0000000000000ULL }, // +infinity
+};
+
+#define countof(a) (sizeof(a) / sizeof((a)[0]))
+static const unsigned int float_test_vec_size = countof(float_test_vec);
+
+#define PRINT_FLOAT \
+        printf("0x%016llx %f %F %a %A\n", \
+                float_test_vec[i], \
+                *(const double *)&float_test_vec[i], \
+                *(const double *)&float_test_vec[i], \
+                *(const double *)&float_test_vec[i], \
+                *(const double *)&float_test_vec[i])
+
diff --git a/src/bsp/lk/app/tests/include/app/tests.h b/src/bsp/lk/app/tests/include/app/tests.h
new file mode 100644
index 0000000..ea65bf8
--- /dev/null
+++ b/src/bsp/lk/app/tests/include/app/tests.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2008-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.
+ */
+#ifndef __APP_TESTS_H
+#define __APP_TESTS_H
+
+#include <lib/console.h>
+
+int port_tests(int argc, const cmd_args *argv);
+int fibo(int argc, const cmd_args *argv);
+int spinner(int argc, const cmd_args *argv);
+int thread_tests(int argc, const cmd_args *argv);
+int benchmarks(int argc, const cmd_args *argv);
+int clock_tests(int argc, const cmd_args *argv);
+int printf_tests(int argc, const cmd_args *argv);
+int printf_tests_float(int argc, const cmd_args *argv);
+
+#endif
+
diff --git a/src/bsp/lk/app/tests/mem_tests.c b/src/bsp/lk/app/tests/mem_tests.c
new file mode 100644
index 0000000..0b76e64
--- /dev/null
+++ b/src/bsp/lk/app/tests/mem_tests.c
@@ -0,0 +1,242 @@
+/*
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <err.h>
+#include <arch.h>
+#include <arch/ops.h>
+#include <lib/console.h>
+#include <platform.h>
+#include <debug.h>
+
+#if WITH_KERNEL_VM
+#include <kernel/vm.h>
+#endif
+
+static void mem_test_fail(void *ptr, uint32_t should, uint32_t is)
+{
+    printf("ERROR at %p: should be 0x%x, is 0x%x\n", ptr, should, is);
+
+    ptr = (void *)ROUNDDOWN((uintptr_t)ptr, 64);
+    hexdump(ptr, 128);
+}
+
+static status_t do_pattern_test(void *ptr, size_t len, uint32_t pat)
+{
+    volatile uint32_t *vbuf32 = ptr;
+    size_t i;
+
+    printf("\tpattern 0x%08x\n", pat);
+    for (i = 0; i < len / 4; i++) {
+        vbuf32[i] = pat;
+    }
+
+    for (i = 0; i < len / 4; i++) {
+        if (vbuf32[i] != pat) {
+            mem_test_fail((void *)&vbuf32[i], pat, vbuf32[i]);
+            return ERR_GENERIC;
+        }
+    }
+
+    return NO_ERROR;
+}
+
+static status_t do_moving_inversion_test(void *ptr, size_t len, uint32_t pat)
+{
+    volatile uint32_t *vbuf32 = ptr;
+    size_t i;
+
+    printf("\tpattern 0x%08x\n", pat);
+
+    /* fill memory */
+    for (i = 0; i < len / 4; i++) {
+        vbuf32[i] = pat;
+    }
+
+    /* from the bottom, walk through each cell, inverting the value */
+    //printf("\t\tbottom up invert\n");
+    for (i = 0; i < len / 4; i++) {
+        if (vbuf32[i] != pat) {
+            mem_test_fail((void *)&vbuf32[i], pat, vbuf32[i]);
+            return ERR_GENERIC;
+        }
+
+        vbuf32[i] = ~pat;
+    }
+
+    /* repeat, walking from top down */
+    //printf("\t\ttop down invert\n");
+    for (i = len / 4; i > 0; i--) {
+        if (vbuf32[i-1] != ~pat) {
+            mem_test_fail((void *)&vbuf32[i-1], ~pat, vbuf32[i-1]);
+            return ERR_GENERIC;
+        }
+
+        vbuf32[i-1] = pat;
+    }
+
+    /* verify that we have the original pattern */
+    //printf("\t\tfinal test\n");
+    for (i = 0; i < len / 4; i++) {
+        if (vbuf32[i] != pat) {
+            mem_test_fail((void *)&vbuf32[i], pat, vbuf32[i]);
+            return ERR_GENERIC;
+        }
+    }
+
+    return NO_ERROR;
+}
+
+static void do_mem_tests(void *ptr, size_t len)
+{
+    size_t i;
+
+    /* test 1: simple write address to memory, read back */
+    printf("test 1: simple address write, read back\n");
+    volatile uint32_t *vbuf32 = ptr;
+    for (i = 0; i < len / 4; i++) {
+        vbuf32[i] = i;
+    }
+
+    for (i = 0; i < len / 4; i++) {
+        if (vbuf32[i] != i) {
+            mem_test_fail((void *)&vbuf32[i], i, vbuf32[i]);
+            goto out;
+        }
+    }
+
+    /* test 2: write various patterns, read back */
+    printf("test 2: write patterns, read back\n");
+
+    static const uint32_t pat[] = {
+        0x0, 0xffffffff,
+        0xaaaaaaaa, 0x55555555,
+    };
+
+    for (size_t p = 0; p < countof(pat); p++) {
+        if (do_pattern_test(ptr, len, pat[p]) < 0)
+            goto out;
+    }
+    // shift bits through 32bit word
+    for (uint32_t p = 1; p != 0; p <<= 1) {
+        if (do_pattern_test(ptr, len, p) < 0)
+            goto out;
+    }
+    // shift bits through 16bit word, invert top of 32bit
+    for (uint16_t p = 1; p != 0; p <<= 1) {
+        if (do_pattern_test(ptr, len, ((~p) << 16) | p) < 0)
+            goto out;
+    }
+
+    /* test 3: moving inversion, patterns */
+    printf("test 3: moving inversions with patterns\n");
+    for (size_t p = 0; p < countof(pat); p++) {
+        if (do_moving_inversion_test(ptr, len, pat[p]) < 0)
+            goto out;
+
+    }
+    // shift bits through 32bit word
+    for (uint32_t p = 1; p != 0; p <<= 1) {
+        if (do_moving_inversion_test(ptr, len, p) < 0)
+            goto out;
+    }
+    // shift bits through 16bit word, invert top of 32bit
+    for (uint16_t p = 1; p != 0; p <<= 1) {
+        if (do_moving_inversion_test(ptr, len, ((~p) << 16) | p) < 0)
+            goto out;
+    }
+
+out:
+    printf("done with tests\n");
+}
+
+static int mem_test(int argc, const cmd_args *argv)
+{
+    if (argc < 2) {
+        printf("not enough arguments\n");
+usage:
+        printf("usage: %s <length>\n", argv[0].str);
+        printf("usage: %s <base> <length>\n", argv[0].str);
+        return -1;
+    }
+
+    if (argc == 2) {
+        void *ptr;
+        size_t len = argv[1].u;
+
+#if WITH_KERNEL_VM
+        /* rounding up len to the next page */
+        len = PAGE_ALIGN(len);
+        if (len == 0) {
+            printf("invalid length\n");
+            return -1;
+        }
+
+        /* allocate a region to test in */
+        status_t err = vmm_alloc_contiguous(vmm_get_kernel_aspace(), "memtest", len, &ptr, 0, 0, ARCH_MMU_FLAG_UNCACHED);
+        if (err < 0) {
+            printf("error %d allocating test region\n", err);
+            return -1;
+        }
+
+        paddr_t pa;
+        arch_mmu_query((vaddr_t)ptr, &pa, 0);
+        printf("physical address 0x%lx\n", pa);
+#else
+        /* allocate from the heap */
+        ptr = malloc(len);
+        if (!ptr ) {
+            printf("error allocating test area from heap\n");
+            return -1;
+        }
+
+#endif
+
+        printf("got buffer at %p of length 0x%lx\n", ptr, len);
+
+        /* run the tests */
+        do_mem_tests(ptr, len);
+
+#if WITH_KERNEL_VM
+        // XXX free memory region here
+        printf("NOTE: leaked memory\n");
+#else
+        free(ptr);
+#endif
+    } else if (argc == 3) {
+        void *ptr = argv[1].p;
+        size_t len = argv[2].u;
+
+        /* run the tests */
+        do_mem_tests(ptr, len);
+    } else {
+        goto usage;
+    }
+
+    return 0;
+}
+
+STATIC_COMMAND_START
+STATIC_COMMAND("mem_test", "test memory", &mem_test)
+STATIC_COMMAND_END(mem_tests);
diff --git a/src/bsp/lk/app/tests/port_tests.c b/src/bsp/lk/app/tests/port_tests.c
new file mode 100644
index 0000000..591096c
--- /dev/null
+++ b/src/bsp/lk/app/tests/port_tests.c
@@ -0,0 +1,605 @@
+/*
+ * Copyright (c) 2015 Carlos Pizano-Uribe  cpu@chromium.org
+ *
+ * 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 <err.h>
+#include <rand.h>
+#include <string.h>
+#include <trace.h>
+
+#include <kernel/port.h>
+#include <kernel/thread.h>
+
+#include <platform.h>
+
+#define LOCAL_TRACE 0
+
+void* context1 = (void*) 0x53;
+
+static void dump_port_result(const port_result_t* result)
+{
+    const port_packet_t* p = &result->packet;
+    LTRACEF("[%02x %02x %02x %02x %02x %02x %02x %02x]\n",
+            p->value[0], p->value[1], p->value[2], p->value[3],
+            p->value[4], p->value[5], p->value[6], p->value[7]);
+}
+
+static int single_thread_basic(void)
+{
+    port_t w_port;
+    status_t st = port_create("sh_prt1", PORT_MODE_UNICAST, &w_port);
+    if (st < 0) {
+        printf("could not create port, status = %d\n", st);
+        return __LINE__;
+    }
+
+    port_t r_port;
+    st = port_open("sh_prt0", context1, &r_port);
+    if (st != ERR_NOT_FOUND) {
+        printf("expected not to find port, status = %d\n", st);
+        return __LINE__;
+    }
+
+    st = port_open("sh_prt1", context1, &r_port);
+    if (st < 0) {
+        printf("could not open port, status = %d\n", st);
+        return __LINE__;
+    }
+
+    port_packet_t packet[3] = {
+        {{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}},
+        {{0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11}},
+        {{0x33, 0x66, 0x99, 0xcc, 0x33, 0x66, 0x99, 0xcc}},
+    };
+
+    st = port_write(w_port, &packet[0], 1);
+    if (st < 0) {
+        printf("could not write port, status = %d\n", st);
+        return __LINE__;
+    }
+
+    printf("reading from port:\n");
+
+    port_result_t res = {0};
+
+    st = port_read(r_port, 0, &res);
+    if (st < 0) {
+        printf("could not read port, status = %d\n", st);
+        return __LINE__;
+    }
+    if (res.ctx != context1) {
+        printf("bad context! = %p\n", res.ctx);
+        return __LINE__;
+    }
+
+    st = port_read(r_port, 0, &res);
+    if (st != ERR_TIMED_OUT) {
+        printf("expected timeout, status = %d\n", st);
+        return __LINE__;
+    }
+
+    st = port_write(w_port, &packet[1], 1);
+    if (st < 0) {
+        printf("could not write port, status = %d\n", st);
+        return __LINE__;
+    }
+
+    st = port_write(w_port, &packet[0], 1);
+    if (st < 0) {
+        printf("could not write port, status = %d\n", st);
+        return __LINE__;
+    }
+
+    st = port_write(w_port, &packet[2], 1);
+    if (st < 0) {
+        printf("could not write port, status = %d\n", st);
+        return __LINE__;
+    }
+
+    int expected_count = 3;
+    while (true) {
+        st = port_read(r_port, 0, &res);
+        if (st < 0)
+            break;
+        dump_port_result(&res);
+        --expected_count;
+    }
+
+    if (expected_count != 0) {
+        printf("invalid read count = %d\n", expected_count);
+        return __LINE__;
+    }
+
+    printf("\n");
+
+    // port should be empty. should be able to write 8 packets.
+    expected_count = 8;
+    while (true) {
+        st = port_write(w_port, &packet[1], 1);
+        if (st < 0)
+            break;
+        --expected_count;
+        st = port_write(w_port, &packet[2], 1);
+        if (st < 0)
+            break;
+        --expected_count;
+    }
+
+    if (expected_count != 0) {
+        printf("invalid write count = %d\n", expected_count);
+        return __LINE__;
+    }
+
+    // tod(cpu) fix this possibly wrong error.
+    if (st != ERR_PARTIAL_WRITE) {
+        printf("expected buffer error, status =%d\n", st);
+        return __LINE__;
+    }
+
+    // read 3 packets.
+    for (int ix = 0; ix != 3; ++ix) {
+        st = port_read(r_port, 0, &res);
+        if (st < 0) {
+            printf("could not read port, status = %d\n", st);
+            return __LINE__;
+        }
+    }
+
+    // there are 5 packets, now we add another 3.
+    st = port_write(w_port, packet, 3);
+    if (st < 0) {
+        printf("could not write port, status = %d\n", st);
+        return __LINE__;
+    }
+
+    expected_count = 8;
+    while (true) {
+        st = port_read(r_port, 0, &res);
+        if (st < 0)
+            break;
+        dump_port_result(&res);
+        --expected_count;
+    }
+
+    if (expected_count != 0) {
+        printf("invalid read count = %d\n", expected_count);
+        return __LINE__;
+    }
+
+    // attempt to use the wrong port.
+    st = port_write(r_port, &packet[1], 1);
+    if (st !=  ERR_BAD_HANDLE) {
+        printf("expected bad handle error, status = %d\n", st);
+        return __LINE__;
+    }
+
+    st = port_read(w_port, 0, &res);
+    if (st !=  ERR_BAD_HANDLE) {
+        printf("expected bad handle error, status = %d\n", st);
+        return __LINE__;
+    }
+
+    st = port_close(r_port);
+    if (st < 0) {
+        printf("could not close read port, status = %d\n", st);
+        return __LINE__;
+    }
+
+    st = port_close(w_port);
+    if (st < 0) {
+        printf("could not close write port, status = %d\n", st);
+        return __LINE__;
+    }
+
+    st = port_close(r_port);
+    if (st != ERR_BAD_HANDLE) {
+        printf("expected bad handle error, status = %d\n", st);
+        return __LINE__;
+    }
+
+    st = port_close(w_port);
+    if (st != ERR_BAD_HANDLE) {
+        printf("expected bad handle error, status = %d\n", st);
+        return __LINE__;
+    }
+
+    st = port_destroy(w_port);
+    if (st < 0) {
+        printf("could not destroy port, status = %d\n", st);
+        return __LINE__;
+    }
+
+    printf("single_thread_basic : ok\n");
+    return 0;
+}
+
+static int ping_pong_thread(void *arg)
+{
+    port_t r_port;
+    status_t st = port_open("ping_port", NULL, &r_port);
+    if (st < 0) {
+        printf("thread: could not open port, status = %d\n", st);
+        return __LINE__;
+    }
+
+    bool should_dispose_pong_port = true;
+    port_t w_port;
+    st = port_create("pong_port", PORT_MODE_UNICAST, &w_port);
+    if (st == ERR_ALREADY_EXISTS) {
+        // won the race to create the port.
+        should_dispose_pong_port = false;
+    } else if (st < 0) {
+        printf("thread: could not open port, status = %d\n", st);
+        return __LINE__;
+    }
+
+    port_result_t pr;
+
+    // the loop is read-mutate-write until the write port
+    // is closed by the master thread.
+    while (true) {
+        st = port_read(r_port, INFINITE_TIME, &pr);
+
+        if (st == ERR_CANCELLED) {
+            break;
+        } else if (st < 0) {
+            printf("thread: could not read port, status = %d\n", st);
+            return __LINE__;
+        }
+
+        pr.packet.value[0]++;
+        pr.packet.value[5]--;
+
+        st = port_write(w_port, &pr.packet, 1);
+        if (st < 0) {
+            printf("thread: could not write port, status = %d\n", st);
+            return __LINE__;
+        }
+    }
+
+    port_close(r_port);
+
+    if (should_dispose_pong_port) {
+        port_close(w_port);
+        port_destroy(w_port);
+    }
+
+    return 0;
+
+bail:
+    return __LINE__;
+}
+
+
+int two_threads_basic(void)
+{
+    port_t w_port;
+    status_t st = port_create("ping_port", PORT_MODE_BROADCAST, &w_port);
+    if (st < 0) {
+        printf("could not create port, status = %d\n", st);
+        return __LINE__;
+    }
+
+    thread_t* t1 = thread_create(
+                       "worker1", &ping_pong_thread, NULL, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
+    thread_t* t2 = thread_create(
+                       "worker2", &ping_pong_thread, NULL, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
+    thread_resume(t1);
+    thread_resume(t2);
+
+    // wait for the pong port to be created, the two threads race to do it.
+    port_t r_port;
+    while (true) {
+        status_t st = port_open("pong_port", NULL, &r_port);
+        if (st == NO_ERROR) {
+            break;
+        } else if (st == ERR_NOT_FOUND) {
+            thread_sleep(100);
+        } else {
+            printf("could not open port, status = %d\n", st);
+            return __LINE__;
+        }
+    }
+
+    // We have two threads listening to the ping port. Which both reply
+    // on the pong port, so we get two packets in per packet out.
+    const int passes = 256;
+    printf("two_threads_basic test, %d passes\n", passes);
+
+    port_packet_t packet_out = {{0xaf, 0x77, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05}};
+
+    port_result_t pr;
+    for (int ix = 0; ix != passes; ++ix) {
+        const size_t count = 1 + ((unsigned int)rand() % 3);
+
+        for (size_t jx = 0; jx != count; ++jx) {
+            st = port_write(w_port, &packet_out, 1);
+            if (st < 0) {
+                printf("could not write port, status = %d\n", st);
+                return __LINE__;
+            }
+        }
+
+        packet_out.value[0]++;
+        packet_out.value[5]--;
+
+        for (size_t jx = 0; jx != count * 2; ++jx) {
+            st = port_read(r_port, INFINITE_TIME, &pr);
+            if (st < 0) {
+                printf("could not read port, status = %d\n", st);
+                return __LINE__;
+            }
+
+            if ((pr.packet.value[0] != packet_out.value[0]) ||
+                    (pr.packet.value[5] != packet_out.value[5])) {
+                printf("unexpected data in packet, loop %d", ix);
+                return __LINE__;
+            }
+        }
+    }
+
+    thread_sleep(100);
+
+    // there should be no more packets to read.
+    st = port_read(r_port, 0, &pr);
+    if (st != ERR_TIMED_OUT) {
+        printf("unexpected packet, status = %d\n", st);
+        return __LINE__;
+    }
+
+    printf("two_threads_basic master shutdown\n");
+
+    st = port_close(r_port);
+    if (st < 0) {
+        printf("could not close port, status = %d\n", st);
+        return __LINE__;
+    }
+
+    st = port_close(w_port);
+    if (st < 0) {
+        printf("could not close port, status = %d\n", st);
+        return __LINE__;
+    }
+
+    st = port_destroy(w_port);
+    if (st < 0) {
+        printf("could not destroy port, status = %d\n", st);
+        return __LINE__;
+    }
+
+    int retcode = -1;
+    thread_join(t1, &retcode, INFINITE_TIME);
+    if (retcode)
+        goto fail;
+
+    thread_join(t2,  &retcode, INFINITE_TIME);
+    if (retcode)
+        goto fail;
+
+    return 0;
+
+fail:
+    printf("child thread exited with %d\n", retcode);
+    return __LINE__;
+}
+
+#define CMD_PORT_CTX ((void*) 0x77)
+#define TS1_PORT_CTX ((void*) 0x11)
+#define TS2_PORT_CTX ((void*) 0x12)
+
+typedef enum {
+    ADD_PORT,
+    QUIT
+} action_t;
+
+typedef struct {
+    action_t what;
+    port_t port;
+} watcher_cmd;
+
+status_t send_watcher_cmd(port_t cmd_port, action_t action, port_t port)
+{
+    watcher_cmd cmd  = {action, port};
+    return port_write(cmd_port, ((port_packet_t*) &cmd), 1);;
+}
+
+static int group_watcher_thread(void *arg)
+{
+    port_t watched[8] = {0};
+    status_t st = port_open("grp_ctrl", CMD_PORT_CTX, &watched[0]);
+    if (st < 0) {
+        printf("could not open port, status = %d\n", st);
+        return __LINE__;
+    }
+
+    size_t count = 1;
+    port_t group;
+    int ctx_count = -1;
+
+    while (true) {
+        st = port_group(watched, count, &group);
+        if (st < 0) {
+            printf("could not make group, status = %d\n", st);
+            return __LINE__;
+        }
+
+        port_result_t pr;
+        while (true) {
+            st = port_read(group, INFINITE_TIME, &pr);
+            if (st < 0) {
+                printf("could not read port, status = %d\n", st);
+                return __LINE__;
+            }
+
+            if (pr.ctx == CMD_PORT_CTX) {
+                break;
+            } else if (pr.ctx == TS1_PORT_CTX) {
+                ctx_count += 1;
+            } else if (pr.ctx == TS2_PORT_CTX) {
+                ctx_count += 2;
+            } else {
+                printf("unknown context %p\n", pr.ctx);
+                return __LINE__;
+            }
+        }
+
+        // Either adding a port or exiting; either way close the
+        // existing group port and create a new one if needed
+        // at the top of the loop.
+
+        port_close(group);
+        watcher_cmd* wc = (watcher_cmd*) &pr.packet;
+
+        if (wc->what == ADD_PORT) {
+            watched[count++] = wc->port;
+        }  else if (wc->what == QUIT) {
+            break;
+        } else {
+            printf("unknown command %d\n", wc->what);
+            return __LINE__;
+        }
+    }
+
+    if (ctx_count !=  2) {
+        printf("unexpected context count %d", ctx_count);
+        return __LINE__;
+    }
+
+    printf("group watcher shutdown\n");
+
+    for (size_t ix = 0; ix != count; ++ix) {
+        st = port_close(watched[ix]);
+        if (st < 0) {
+            printf("failed to close read port, status = %d\n", st);
+            return __LINE__;
+        }
+    }
+
+    return 0;
+}
+
+static status_t make_port_pair(const char* name, void* ctx, port_t* write, port_t* read)
+{
+    status_t st = port_create(name, PORT_MODE_UNICAST, write);
+    if (st < 0)
+        return st;
+    return port_open(name,ctx, read);
+}
+
+int group_basic(void)
+{
+    // we spin a thread that connects to a well known port, then we
+    // send two ports that it will add to a group port.
+    port_t cmd_port;
+    status_t st = port_create("grp_ctrl", PORT_MODE_UNICAST, &cmd_port);
+    if (st < 0 ) {
+        printf("could not create port, status = %d\n", st);
+        return __LINE__;
+    }
+
+    thread_t* wt = thread_create(
+                       "g_watcher", &group_watcher_thread, NULL, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
+    thread_resume(wt);
+
+    port_t w_test_port1, r_test_port1;
+    st = make_port_pair("tst_port1", TS1_PORT_CTX, &w_test_port1, &r_test_port1);
+    if (st < 0)
+        return __LINE__;
+
+    port_t w_test_port2, r_test_port2;
+    st = make_port_pair("tst_port2", TS2_PORT_CTX, &w_test_port2, &r_test_port2);
+    if (st < 0)
+        return __LINE__;
+
+    st = send_watcher_cmd(cmd_port, ADD_PORT, r_test_port1);
+    if (st < 0)
+        return __LINE__;
+
+    st = send_watcher_cmd(cmd_port, ADD_PORT, r_test_port2);
+    if (st < 0)
+        return __LINE__;
+
+    thread_sleep(50);
+
+    port_packet_t pp = {{0}};
+    st = port_write(w_test_port1, &pp, 1);
+    if (st < 0)
+        return __LINE__;
+
+    st = port_write(w_test_port2, &pp, 1);
+    if (st < 0)
+        return __LINE__;
+
+    st = send_watcher_cmd(cmd_port, QUIT, 0);
+    if (st < 0)
+        return __LINE__;
+
+    int retcode = -1;
+    thread_join(wt, &retcode, INFINITE_TIME);
+    if (retcode) {
+        printf("child thread exited with %d\n", retcode);
+        return __LINE__;
+    }
+
+    st = port_close(w_test_port1);
+    if (st < 0)
+        return __LINE__;
+    st = port_close(w_test_port2);
+    if (st < 0)
+        return __LINE__;
+    st = port_close(cmd_port);
+    if (st < 0)
+        return __LINE__;
+    st = port_destroy(w_test_port1);
+    if (st < 0)
+        return __LINE__;
+    st = port_destroy(w_test_port2);
+    if (st < 0)
+        return __LINE__;
+    st = port_destroy(cmd_port);
+    if (st < 0)
+        return __LINE__;
+
+    return 0;
+}
+
+#define RUN_TEST(t)  result = t(); if (result) goto fail
+
+int port_tests(void)
+{
+    int result;
+    int count = 3;
+    while (count--) {
+        RUN_TEST(single_thread_basic);
+        RUN_TEST(two_threads_basic);
+        RUN_TEST(group_basic);
+    }
+
+    printf("all tests passed\n");
+    return 0;
+fail:
+    printf("test failed at line %d\n", result);
+    return 1;
+}
+
+#undef RUN_TEST
diff --git a/src/bsp/lk/app/tests/printf_tests.c b/src/bsp/lk/app/tests/printf_tests.c
new file mode 100644
index 0000000..3a9747f
--- /dev/null
+++ b/src/bsp/lk/app/tests/printf_tests.c
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2008-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 <app/tests.h>
+#include <err.h>
+#include <stdio.h>
+#include <string.h>
+#include <debug.h>
+
+int printf_tests(int argc, const cmd_args *argv)
+{
+    printf("printf tests\n");
+
+    printf("numbers:\n");
+    printf("int8:  %hhd %hhd %hhd\n", -12, 0, 254);
+    printf("uint8: %hhu %hhu %hhu\n", -12, 0, 254);
+    printf("int16: %hd %hd %hd\n", -1234, 0, 1234);
+    printf("uint16:%hu %hu %hu\n", -1234, 0, 1234);
+    printf("int:   %d %d %d\n", -12345678, 0, 12345678);
+    printf("uint:  %u %u %u\n", -12345678, 0, 12345678);
+    printf("long:  %ld %ld %ld\n", -12345678L, 0L, 12345678L);
+    printf("ulong: %lu %lu %lu\n", -12345678UL, 0UL, 12345678UL);
+
+    printf("longlong: %lli %lli %lli\n", -12345678LL, 0LL, 12345678LL);
+    printf("ulonglong: %llu %llu %llu\n", -12345678LL, 0LL, 12345678LL);
+    printf("ssize_t: %zd %zd %zd\n", (ssize_t)-12345678, (ssize_t)0, (ssize_t)12345678);
+    printf("usize_t: %zu %zu %zu\n", (size_t)-12345678, (size_t)0, (size_t)12345678);
+    printf("intmax_t: %jd %jd %jd\n", (intmax_t)-12345678, (intmax_t)0, (intmax_t)12345678);
+    printf("uintmax_t: %ju %ju %ju\n", (uintmax_t)-12345678, (uintmax_t)0, (uintmax_t)12345678);
+    printf("ptrdiff_t: %td %td %td\n", (ptrdiff_t)-12345678, (ptrdiff_t)0, (ptrdiff_t)12345678);
+    printf("ptrdiff_t (u): %tu %tu %tu\n", (ptrdiff_t)-12345678, (ptrdiff_t)0, (ptrdiff_t)12345678);
+
+    printf("hex:\n");
+    printf("uint8: %hhx %hhx %hhx\n", -12, 0, 254);
+    printf("uint16:%hx %hx %hx\n", -1234, 0, 1234);
+    printf("uint:  %x %x %x\n", -12345678, 0, 12345678);
+    printf("ulong: %lx %lx %lx\n", -12345678UL, 0UL, 12345678UL);
+    printf("ulong: %X %X %X\n", -12345678, 0, 12345678);
+    printf("ulonglong: %llx %llx %llx\n", -12345678LL, 0LL, 12345678LL);
+    printf("usize_t: %zx %zx %zx\n", (size_t)-12345678, (size_t)0, (size_t)12345678);
+
+    printf("alt/sign:\n");
+    printf("uint: %#x %#X\n", 0xabcdef, 0xabcdef);
+    printf("int: %+d %+d\n", 12345678, -12345678);
+    printf("int: % d %+d\n", 12345678, 12345678);
+
+    printf("formatting\n");
+    printf("int: a%8da\n", 12345678);
+    printf("int: a%9da\n", 12345678);
+    printf("int: a%-9da\n", 12345678);
+    printf("int: a%10da\n", 12345678);
+    printf("int: a%-10da\n", 12345678);
+    printf("int: a%09da\n", 12345678);
+    printf("int: a%010da\n", 12345678);
+    printf("int: a%6da\n", 12345678);
+
+    printf("a%1sa\n", "b");
+    printf("a%9sa\n", "b");
+    printf("a%-9sa\n", "b");
+    printf("a%5sa\n", "thisisatest");
+
+    printf("%03d\n", -2);       /* '-02' */
+    printf("%0+3d\n", -2);      /* '-02' */
+    printf("%0+3d\n", 2);       /* '+02' */
+    printf("%+3d\n", 2);        /* ' +2' */
+    printf("% 3d\n", -2000);    /* '-2000' */
+    printf("% 3d\n", 2000);     /* ' 2000' */
+    printf("%+3d\n", 2000);     /* '+2000' */
+    printf("%10s\n", "test");   /* '      test' */
+    printf("%010s\n", "test");  /* '      test' */
+    printf("%-10s\n", "test");  /* 'test      ' */
+    printf("%-010s\n", "test"); /* 'test      ' */
+
+    int err;
+
+    err = printf("a");
+    printf(" returned %d\n", err);
+    err = printf("ab");
+    printf(" returned %d\n", err);
+    err = printf("abc");
+    printf(" returned %d\n", err);
+    err = printf("abcd");
+    printf(" returned %d\n", err);
+    err = printf("abcde");
+    printf(" returned %d\n", err);
+    err = printf("abcdef");
+    printf(" returned %d\n", err);
+
+    /* make sure snprintf terminates at the right spot */
+    char buf[32];
+
+    memset(buf, 0, sizeof(buf));
+    err = sprintf(buf, "0123456789abcdef012345678");
+    printf("sprintf returns %d\n", err);
+    hexdump8(buf, sizeof(buf));
+
+    memset(buf, 0, sizeof(buf));
+    err = snprintf(buf, 15, "0123456789abcdef012345678");
+    printf("snprintf returns %d\n", err);
+    hexdump8(buf, sizeof(buf));
+
+    return NO_ERROR;
+}
+
+#include "float_test_vec.c"
+
+int printf_tests_float(int argc, const cmd_args *argv)
+{
+    printf("floating point printf tests\n");
+
+    for (size_t i = 0; i < float_test_vec_size; i++) {
+        PRINT_FLOAT;
+    }
+
+    return NO_ERROR;
+}
+
+
diff --git a/src/bsp/lk/app/tests/rules.mk b/src/bsp/lk/app/tests/rules.mk
new file mode 100644
index 0000000..aba98c7
--- /dev/null
+++ b/src/bsp/lk/app/tests/rules.mk
@@ -0,0 +1,23 @@
+LOCAL_DIR := $(GET_LOCAL_DIR)
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_SRCS += \
+    $(LOCAL_DIR)/benchmarks.c \
+    $(LOCAL_DIR)/cache_tests.c \
+    $(LOCAL_DIR)/clock_tests.c \
+    $(LOCAL_DIR)/fibo.c \
+    $(LOCAL_DIR)/float.c \
+    $(LOCAL_DIR)/float_instructions.S \
+    $(LOCAL_DIR)/float_test_vec.c \
+    $(LOCAL_DIR)/mem_tests.c \
+    $(LOCAL_DIR)/printf_tests.c \
+    $(LOCAL_DIR)/tests.c \
+    $(LOCAL_DIR)/thread_tests.c \
+    $(LOCAL_DIR)/port_tests.c \
+
+MODULE_ARM_OVERRIDE_SRCS := \
+
+MODULE_COMPILEFLAGS += -Wno-format
+
+include make/module.mk
diff --git a/src/bsp/lk/app/tests/tests.c b/src/bsp/lk/app/tests/tests.c
new file mode 100644
index 0000000..4d5dcda
--- /dev/null
+++ b/src/bsp/lk/app/tests/tests.c
@@ -0,0 +1,52 @@
+/*
+ * 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 <app.h>
+#include <debug.h>
+#include <app/tests.h>
+#include <compiler.h>
+
+#if defined(WITH_LIB_CONSOLE)
+#include <lib/console.h>
+
+STATIC_COMMAND_START
+STATIC_COMMAND("printf_tests", "test printf", &printf_tests)
+STATIC_COMMAND("printf_tests_float", "test printf with floating point", &printf_tests_float)
+STATIC_COMMAND("thread_tests", "test the scheduler", &thread_tests)
+STATIC_COMMAND("port_tests", "test the ports", &port_tests)
+STATIC_COMMAND("clock_tests", "test clocks", &clock_tests)
+STATIC_COMMAND("bench", "miscellaneous benchmarks", &benchmarks)
+STATIC_COMMAND("fibo", "threaded fibonacci", &fibo)
+STATIC_COMMAND("spinner", "create a spinning thread", &spinner)
+STATIC_COMMAND_END(tests);
+
+#endif
+
+static void tests_init(const struct app_descriptor *app)
+{
+}
+
+APP_START(tests)
+.init = tests_init,
+.flags = 0,
+APP_END
+
diff --git a/src/bsp/lk/app/tests/thread_tests.c b/src/bsp/lk/app/tests/thread_tests.c
new file mode 100644
index 0000000..543bd52
--- /dev/null
+++ b/src/bsp/lk/app/tests/thread_tests.c
@@ -0,0 +1,666 @@
+/*
+ * 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 <rand.h>
+#include <err.h>
+#include <assert.h>
+#include <string.h>
+#include <app/tests.h>
+#include <kernel/thread.h>
+#include <kernel/mutex.h>
+#include <kernel/semaphore.h>
+#include <kernel/event.h>
+#include <platform.h>
+
+static int sleep_thread(void *arg)
+{
+	for (;;) {
+		printf("sleeper %p\n", get_current_thread());
+		thread_sleep(rand() % 500);
+	}
+	return 0;
+}
+
+int sleep_test(void)
+{
+	int i;
+	for (i=0; i < 16; i++)
+		thread_detach_and_resume(thread_create("sleeper", &sleep_thread, NULL, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE));
+	return 0;
+}
+
+static semaphore_t sem;
+static const int sem_total_its = 10000;
+static const int sem_thread_max_its = 1000;
+static const int sem_start_value = 10;
+static int sem_remaining_its = 0;
+static int sem_threads = 0;
+static mutex_t sem_test_mutex;
+
+static int semaphore_producer(void *unused)
+{
+	printf("semaphore producer %p starting up, running for %d iterations\n", get_current_thread(), sem_total_its);
+
+	for (int x = 0; x < sem_total_its; x++) {
+		sem_post(&sem, true);
+	}
+
+	return 0;
+}
+
+static int semaphore_consumer(void *unused)
+{
+	unsigned int iterations = 0;
+
+	mutex_acquire(&sem_test_mutex);
+	if (sem_remaining_its >= sem_thread_max_its) {
+		iterations = rand();
+		iterations %= sem_thread_max_its;
+	} else {
+		iterations = sem_remaining_its;
+	}
+	sem_remaining_its -= iterations;
+	mutex_release(&sem_test_mutex);
+
+	printf("semaphore consumer %p starting up, running for %u iterations\n", get_current_thread(), iterations);
+	for (unsigned int x = 0; x < iterations; x++)
+		sem_wait(&sem);
+	printf("semaphore consumer %p done\n", get_current_thread());
+	atomic_add(&sem_threads, -1);
+	return 0;
+}
+
+static int semaphore_test(void)
+{
+	static semaphore_t isem = SEMAPHORE_INITIAL_VALUE(isem, 99);
+	printf("preinitialized sempahore:\n");
+	hexdump(&isem, sizeof(isem));
+
+	sem_init(&sem, sem_start_value);
+	mutex_init(&sem_test_mutex);
+
+	sem_remaining_its = sem_total_its;
+	while (1) {
+		mutex_acquire(&sem_test_mutex);
+		if (sem_remaining_its) {
+			thread_detach_and_resume(thread_create("semaphore consumer", &semaphore_consumer, NULL, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE));
+			atomic_add(&sem_threads, 1);
+		} else {
+			mutex_release(&sem_test_mutex);
+			break;
+		}
+		mutex_release(&sem_test_mutex);
+	}
+
+	thread_detach_and_resume(thread_create("semaphore producer", &semaphore_producer, NULL, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE));
+
+	while (sem_threads)
+		thread_yield();
+
+	if (sem.count == sem_start_value)
+		printf("semaphore tests successfully complete\n");
+	else
+		printf("semaphore tests failed: %d != %d\n", sem.count, sem_start_value);
+
+	sem_destroy(&sem);
+	mutex_destroy(&sem_test_mutex);
+
+	return 0;
+}
+
+static int mutex_thread(void *arg)
+{
+	int i;
+	const int iterations = 1000000;
+
+	static volatile int shared = 0;
+
+	mutex_t *m = (mutex_t *)arg;
+
+	printf("mutex tester thread %p starting up, will go for %d iterations\n", get_current_thread(), iterations);
+
+	for (i = 0; i < iterations; i++) {
+		mutex_acquire(m);
+
+		if (shared != 0)
+			panic("someone else has messed with the shared data\n");
+
+		shared = (intptr_t)get_current_thread();
+		thread_yield();
+		shared = 0;
+
+		mutex_release(m);
+		thread_yield();
+	}
+
+	return 0;
+}
+
+static int mutex_timeout_thread(void *arg)
+{
+	mutex_t *timeout_mutex = (mutex_t *)arg;
+	status_t err;
+
+	printf("mutex_timeout_thread acquiring mutex %p with 1 second timeout\n", timeout_mutex);
+	err = mutex_acquire_timeout(timeout_mutex, 1000);
+	if (err == ERR_TIMED_OUT)
+		printf("mutex_acquire_timeout returns with TIMEOUT\n");
+	else
+		printf("mutex_acquire_timeout returns %d\n", err);
+
+	return err;
+}
+
+static int mutex_zerotimeout_thread(void *arg)
+{
+	mutex_t *timeout_mutex = (mutex_t *)arg;
+	status_t err;
+
+	printf("mutex_zerotimeout_thread acquiring mutex %p with zero second timeout\n", timeout_mutex);
+	err = mutex_acquire_timeout(timeout_mutex, 0);
+	if (err == ERR_TIMED_OUT)
+		printf("mutex_acquire_timeout returns with TIMEOUT\n");
+	else
+		printf("mutex_acquire_timeout returns %d\n", err);
+
+	return err;
+}
+
+int mutex_test(void)
+{
+	static mutex_t imutex = MUTEX_INITIAL_VALUE(imutex);
+	printf("preinitialized mutex:\n");
+	hexdump(&imutex, sizeof(imutex));
+
+	mutex_t m;
+	mutex_init(&m);
+
+	thread_t *threads[5];
+
+	for (uint i=0; i < countof(threads); i++) {
+		threads[i] = thread_create("mutex tester", &mutex_thread, &m, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
+		thread_resume(threads[i]);
+	}
+
+	for (uint i=0; i < countof(threads); i++) {
+		thread_join(threads[i], NULL, INFINITE_TIME);
+	}
+
+	printf("done with simple mutex tests\n");
+
+	printf("testing mutex timeout\n");
+
+	mutex_t timeout_mutex;
+
+	mutex_init(&timeout_mutex);
+	mutex_acquire(&timeout_mutex);
+
+	for (uint i=0; i < 2; i++) {
+		threads[i] = thread_create("mutex timeout tester", &mutex_timeout_thread, (void *)&timeout_mutex, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
+		thread_resume(threads[i]);
+	}
+
+	for (uint i=2; i < 4; i++) {
+		threads[i] = thread_create("mutex timeout tester", &mutex_zerotimeout_thread, (void *)&timeout_mutex, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
+		thread_resume(threads[i]);
+	}
+
+	thread_sleep(5000);
+	mutex_release(&timeout_mutex);
+
+	for (uint i=0; i < 4; i++) {
+		thread_join(threads[i], NULL, INFINITE_TIME);
+	}
+
+	printf("done with mutex tests\n");
+
+	mutex_destroy(&timeout_mutex);
+
+	return 0;
+}
+
+static event_t e;
+
+static int event_signaller(void *arg)
+{
+	printf("event signaller pausing\n");
+	thread_sleep(1000);
+
+//	for (;;) {
+		printf("signalling event\n");
+		event_signal(&e, true);
+		printf("done signalling event\n");
+		thread_yield();
+//	}
+
+	return 0;
+}
+
+static int event_waiter(void *arg)
+{
+	int count = (intptr_t)arg;
+
+	printf("event waiter starting\n");
+
+	while (count > 0) {
+		printf("%p: waiting on event...\n", get_current_thread());
+		if (event_wait(&e) < 0) {
+			printf("%p: event_wait() returned error\n", get_current_thread());
+			return -1;
+		}
+		printf("%p: done waiting on event...\n", get_current_thread());
+		thread_yield();
+		count--;
+	}
+
+	return 0;
+}
+
+void event_test(void)
+{
+	thread_t *threads[5];
+
+	static event_t ievent = EVENT_INITIAL_VALUE(ievent, true, 0x1234);
+	printf("preinitialized event:\n");
+	hexdump(&ievent, sizeof(ievent));
+
+	printf("event tests starting\n");
+
+	/* make sure signalling the event wakes up all the threads */
+	event_init(&e, false, 0);
+	threads[0] = thread_create("event signaller", &event_signaller, NULL, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
+	threads[1] = thread_create("event waiter 0", &event_waiter, (void *)2, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
+	threads[2] = thread_create("event waiter 1", &event_waiter, (void *)2, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
+	threads[3] = thread_create("event waiter 2", &event_waiter, (void *)2, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
+	threads[4] = thread_create("event waiter 3", &event_waiter, (void *)2, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
+
+	for (uint i = 0; i < countof(threads); i++)
+		thread_resume(threads[i]);
+
+	thread_sleep(2000);
+	printf("destroying event\n");
+	event_destroy(&e);
+
+	for (uint i = 0; i < countof(threads); i++)
+		thread_join(threads[i], NULL, INFINITE_TIME);
+
+	/* make sure signalling the event wakes up precisely one thread */
+	event_init(&e, false, EVENT_FLAG_AUTOUNSIGNAL);
+	threads[0] = thread_create("event signaller", &event_signaller, NULL, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
+	threads[1] = thread_create("event waiter 0", &event_waiter, (void *)99, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
+	threads[2] = thread_create("event waiter 1", &event_waiter, (void *)99, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
+	threads[3] = thread_create("event waiter 2", &event_waiter, (void *)99, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
+	threads[4] = thread_create("event waiter 3", &event_waiter, (void *)99, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
+
+	for (uint i = 0; i < countof(threads); i++)
+		thread_resume(threads[i]);
+
+	thread_sleep(2000);
+	event_destroy(&e);
+
+	for (uint i = 0; i < countof(threads); i++)
+		thread_join(threads[i], NULL, INFINITE_TIME);
+
+	printf("event tests done\n");
+}
+
+static int quantum_tester(void *arg)
+{
+	for (;;) {
+		printf("%p: in this thread. rq %d\n", get_current_thread(), get_current_thread()->remaining_quantum);
+	}
+	return 0;
+}
+
+void quantum_test(void)
+{
+	thread_detach_and_resume(thread_create("quantum tester 0", &quantum_tester, NULL, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE));
+	thread_detach_and_resume(thread_create("quantum tester 1", &quantum_tester, NULL, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE));
+	thread_detach_and_resume(thread_create("quantum tester 2", &quantum_tester, NULL, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE));
+	thread_detach_and_resume(thread_create("quantum tester 3", &quantum_tester, NULL, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE));
+}
+
+static event_t context_switch_event;
+static event_t context_switch_done_event;
+
+static int context_switch_tester(void *arg)
+{
+	int i;
+	uint total_count = 0;
+	const int iter = 100000;
+	int thread_count = (intptr_t)arg;
+
+	event_wait(&context_switch_event);
+
+	uint count = arch_cycle_count();
+	for (i = 0; i < iter; i++) {
+		thread_yield();
+	}
+	total_count += arch_cycle_count() - count;
+	thread_sleep(1000);
+	printf("took %u cycles to yield %d times, %u per yield, %u per yield per thread\n",
+	       total_count, iter, total_count / iter, total_count / iter / thread_count);
+
+	event_signal(&context_switch_done_event, true);
+
+	return 0;
+}
+
+void context_switch_test(void)
+{
+	event_init(&context_switch_event, false, 0);
+	event_init(&context_switch_done_event, false, 0);
+
+	thread_detach_and_resume(thread_create("context switch idle", &context_switch_tester, (void *)1, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE));
+	thread_sleep(100);
+	event_signal(&context_switch_event, true);
+	event_wait(&context_switch_done_event);
+	thread_sleep(100);
+
+	event_unsignal(&context_switch_event);
+	event_unsignal(&context_switch_done_event);
+	thread_detach_and_resume(thread_create("context switch 2a", &context_switch_tester, (void *)2, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE));
+	thread_detach_and_resume(thread_create("context switch 2b", &context_switch_tester, (void *)2, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE));
+	thread_sleep(100);
+	event_signal(&context_switch_event, true);
+	event_wait(&context_switch_done_event);
+	thread_sleep(100);
+
+	event_unsignal(&context_switch_event);
+	event_unsignal(&context_switch_done_event);
+	thread_detach_and_resume(thread_create("context switch 4a", &context_switch_tester, (void *)4, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE));
+	thread_detach_and_resume(thread_create("context switch 4b", &context_switch_tester, (void *)4, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE));
+	thread_detach_and_resume(thread_create("context switch 4c", &context_switch_tester, (void *)4, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE));
+	thread_detach_and_resume(thread_create("context switch 4d", &context_switch_tester, (void *)4, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE));
+	thread_sleep(100);
+	event_signal(&context_switch_event, true);
+	event_wait(&context_switch_done_event);
+	thread_sleep(100);
+}
+
+static volatile int atomic;
+static volatile int atomic_count;
+
+static int atomic_tester(void *arg)
+{
+	int add = (intptr_t)arg;
+	int i;
+
+	const int iter = 10000000;
+
+	TRACEF("add %d, %d iterations\n", add, iter);
+
+	for (i=0; i < iter; i++) {
+		atomic_add(&atomic, add);
+	}
+
+	int old = atomic_add(&atomic_count, -1);
+	TRACEF("exiting, old count %d\n", old);
+
+	return 0;
+}
+
+static void atomic_test(void)
+{
+	atomic = 0;
+	atomic_count = 8;
+
+	printf("testing atomic routines\n");
+
+	thread_t *threads[8];
+	threads[0] = thread_create("atomic tester 1", &atomic_tester, (void *)1, LOW_PRIORITY, DEFAULT_STACK_SIZE);
+	threads[1] = thread_create("atomic tester 1", &atomic_tester, (void *)1, LOW_PRIORITY, DEFAULT_STACK_SIZE);
+	threads[2] = thread_create("atomic tester 1", &atomic_tester, (void *)1, LOW_PRIORITY, DEFAULT_STACK_SIZE);
+	threads[3] = thread_create("atomic tester 1", &atomic_tester, (void *)1, LOW_PRIORITY, DEFAULT_STACK_SIZE);
+	threads[4] = thread_create("atomic tester 2", &atomic_tester, (void *)-1, LOW_PRIORITY, DEFAULT_STACK_SIZE);
+	threads[5] = thread_create("atomic tester 2", &atomic_tester, (void *)-1, LOW_PRIORITY, DEFAULT_STACK_SIZE);
+	threads[6] = thread_create("atomic tester 2", &atomic_tester, (void *)-1, LOW_PRIORITY, DEFAULT_STACK_SIZE);
+	threads[7] = thread_create("atomic tester 2", &atomic_tester, (void *)-1, LOW_PRIORITY, DEFAULT_STACK_SIZE);
+
+	/* start all the threads */
+	for (uint i = 0; i < countof(threads); i++)
+		thread_resume(threads[i]);
+
+	/* wait for them to all stop */
+	for (uint i = 0; i < countof(threads); i++) {
+		thread_join(threads[i], NULL, INFINITE_TIME);
+	}
+
+	printf("atomic count == %d (should be zero)\n", atomic);
+}
+
+static volatile int preempt_count;
+
+static int preempt_tester(void *arg)
+{
+	spin(1000000);
+
+	printf("exiting ts %lld\n", current_time_hires());
+
+	atomic_add(&preempt_count, -1);
+#undef COUNT
+
+	return 0;
+}
+
+static void preempt_test(void)
+{
+	/* create 5 threads, let them run. If the system is properly timer preempting,
+	 * the threads should interleave each other at a fine enough granularity so
+	 * that they complete at roughly the same time. */
+	printf("testing preemption\n");
+
+	preempt_count = 5;
+
+	for (int i = 0; i < preempt_count; i++)
+		thread_detach_and_resume(thread_create("preempt tester", &preempt_tester, NULL, LOW_PRIORITY, DEFAULT_STACK_SIZE));
+
+	while (preempt_count > 0) {
+		thread_sleep(1000);
+	}
+
+	printf("done with preempt test, above time stamps should be very close\n");
+
+	/* do the same as above, but mark the threads as real time, which should
+	 * effectively disable timer based preemption for them. They should
+	 * complete in order, about a second apart. */
+	printf("testing real time preemption\n");
+
+	preempt_count = 5;
+
+	for (int i = 0; i < preempt_count; i++) {
+		thread_t *t = thread_create("preempt tester", &preempt_tester, NULL, LOW_PRIORITY, DEFAULT_STACK_SIZE);
+		thread_set_real_time(t);
+		thread_detach_and_resume(t);
+	}
+
+	while (preempt_count > 0) {
+		thread_sleep(1000);
+	}
+
+	printf("done with real-time preempt test, above time stamps should be 1 second apart\n");
+}
+
+static int join_tester(void *arg)
+{
+	long val = (long)arg;
+
+	printf("\t\tjoin tester starting\n");
+	thread_sleep(500);
+	printf("\t\tjoin tester exiting with result %ld\n", val);
+
+	return val;
+}
+
+static int join_tester_server(void *arg)
+{
+	int ret;
+	status_t err;
+	thread_t *t;
+
+	printf("\ttesting thread_join/thread_detach\n");
+
+	printf("\tcreating and waiting on thread to exit with thread_join\n");
+	t = thread_create("join tester", &join_tester, (void *)1, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
+	thread_resume(t);
+	ret = 99;
+	printf("\tthread magic is 0x%x (should be 0x%x)\n", t->magic, THREAD_MAGIC);
+	err = thread_join(t, &ret, INFINITE_TIME);
+	printf("\tthread_join returns err %d, retval %d\n", err, ret);
+	printf("\tthread magic is 0x%x (should be 0)\n", t->magic);
+
+	printf("\tcreating and waiting on thread to exit with thread_join, after thread has exited\n");
+	t = thread_create("join tester", &join_tester, (void *)2, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
+	thread_resume(t);
+	thread_sleep(1000); // wait until thread is already dead
+	ret = 99;
+	printf("\tthread magic is 0x%x (should be 0x%x)\n", t->magic, THREAD_MAGIC);
+	err = thread_join(t, &ret, INFINITE_TIME);
+	printf("\tthread_join returns err %d, retval %d\n", err, ret);
+	printf("\tthread magic is 0x%x (should be 0)\n", t->magic);
+
+	printf("\tcreating a thread, detaching it, let it exit on its own\n");
+	t = thread_create("join tester", &join_tester, (void *)3, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
+	thread_detach(t);
+	thread_resume(t);
+	thread_sleep(1000); // wait until the thread should be dead
+	printf("\tthread magic is 0x%x (should be 0)\n", t->magic);
+
+	printf("\tcreating a thread, detaching it after it should be dead\n");
+	t = thread_create("join tester", &join_tester, (void *)4, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
+	thread_resume(t);
+	thread_sleep(1000); // wait until thread is already dead
+	printf("\tthread magic is 0x%x (should be 0x%x)\n", t->magic, THREAD_MAGIC);
+	thread_detach(t);
+	printf("\tthread magic is 0x%x\n", t->magic);
+
+	printf("\texiting join tester server\n");
+
+	return 55;
+}
+
+static void join_test(void)
+{
+	int ret;
+	status_t err;
+	thread_t *t;
+
+	printf("testing thread_join/thread_detach\n");
+
+	printf("creating thread join server thread\n");
+	t = thread_create("join tester server", &join_tester_server, (void *)1, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
+	thread_resume(t);
+	ret = 99;
+	err = thread_join(t, &ret, INFINITE_TIME);
+	printf("thread_join returns err %d, retval %d (should be 0 and 55)\n", err, ret);
+}
+
+static void spinlock_test(void)
+{
+    spin_lock_saved_state_t state;
+    spin_lock_t lock;
+
+    spin_lock_init(&lock);
+
+    // verify basic functionality (single core)
+    printf("testing spinlock:\n");
+    ASSERT(!spin_lock_held(&lock));
+    ASSERT(!arch_ints_disabled());
+    spin_lock_irqsave(&lock, state);
+    ASSERT(arch_ints_disabled());
+    ASSERT(spin_lock_held(&lock));
+    spin_unlock_irqrestore(&lock, state);
+    ASSERT(!spin_lock_held(&lock));
+    ASSERT(!arch_ints_disabled());
+    printf("seems to work\n");
+
+#define COUNT (1024*1024)
+    uint32_t c = arch_cycle_count();
+    for (uint i = 0; i < COUNT; i++) {
+        spin_lock(&lock);
+        spin_unlock(&lock);
+    }
+    c = arch_cycle_count() - c;
+
+    printf("%u cycles to acquire/release lock %u times (%u cycles per)\n", c, COUNT, c / COUNT);
+
+    c = arch_cycle_count();
+    for (uint i = 0; i < COUNT; i++) {
+        spin_lock_irqsave(&lock, state);
+        spin_unlock_irqrestore(&lock, state);
+    }
+    c = arch_cycle_count() - c;
+
+    printf("%u cycles to acquire/release lock w/irqsave %u times (%u cycles per)\n", c, COUNT, c / COUNT);
+#undef COUNT
+}
+
+int thread_tests(int argc, const cmd_args *argv)
+{
+	mutex_test();
+	semaphore_test();
+	event_test();
+
+	spinlock_test();
+	atomic_test();
+
+	thread_sleep(200);
+	context_switch_test();
+
+	preempt_test();
+
+	join_test();
+
+	return 0;
+}
+
+static int spinner_thread(void *arg)
+{
+	for (;;)
+		;
+
+	return 0;
+}
+
+int spinner(int argc, const cmd_args *argv)
+{
+	if (argc < 2) {
+		printf("not enough args\n");
+		printf("usage: %s <priority> <rt>\n", argv[0].str);
+		return -1;
+	}
+
+	thread_t *t = thread_create("spinner", spinner_thread, NULL, argv[1].u, DEFAULT_STACK_SIZE);
+	if (!t)
+		return ERR_NO_MEMORY;
+
+	if (argc >= 3 && !strcmp(argv[2].str, "rt")) {
+		thread_set_real_time(t);
+	}
+	thread_resume(t);
+
+	return 0;
+}
+
+/* vim: set ts=4 sw=4 noexpandtab: */