diff --git a/platform/linux-generic/Makefile.am b/platform/linux-generic/Makefile.am
index f4dfdc1..f30c6ff 100644
--- a/platform/linux-generic/Makefile.am
+++ b/platform/linux-generic/Makefile.am
@@ -20,6 +20,7 @@ include_HEADERS = \
 		  $(top_srcdir)/platform/linux-generic/include/api/odp_debug.h \
 		  $(top_srcdir)/platform/linux-generic/include/api/odp_hints.h \
 		  $(top_srcdir)/platform/linux-generic/include/api/odp_init.h \
+		  $(top_srcdir)/platform/linux-generic/include/api/odp_log.h \
 		  $(top_srcdir)/platform/linux-generic/include/api/odp_packet_flags.h \
 		  $(top_srcdir)/platform/linux-generic/include/api/odp_packet.h \
 		  $(top_srcdir)/platform/linux-generic/include/api/odp_packet_io.h \
@@ -58,6 +59,7 @@ __LIB__libodp_la_SOURCES = \
 			   odp_crypto.c \
 			   odp_init.c \
 			   odp_linux.c \
+			   odp_log.c \
 			   odp_packet.c \
 			   odp_packet_flags.c \
 			   odp_packet_io.c \
diff --git a/platform/linux-generic/include/api/odp_log.h b/platform/linux-generic/include/api/odp_log.h
new file mode 100644
index 0000000..b8c49e3
--- /dev/null
+++ b/platform/linux-generic/include/api/odp_log.h
@@ -0,0 +1,59 @@
+/* Copyright (c) 2014, Linaro Limited
+ * All rights reserved.
+ *
+ * SPDX-License-Identifier:     BSD-3-Clause
+ */
+
+
+/**
+ * @file
+ *
+ * ODP internal logging facility - public management API
+ */
+
+#ifndef ODP_LOG_H_
+#define ODP_LOG_H_
+
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** Fatal error for function, may not return to caller */
+#define ODP_LOG_LVL_FATAL   1U
+/** Error prevented function to succeed but caller should be able to recover */
+#define ODP_LOG_LVL_ERROR   2U
+/** Problem detected in function but recovered locally */
+#define ODP_LOG_LVL_WARNING 3U
+/** Informational message intended for user */
+#define ODP_LOG_LVL_INFORM  4U
+/** Debug message intended for developer */
+#define ODP_LOG_LVL_DEBUG   5U
+
+#define ODP_LOG_LVL_NONE 0U  /**< Disable all log levels */
+#define ODP_LOG_LVL_ALL  7U /**< Enable all log levels */
+
+typedef volatile uint32_t odp_log_t;
+#define ODP_LOG_INVALID 0U
+
+/** Call-back type definition for user-defined log facility */
+typedef void (*odp_log_callback_t)(const char *name,
+				   unsigned level,
+				   const char *fmt,
+				   va_list ap);
+
+/** Set the log level for a source identified by name */
+int odp_log_set_level(const char *name, unsigned level);
+/** Install a user-defined call-back for handling log messages */
+void odp_log_set_callback(odp_log_callback_t cb);
+/** Iterate through all registered log sources */
+int odp_log_next(odp_log_t *phdl, char *name, size_t namelen, unsigned *level);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/platform/linux-generic/include/odp_log_internal.h b/platform/linux-generic/include/odp_log_internal.h
new file mode 100644
index 0000000..7ac3ab1
--- /dev/null
+++ b/platform/linux-generic/include/odp_log_internal.h
@@ -0,0 +1,115 @@
+/* Copyright (c) 2014, Linaro Limited
+ * All rights reserved.
+ *
+ * SPDX-License-Identifier:     BSD-3-Clause
+ */
+
+
+/**
+ * @file
+ *
+ * ODP internal logging facility - client API
+ */
+
+#ifndef ODP_LOG_INTERNAL_H_
+#define ODP_LOG_INTERNAL_H_
+
+#include <stdint.h>
+#include <odp_log.h>
+#include <odp_hints.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef ODP_LOG_LEVEL
+#define ODP_LOG_LEVEL ODP_LOG_LVL_ALL
+#endif
+
+/* Report a message to the log */
+#define ODP_LOG_MSG(_lvl, _hdl, _fmt, ...) \
+do { \
+	unsigned __lvl = (_lvl); \
+	odp_log_t __hdl = (_hdl); \
+	const char *__fmt = (_fmt); \
+	if (odp_unlikely((__hdl & 0x7) >= __lvl)) \
+		odp_log_printf(__lvl, __hdl, __fmt, ##__VA_ARGS__); \
+} while (0)
+
+#if ODP_LOG_LEVEL >= ODP_LOG_LVL_FATAL
+#define ODP_LOG_FATAL(_src, _fmt, ...) \
+ODP_LOG_MSG(ODP_LOG_LVL_FATAL, (_odp_log_src_ ## _src), (_fmt), ##__VA_ARGS__)
+#else
+#define ODP_LOG_FATAL(_src, _fmt, ...) (void)0
+#endif
+
+#if ODP_LOG_LEVEL >= ODP_LOG_LVL_ERROR
+#define ODP_LOG_ERROR(_src, _fmt, ...) \
+ODP_LOG_MSG(ODP_LOG_LVL_ERROR, (_odp_log_src_ ## _src), (_fmt), ##__VA_ARGS__)
+#else
+#define ODP_LOG_ERROR(_src, _fmt, ...) (void)0
+#endif
+
+#if ODP_LOG_LEVEL >= ODP_LOG_LVL_WARNING
+#define ODP_LOG_WARNING(_src, _fmt, ...) \
+ODP_LOG_MSG(ODP_LOG_LVL_WARNING, (_odp_log_src_ ## _src), (_fmt), ##__VA_ARGS__)
+#else
+#define ODP_LOG_WARNING(_src, _fmt, ...) (void)0
+#endif
+
+#if ODP_LOG_LEVEL >= ODP_LOG_LVL_INFORM
+#define ODP_LOG_INFORM(_src, _fmt, ...) \
+ODP_LOG_MSG(ODP_LOG_LVL_INFORM, (_odp_log_src_ ## _src), (_fmt), ##__VA_ARGS__)
+#else
+#define ODP_LOG_INFORM(_src, _fmt, ...) (void)0
+#endif
+
+#if ODP_LOG_LEVEL >= ODP_LOG_LVL_DEBUG
+#define ODP_LOG_DEBUG(_src, _fmt, ...) \
+ODP_LOG_MSG(ODP_LOG_LVL_DEBUG, (_odp_log_src_ ## _src), (_fmt), ##__VA_ARGS__)
+#else
+#define ODP_LOG_DEBUG(_src, _fmt, ...) (void)0
+#endif
+
+
+#define ODP_LOG_DECLARE(src) odp_log_t _odp_log_src_ ## src
+#define ODP_LOG_REGISTER(src) odp_log_register(&_odp_log_src_ ## src, #src)
+
+/* Calls indirectly used by log clients. Use macros above! */
+/* Register a log source with the specified name */
+void odp_log_register(odp_log_t *hdl, const char *name);
+/* Report a log message */
+void odp_log_printf(unsigned lvl, odp_log_t hdl, const char *fmt, ...);
+
+/* Init log module, specify call-back (or NULL for default behaviour) */
+void odp_log_init(odp_log_callback_t cb);
+
+
+/* Example usage:
+
+ODP_LOG_DECLARE(odp_queue);
+
+//Use default output mechanism
+odp_log_init(NULL);
+
+//Register log source and set default log level
+ODP_LOG_REGISTER(odp_queue);
+
+//Log a message with severity error
+ODP_LOG_ERROR(odp_queue, "Failed to create queue, param=%u\n", 242);
+
+//Set log level for a source to log everything
+(void)odp_log_set_level("odp_queue", ODP_LOG_LVL_ALL);
+
+//Install a custom vprintf function to handle log messages
+odp_log_set_callback(my_vprintf);
+
+//Log a message that will be forwarded to the user-specified log
+ODP_LOG_INFORM(odp_queue, "Message printed using user-defined call-back\n");
+
+*/
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/platform/linux-generic/odp_init.c b/platform/linux-generic/odp_init.c
index 5b7e192..3677f1b 100644
--- a/platform/linux-generic/odp_init.c
+++ b/platform/linux-generic/odp_init.c
@@ -7,46 +7,54 @@
 #include <odp_init.h>
 #include <odp_internal.h>
 #include <odp_debug.h>
+#include <odp_log_internal.h>
 
+ODP_LOG_DECLARE(odp_init);
 
 int odp_init_global(void)
 {
+	/* Init log mechanism first so that it can be used by all other
+	   modules (including init) */
+	odp_log_init(NULL); /* NULL => Use default output mechanism */
+
+	ODP_LOG_REGISTER(odp_init);
+
 	odp_thread_init_global();
 
 	odp_system_info_init();
 
 	if (odp_shm_init_global()) {
-		ODP_ERR("ODP shm init failed.\n");
+		ODP_LOG_FATAL(odp_init, "ODP shm init failed.\n");
 		return -1;
 	}
 
 	if (odp_buffer_pool_init_global()) {
-		ODP_ERR("ODP buffer pool init failed.\n");
+		ODP_LOG_FATAL(odp_init, "ODP buffer pool init failed.\n");
 		return -1;
 	}
 
 	if (odp_queue_init_global()) {
-		ODP_ERR("ODP queue init failed.\n");
+		ODP_LOG_FATAL(odp_init, "ODP queue init failed.\n");
 		return -1;
 	}
 
 	if (odp_schedule_init_global()) {
-		ODP_ERR("ODP schedule init failed.\n");
+		ODP_LOG_FATAL(odp_init, "ODP schedule init failed.\n");
 		return -1;
 	}
 
 	if (odp_pktio_init_global()) {
-		ODP_ERR("ODP packet io init failed.\n");
+		ODP_LOG_FATAL(odp_init, "ODP packet io init failed.\n");
 		return -1;
 	}
 
 	if (odp_timer_init_global()) {
-		ODP_ERR("ODP timer init failed.\n");
+		ODP_LOG_FATAL(odp_init, "ODP timer init failed.\n");
 		return -1;
 	}
 
 	if (odp_crypto_init_global()) {
-		ODP_ERR("ODP crypto init failed.\n");
+		ODP_LOG_FATAL(odp_init, "ODP crypto init failed.\n");
 		return -1;
 	}
 
@@ -59,12 +67,12 @@ int odp_init_local(int thr_id)
 	odp_thread_init_local(thr_id);
 
 	if (odp_pktio_init_local()) {
-		ODP_ERR("ODP packet io local init failed.\n");
+		ODP_LOG_FATAL(odp_init, "ODP packet io local init failed.\n");
 		return -1;
 	}
 
 	if (odp_schedule_init_local()) {
-		ODP_ERR("ODP schedule local init failed.\n");
+		ODP_LOG_FATAL(odp_init, "ODP schedule local init failed.\n");
 		return -1;
 	}
 
diff --git a/platform/linux-generic/odp_log.c b/platform/linux-generic/odp_log.c
new file mode 100644
index 0000000..99b2803
--- /dev/null
+++ b/platform/linux-generic/odp_log.c
@@ -0,0 +1,189 @@
+/* Copyright (c) 2014, Linaro Limited
+ * All rights reserved.
+ *
+ * SPDX-License-Identifier:     BSD-3-Clause
+ */
+
+
+/**
+ * @file
+ *
+ * ODP logging utility
+ */
+
+#include <errno.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <odp_debug.h>
+#include <odp_spinlock.h>
+#include <odp_sync.h>
+#include <odp_log.h>
+#include <odp_log_internal.h>
+
+#ifndef ODP_LOG_DEFAULT_MASK
+#define ODP_LOG_DEFAULT_MASK (ODP_LOG_LVL_FATAL | ODP_LOG_LVL_ERROR)
+#endif
+
+/* Maximum number of log sources */
+#ifndef ODP_LOG_MAX
+#define ODP_LOG_MAX 64
+#endif
+
+ODP_STATIC_ASSERT(ODP_LOG_LVL_ALL == 7U, "ODP_LOG_LVL_ALL_ERROR");
+
+#define HDL_TO_LVL(hdl) ((hdl) & 0x7U)
+#define HDL_TO_SLOT(hdl) (((hdl) >> 3U) - 1U)
+#define SLOT_LVL_TO_HDL(slot, lvl) ((((slot) + 1U) << 3U) | (lvl))
+#define INVALID_SLOT ~0U
+
+struct source {
+	odp_log_t *phdl;	/* Pointer to the user's odp_log_t variable */
+	const char *name;
+};
+
+static odp_spinlock_t lock;
+static volatile odp_log_callback_t callback;
+ODP_LOG_DECLARE(odp_log);
+static struct source sources[ODP_LOG_MAX];
+
+void
+odp_log_init(odp_log_callback_t cb)
+{
+	odp_spinlock_init(&lock);
+	callback = cb;
+	memset(sources, 0, sizeof(sources));
+	odp_sync_stores();
+	ODP_LOG_REGISTER(odp_log);
+	odp_log_set_level("odp_log", ODP_LOG_LVL_ALL);
+}
+
+/* Register a log source with the specified name */
+void
+odp_log_register(odp_log_t *phdl, const char *name)
+{
+	unsigned i;
+	unsigned slot = INVALID_SLOT;
+	odp_spinlock_lock(&lock);
+	*phdl = ODP_LOG_INVALID;
+	for (i = 0; i < ODP_LOG_MAX; i++) {
+		/* Check for unused slot */
+		if (sources[i].phdl == NULL && slot == INVALID_SLOT) {
+			/* Found first unused slot */
+			slot = i;
+		}
+		/* Check for source already present */
+		if (sources[i].name != NULL &&
+		    strcmp(sources[i].name, name) == 0) {
+			odp_spinlock_unlock(&lock);
+			ODP_LOG_ERROR(odp_log,
+				      "Attempt to re-register source %s\n",
+				      name);
+			return;
+		}
+	}
+	if (slot != INVALID_SLOT) {
+		sources[slot].phdl = phdl;
+		sources[slot].name = name;
+		*phdl = SLOT_LVL_TO_HDL(slot, ODP_LOG_DEFAULT_MASK);
+	}
+	odp_spinlock_unlock(&lock);
+	if (slot != INVALID_SLOT)
+		ODP_LOG_DEBUG(odp_log, "Registered source %s level %u\n",
+			      name, ODP_LOG_DEFAULT_MASK);
+	else
+		ODP_LOG_FATAL(odp_log, "Failed to register source %s\n", name);
+}
+
+/* Set the log level for a source identified by name */
+int
+odp_log_set_level(const char *name, unsigned level)
+{
+	unsigned i;
+	bool found = false;
+	if (level > ODP_LOG_LVL_ALL)
+		level = ODP_LOG_LVL_ALL;
+	odp_spinlock_lock(&lock);
+	for (i = 0; i < ODP_LOG_MAX; i++) {
+		if (name == NULL ||
+		    (sources[i].name != NULL &&
+		     strcmp(sources[i].name, name) == 0)) {
+			*sources[i].phdl = SLOT_LVL_TO_HDL(i, level);
+			found = true;
+			break;
+		}
+	}
+	odp_spinlock_unlock(&lock);
+	if (found) {
+		if (name == NULL)
+			ODP_LOG_DEBUG(odp_log,
+				      "Set log level for all sources to %u\n",
+				      level);
+		else
+			ODP_LOG_DEBUG(odp_log,
+				      "Set log level for source %s to %u\n",
+				      name, level);
+	} else {
+		errno = ENOENT;
+	}
+	return found ? 0 : -1;
+}
+
+/* Report a log message */
+void
+odp_log_printf(unsigned lvl, odp_log_t hdl, const char *fmt, ...)
+{
+	unsigned slot = HDL_TO_SLOT(hdl);
+	if (odp_likely(slot < ODP_LOG_MAX)) {
+		const char *name = sources[slot].name;
+		va_list ap;
+		odp_log_callback_t cb;
+		va_start(ap, fmt);
+		cb = callback;
+		if (cb != NULL) {
+			cb(name, lvl, fmt, ap);
+		} else {
+			fprintf(stderr, "%s/%c: ", name,
+				" FEWID67"[lvl & 0x7U]);
+			vfprintf(stderr, fmt, ap);
+		}
+		va_end(ap);
+	}
+	/* Else ignore call with invalid handle */
+}
+
+/* Install a user-defined call-back for handling log messages */
+void
+odp_log_set_callback(odp_log_callback_t cb)
+{
+	callback = cb;
+	odp_sync_stores();
+}
+
+/* Iterate through all registered log sources */
+int
+odp_log_next(odp_log_t *phdl, char *name, size_t namelen, unsigned *plvl)
+{
+	int another;
+	unsigned slot;
+	odp_spinlock_lock(&lock);
+	if (*phdl == ODP_LOG_INVALID)
+		slot = 0;	/* First slot */
+	else
+		slot = HDL_TO_SLOT(*phdl) + 1U;	/* Next slot */
+	if (slot < ODP_LOG_MAX && sources[slot].phdl != NULL) {
+		*phdl = SLOT_LVL_TO_HDL(slot, 0);
+		strncpy(name, sources[slot].name, namelen - 1U);
+		name[namelen - 1U] = 0;
+		*plvl = HDL_TO_LVL(*sources[slot].phdl);
+		another = 1;	/* true */
+	} else {
+		another = 0;	/* false */
+	}
+	odp_spinlock_unlock(&lock);
+	return another;
+}
