diff --git a/helper/test/.gitignore b/helper/test/.gitignore
index e232753..5ce3c3b 100644
--- a/helper/test/.gitignore
+++ b/helper/test/.gitignore
@@ -1,6 +1,7 @@
 *.trs
 *.log
 chksum
+odpthreads
 parse
 process
 table
diff --git a/helper/test/Makefile.am b/helper/test/Makefile.am
index 7f0b67d..4ec561b 100644
--- a/helper/test/Makefile.am
+++ b/helper/test/Makefile.am
@@ -11,9 +11,11 @@ EXECUTABLES = chksum$(EXEEXT) \
               process$(EXEEXT)\
               table$(EXEEXT)
 
-COMPILE_ONLY =
+COMPILE_ONLY = odpthreads
 
-TESTSCRIPTS =
+TESTSCRIPTS = odpthreads_as_mixed \
+	      odpthreads_as_processes \
+	      odpthreads_as_pthreads
 
 if test_helper
 TESTS = $(EXECUTABLES) $(TESTSCRIPTS)
@@ -25,6 +27,8 @@ bin_PROGRAMS = $(EXECUTABLES) $(COMPILE_ONLY)
 
 
 dist_chksum_SOURCES = chksum.c
+dist_odpthreads_SOURCES = odpthreads.c
+odpthreads_LDADD = $(LIB)/libodphelper-linux.la $(LIB)/libodp-linux.la
 dist_thread_SOURCES = thread.c
 thread_LDADD = $(LIB)/libodphelper-linux.la $(LIB)/libodp-linux.la
 dist_process_SOURCES = process.c
diff --git a/helper/test/odpthreads.c b/helper/test/odpthreads.c
new file mode 100644
index 0000000..369da62
--- /dev/null
+++ b/helper/test/odpthreads.c
@@ -0,0 +1,100 @@
+/* Copyright (c) 2016, Linaro Limited
+ * All rights reserved.
+ *
+ * SPDX-License-Identifier:     BSD-3-Clause
+ */
+
+/*
+ * This program tests the ability of the linux helper to create ODP threads,
+ * either implemented as linux pthreads or as linux processes, depending on
+ * the option passed to the program (--odph_proc, --odph_thread or both)
+ */
+
+#include <test_debug.h>
+#include <odp_api.h>
+#include <odp/helper/linux.h>
+
+#define NUMBER_WORKERS 16
+static int worker_fn(void *arg TEST_UNUSED)
+{
+	/* depend on the odp helper to call odp_init_local */
+
+	printf("Worker thread on CPU %d\n", odp_cpu_id());
+
+	/* depend on the odp helper to call odp_term_local */
+
+	return 0;
+}
+
+/* Create additional dataplane opdthreads */
+int main(int argc, char *argv[])
+{
+	odp_instance_t instance;
+	odph_odpthread_params_t thr_params;
+	odph_odpthread_t thread_tbl[NUMBER_WORKERS];
+	odp_cpumask_t cpu_mask;
+	int num_workers;
+	int cpu;
+	int ret;
+	char cpumaskstr[ODP_CPUMASK_STR_SIZE];
+
+	/* let helper collect its own arguments (e.g. --odph_proc) */
+	odph_parse_options(argc, argv);
+
+	if (odp_init_global(&instance, NULL, NULL)) {
+		LOG_ERR("Error: ODP global init failed.\n");
+		exit(EXIT_FAILURE);
+	}
+
+	if (odp_init_local(instance, ODP_THREAD_CONTROL)) {
+		LOG_ERR("Error: ODP local init failed.\n");
+		exit(EXIT_FAILURE);
+	}
+
+	/* discover how many opdthreads this system can support */
+	num_workers = odp_cpumask_default_worker(&cpu_mask, NUMBER_WORKERS);
+	if (num_workers < NUMBER_WORKERS) {
+		printf("System can only support %d threads and not the %d requested\n",
+		       num_workers, NUMBER_WORKERS);
+	}
+
+	/* generate a summary for the user */
+	(void)odp_cpumask_to_str(&cpu_mask, cpumaskstr, sizeof(cpumaskstr));
+	printf("default cpu mask:           %s\n", cpumaskstr);
+	printf("default num worker threads: %i\n", num_workers);
+
+	cpu = odp_cpumask_first(&cpu_mask);
+	printf("the first CPU:              %i\n", cpu);
+
+	/* reserve cpu 0 for the control plane so remove it from
+	 * the default mask */
+	odp_cpumask_clr(&cpu_mask, 0);
+	num_workers = odp_cpumask_count(&cpu_mask);
+	(void)odp_cpumask_to_str(&cpu_mask, cpumaskstr, sizeof(cpumaskstr));
+	printf("new cpu mask:               %s\n", cpumaskstr);
+	printf("new num worker threads:     %i\n\n", num_workers);
+
+	memset(&thr_params, 0, sizeof(thr_params));
+	thr_params.start    = worker_fn;
+	thr_params.arg      = NULL;
+	thr_params.thr_type = ODP_THREAD_WORKER;
+	thr_params.instance = instance;
+
+	odph_odpthreads_create(&thread_tbl[0], &cpu_mask, &thr_params);
+
+	ret = odph_odpthreads_join(thread_tbl);
+	if (ret < 0)
+		exit(EXIT_FAILURE);
+
+	if (odp_term_local()) {
+		LOG_ERR("Error: ODP local term failed.\n");
+		exit(EXIT_FAILURE);
+	}
+
+	if (odp_term_global(instance)) {
+		LOG_ERR("Error: ODP global term failed.\n");
+		exit(EXIT_FAILURE);
+	}
+
+	return 0;
+}
diff --git a/helper/test/odpthreads_as_mixed b/helper/test/odpthreads_as_mixed
new file mode 100755
index 0000000..61fdad1
--- /dev/null
+++ b/helper/test/odpthreads_as_mixed
@@ -0,0 +1,15 @@
+#!/bin/sh
+#
+# Copyright (c) 2016, Linaro Limited
+# All rights reserved.
+#
+# SPDX-License-Identifier:	BSD-3-Clause
+#
+
+PATH=$(dirname $0):$PATH
+PATH=.:$PATH
+
+# when both "--odph_thread" and "--odph_process" are given, every second odp
+# thread is created as a linux process.
+odpthreads --odph_thread --odph_process
+
diff --git a/helper/test/odpthreads_as_processes b/helper/test/odpthreads_as_processes
new file mode 100755
index 0000000..e3636fb
--- /dev/null
+++ b/helper/test/odpthreads_as_processes
@@ -0,0 +1,15 @@
+#!/bin/sh
+#
+# Copyright (c) 2016, Linaro Limited
+# All rights reserved.
+#
+# SPDX-License-Identifier:	BSD-3-Clause
+#
+
+PATH=$(dirname $0):$PATH
+PATH=.:$PATH
+
+# The odpthreads test recognise the "--odph_process" option to create
+# odp threads as linux process:
+odpthreads --odph_process
+
diff --git a/helper/test/odpthreads_as_pthreads b/helper/test/odpthreads_as_pthreads
new file mode 100755
index 0000000..3d1fe29
--- /dev/null
+++ b/helper/test/odpthreads_as_pthreads
@@ -0,0 +1,15 @@
+#!/bin/sh
+#
+# Copyright (c) 2016, Linaro Limited
+# All rights reserved.
+#
+# SPDX-License-Identifier:	BSD-3-Clause
+#
+
+PATH=$(dirname $0):$PATH
+PATH=.:$PATH
+
+# The odpthreads test recognise the "--odph_thread" option to create
+# odp threads as linux pthreads:
+odpthreads --odph_thread
+
