diff --git a/include/odp.h b/include/odp.h
index 2bac510..fe1dc74 100644
--- a/include/odp.h
+++ b/include/odp.h
@@ -53,6 +53,7 @@ extern "C" {
 #include <odp/event.h>
 #include <odp/random.h>
 #include <odp/errno.h>
+#include <odp/thrmask.h>
 
 #ifdef __cplusplus
 }
diff --git a/include/odp/api/thrmask.h b/include/odp/api/thrmask.h
new file mode 100644
index 0000000..ebc31f2
--- /dev/null
+++ b/include/odp/api/thrmask.h
@@ -0,0 +1,229 @@
+/* Copyright (c) 2015, Linaro Limited
+ * All rights reserved.
+ *
+ * SPDX-License-Identifier:     BSD-3-Clause
+ */
+
+/**
+ * @file
+ *
+ * ODP thread masks
+ */
+
+#ifndef ODP_API_THRMASK_H_
+#define ODP_API_THRMASK_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <odp/std_types.h>
+
+/** @addtogroup odp_thread
+ *  Thread mask operations.
+ *  @{
+ */
+
+/**
+ * @def ODP_THRMASK_STR_SIZE
+ * Minimum size of output buffer for odp_thrmask_to_str()
+ */
+
+/**
+ * Add thread mask bits from a string
+ *
+ * @param[out] mask  Thread mask to modify
+ * @param      str   Hexadecimal digits in a string. Thread ID zero is located
+ *                   at the least significant bit (0x1).
+ */
+void odp_thrmask_from_str(odp_thrmask_t *mask, const char *str);
+
+/**
+ * Format Thread mask as a string of hexadecimal digits
+ *
+ * @param mask       Thread mask to format
+ * @param[out] str   Output buffer (use ODP_THRMASK_STR_SIZE)
+ * @param size       Size of output buffer
+ *
+ * @return number of characters written (including terminating null char)
+ * @retval <0 on failure (buffer too small)
+ */
+int32_t odp_thrmask_to_str(const odp_thrmask_t *mask, char *str, int32_t size);
+
+/**
+ * Clear entire thread mask
+ * @param mask       Thread mask to clear
+ */
+void odp_thrmask_zero(odp_thrmask_t *mask);
+
+/**
+ * Add thread to mask
+ * @param mask       Thread mask to update
+ * @param thr        Thread ID
+ */
+void odp_thrmask_set(odp_thrmask_t *mask, int thr);
+
+/**
+ * Set all threads in mask
+ *
+ * Set all possible threads in the mask. All threads from 0 to
+ * odp_thrmask_count() minus one are set, regardless of which threads are
+ * actually active.
+ *
+ * @param mask       Thread mask to set
+ */
+void odp_thrmask_setall(odp_thrmask_t *mask);
+
+/**
+ * Remove thread from mask
+ * @param mask       Thread mask to update
+ * @param thr        Thread ID
+ */
+void odp_thrmask_clr(odp_thrmask_t *mask, int thr);
+
+/**
+ * Test if thread is a member of mask
+ *
+ * @param mask       Thread mask to test
+ * @param thr        Thread ID
+ *
+ * @return non-zero if set
+ * @retval 0 if not set
+ */
+int odp_thrmask_isset(const odp_thrmask_t *mask, int thr);
+
+/**
+ * Count number of threads set in mask
+ *
+ * @param mask       Thread mask
+ *
+ * @return population count
+ */
+int odp_thrmask_count(const odp_thrmask_t *mask);
+
+/**
+ * Member-wise AND over two thread masks
+ *
+ * @param dest       Destination thread mask (may be one of the source masks)
+ * @param src1       Source thread mask 1
+ * @param src2       Source thread mask 2
+ */
+void odp_thrmask_and(odp_thrmask_t *dest, const odp_thrmask_t *src1,
+		     const odp_thrmask_t *src2);
+
+/**
+ * Member-wise OR over two thread masks
+ *
+ * @param dest       Destination thread mask (may be one of the source masks)
+ * @param src1       Source thread mask 1
+ * @param src2       Source thread mask 2
+ */
+void odp_thrmask_or(odp_thrmask_t *dest, const odp_thrmask_t *src1,
+		    const odp_thrmask_t *src2);
+
+/**
+ * Member-wise XOR over two thread masks
+ *
+ * @param dest       Destination thread mask (may be one of the source masks)
+ * @param src1       Source thread mask 1
+ * @param src2       Source thread mask 2
+ */
+void odp_thrmask_xor(odp_thrmask_t *dest, const odp_thrmask_t *src1,
+		     const odp_thrmask_t *src2);
+
+/**
+ * Test if two thread masks contain the same threads
+ *
+ * @param mask1      Thread mask 1
+ * @param mask2      Thread mask 2
+ *
+ * @retval non-zero if thread masks equal
+ * @retval 0 if thread masks not equal
+ */
+int odp_thrmask_equal(const odp_thrmask_t *mask1,
+		      const odp_thrmask_t *mask2);
+
+/**
+ * Copy a thread mask
+ *
+ * @param dest       Destination thread mask
+ * @param src        Source thread mask
+ */
+void odp_thrmask_copy(odp_thrmask_t *dest, const odp_thrmask_t *src);
+
+/**
+ * Find first set thread in mask
+ *
+ * @param mask       thread mask
+ *
+ * @return Thread ID
+ * @retval <0 if no thread found
+ */
+int odp_thrmask_first(const odp_thrmask_t *mask);
+
+/**
+ * Find last set thread in mask
+ *
+ * @param mask       Thread mask
+ *
+ * @return Thread ID
+ * @retval <0 if no thread found
+ */
+int odp_thrmask_last(const odp_thrmask_t *mask);
+
+/**
+ * Find next set thread in mask
+ *
+ * Finds the next thread in the thread mask, starting at the thread passed.
+ * Use with odp_thrmask_first to traverse a thread mask, e.g.
+ *
+ * int thr = odp_thrmask_first(&mask);
+ * while (0 <= thr) {
+ *     ...
+ *     ...
+ *     thr = odp_thrmask_next(&mask, thr);
+ * }
+ *
+ * @param mask       Thread mask
+ * @param thr        Thread to start from
+ *
+ * @return Thread ID
+ * @retval <0 if no thread found
+ *
+ * @see odp_thrmask_first()
+ */
+int odp_thrmask_next(const odp_thrmask_t *mask, int thr);
+
+/**
+ * Worker thread mask
+ *
+ * Initializes thread mask with current worker threads and returns the count
+ * set.
+ *
+ * @param[out] mask  Thread mask to initialize
+ *
+ * @return Number of threads in the mask
+ */
+int odp_thrmask_worker(odp_thrmask_t *mask);
+
+/**
+ * Control thread mask
+ *
+ * Initializes thread mask with current control threads and returns the count
+ * set.
+ *
+ * @param[out] mask  Thread mask to initialize
+ *
+ * @return Number of threads in the mask
+ */
+int odp_thrmask_control(odp_thrmask_t *mask);
+
+/**
+ * @}
+ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/platform/linux-generic/Makefile.am b/platform/linux-generic/Makefile.am
index 34db0b1..d226483 100644
--- a/platform/linux-generic/Makefile.am
+++ b/platform/linux-generic/Makefile.am
@@ -40,6 +40,7 @@ odpinclude_HEADERS = \
 		  $(srcdir)/include/odp/sync.h \
 		  $(srcdir)/include/odp/system_info.h \
 		  $(srcdir)/include/odp/thread.h \
+		  $(srcdir)/include/odp/thrmask.h \
 		  $(srcdir)/include/odp/ticketlock.h \
 		  $(srcdir)/include/odp/time.h \
 		  $(srcdir)/include/odp/timer.h \
@@ -64,6 +65,7 @@ odpplatinclude_HEADERS = \
 		  $(srcdir)/include/odp/plat/shared_memory_types.h \
 		  $(srcdir)/include/odp/plat/spinlock_types.h \
 		  $(srcdir)/include/odp/plat/strong_types.h \
+		  $(srcdir)/include/odp/plat/thrmask_types.h \
 		  $(srcdir)/include/odp/plat/ticketlock_types.h \
 		  $(srcdir)/include/odp/plat/timer_types.h \
 		  $(srcdir)/include/odp/plat/version_types.h
@@ -100,6 +102,7 @@ odpapiinclude_HEADERS = \
 		  $(top_srcdir)/include/odp/api/sync.h \
 		  $(top_srcdir)/include/odp/api/system_info.h \
 		  $(top_srcdir)/include/odp/api/thread.h \
+		  $(top_srcdir)/include/odp/api/thrmask.h \
 		  $(top_srcdir)/include/odp/api/ticketlock.h \
 		  $(top_srcdir)/include/odp/api/time.h \
 		  $(top_srcdir)/include/odp/api/timer.h \
@@ -149,6 +152,7 @@ __LIB__libodp_la_SOURCES = \
 			   odp_spinlock.c \
 			   odp_system_info.c \
 			   odp_thread.c \
+			   odp_thrmask.c \
 			   odp_ticketlock.c \
 			   odp_time.c \
 			   odp_timer.c \
diff --git a/platform/linux-generic/include/odp/plat/thrmask_types.h b/platform/linux-generic/include/odp/plat/thrmask_types.h
new file mode 100644
index 0000000..30915c9
--- /dev/null
+++ b/platform/linux-generic/include/odp/plat/thrmask_types.h
@@ -0,0 +1,48 @@
+/* Copyright (c) 2015, Linaro Limited
+ * All rights reserved.
+ *
+ * SPDX-License-Identifier:     BSD-3-Clause
+ */
+
+/**
+ * @file
+ *
+ * ODP thread masks
+ */
+
+#ifndef ODP_THRMASK_TYPES_H_
+#define ODP_THRMASK_TYPES_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** @addtogroup odp_thread
+ *  @{
+ */
+
+#include <odp/plat/cpumask_types.h>
+
+/**
+ * Minimum size of output buffer for odp_thrmask_to_str()
+ */
+#define ODP_THRMASK_STR_SIZE ODP_CPUMASK_STR_SIZE
+
+/**
+ * Thread mask
+ *
+ * Don't access directly, use access functions.
+ */
+typedef struct odp_thrmask_t {
+	odp_cpumask_t m; /**< @private Mask*/
+} odp_thrmask_t;
+
+/**
+ * @}
+ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/platform/linux-generic/include/odp/thrmask.h b/platform/linux-generic/include/odp/thrmask.h
new file mode 100644
index 0000000..0e98ece
--- /dev/null
+++ b/platform/linux-generic/include/odp/thrmask.h
@@ -0,0 +1,36 @@
+/* Copyright (c) 2015, Linaro Limited
+ * All rights reserved.
+ *
+ * SPDX-License-Identifier:     BSD-3-Clause
+ */
+
+/**
+ * @file
+ *
+ * ODP thread masks
+ */
+
+#ifndef ODP_PLAT_THRMASK_H_
+#define ODP_PLAT_THRMASK_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <odp/plat/thrmask_types.h>
+
+/** @ingroup odp_thread
+ *  @{
+ */
+
+/**
+ * @}
+ */
+
+#include <odp/api/thrmask.h>
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/platform/linux-generic/odp_thrmask.c b/platform/linux-generic/odp_thrmask.c
new file mode 100644
index 0000000..d10bbf1
--- /dev/null
+++ b/platform/linux-generic/odp_thrmask.c
@@ -0,0 +1,102 @@
+/* Copyright (c) 2015, Linaro Limited
+ * All rights reserved.
+ *
+ * SPDX-License-Identifier:     BSD-3-Clause
+ */
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+#include <sched.h>
+
+#include <odp/config.h>
+#include <odp/thrmask.h>
+#include <odp/cpumask.h>
+
+#if CPU_SETSIZE < ODP_CONFIG_MAX_THREADS
+#error Thread mask does not fit all thread IDs
+#endif
+
+void odp_thrmask_from_str(odp_thrmask_t *mask, const char *str)
+{
+	odp_cpumask_from_str(&mask->m, str);
+}
+
+int32_t odp_thrmask_to_str(const odp_thrmask_t *mask, char *str, int32_t size)
+{
+	return odp_cpumask_to_str(&mask->m, str, size);
+}
+
+void odp_thrmask_zero(odp_thrmask_t *mask)
+{
+	odp_cpumask_zero(&mask->m);
+}
+
+void odp_thrmask_set(odp_thrmask_t *mask, int thr)
+{
+	odp_cpumask_set(&mask->m, thr);
+}
+
+void odp_thrmask_setall(odp_thrmask_t *mask)
+{
+	odp_cpumask_setall(&mask->m);
+}
+
+void odp_thrmask_clr(odp_thrmask_t *mask, int thr)
+{
+	odp_cpumask_clr(&mask->m, thr);
+}
+
+int odp_thrmask_isset(const odp_thrmask_t *mask, int thr)
+{
+	return odp_cpumask_isset(&mask->m, thr);
+}
+
+int odp_thrmask_count(const odp_thrmask_t *mask)
+{
+	return odp_cpumask_count(&mask->m);
+}
+
+void odp_thrmask_and(odp_thrmask_t *dest, const odp_thrmask_t *src1,
+		     const odp_thrmask_t *src2)
+{
+	odp_cpumask_and(&dest->m, &src1->m, &src2->m);
+}
+
+void odp_thrmask_or(odp_thrmask_t *dest, const odp_thrmask_t *src1,
+		    const odp_thrmask_t *src2)
+{
+	odp_cpumask_or(&dest->m, &src1->m, &src2->m);
+}
+
+void odp_thrmask_xor(odp_thrmask_t *dest, const odp_thrmask_t *src1,
+		     const odp_thrmask_t *src2)
+{
+	odp_cpumask_xor(&dest->m, &src1->m, &src2->m);
+}
+
+int odp_thrmask_equal(const odp_thrmask_t *mask1,
+		      const odp_thrmask_t *mask2)
+{
+	return odp_cpumask_equal(&mask1->m, &mask2->m);
+}
+
+void odp_thrmask_copy(odp_thrmask_t *dest, const odp_thrmask_t *src)
+{
+	odp_cpumask_copy(&dest->m, &src->m);
+}
+
+int odp_thrmask_first(const odp_thrmask_t *mask)
+{
+	return odp_cpumask_first(&mask->m);
+}
+
+int odp_thrmask_last(const odp_thrmask_t *mask)
+{
+	return odp_cpumask_last(&mask->m);
+}
+
+int odp_thrmask_next(const odp_thrmask_t *mask, int thr)
+{
+	return odp_cpumask_next(&mask->m, thr);
+}
