diff --git a/configure.ac b/configure.ac
index 296c9c5..220f89a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -51,6 +51,8 @@ AC_ARG_WITH([platform],
 
 AC_SUBST([with_platform])
 
+AM_CONDITIONAL([ODP_NETMAP_ENABLED], [test x$with_platform = xlinux-netmap ])
+
 AC_ARG_WITH([sdk-install-path],
 AC_HELP_STRING([--with-sdk-install-path=DIR Path to external libs and headers],
                [(or in the default path if not specified).]),
@@ -150,6 +152,7 @@ AC_CHECK_FUNCS([bzero clock_gettime gethostbyname getpagesize gettimeofday memse
 AC_CONFIG_FILES([Makefile
 		 platform/Makefile
 		 platform/linux-generic/Makefile
+		 platform/linux-netmap/Makefile
 		 example/Makefile
 		 example/generator/Makefile
 		 example/ipsec/Makefile
diff --git a/platform/linux-netmap/Makefile.am b/platform/linux-netmap/Makefile.am
new file mode 100644
index 0000000..b783521
--- /dev/null
+++ b/platform/linux-netmap/Makefile.am
@@ -0,0 +1,85 @@
+include $(top_srcdir)/Makefile.inc
+include $(top_srcdir)/platform/Makefile.inc
+
+PLAT_CFLAGS =
+if SDK_INSTALL_PATH_
+PLAT_CFLAGS += -I$(SDK_INSTALL_PATH)/sys
+endif
+
+AM_CFLAGS +=  $(PLAT_CFLAGS)
+AM_CFLAGS +=  -I$(srcdir)/include
+AM_CFLAGS +=  -I$(srcdir)/include/api
+AM_CFLAGS +=  -I$(top_srcdir)/platform/linux-generic/include
+AM_CFLAGS +=  -I$(top_srcdir)/platform/linux-generic/include/api
+AM_CFLAGS +=  -I$(top_srcdir)/helper/include
+
+include_HEADERS = \
+		  $(top_srcdir)/platform/linux-generic/include/api/odp.h \
+		  $(top_srcdir)/platform/linux-generic/include/api/odp_align.h \
+		  $(top_srcdir)/platform/linux-generic/include/api/odp_atomic.h \
+		  $(top_srcdir)/platform/linux-generic/include/api/odp_barrier.h \
+		  $(top_srcdir)/platform/linux-generic/include/api/odp_buffer.h \
+		  $(top_srcdir)/platform/linux-generic/include/api/odp_buffer_pool.h \
+		  $(top_srcdir)/platform/linux-generic/include/api/odp_byteorder.h \
+		  $(top_srcdir)/platform/linux-generic/include/api/odp_compiler.h \
+		  $(top_srcdir)/platform/linux-generic/include/api/odp_config.h \
+		  $(top_srcdir)/platform/linux-generic/include/api/odp_coremask.h \
+		  $(top_srcdir)/platform/linux-generic/include/api/odp_crypto.h \
+		  $(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_packet_flags.h \
+		  $(top_srcdir)/platform/linux-generic/include/api/odp_packet.h \
+		  $(top_srcdir)/platform/linux-generic/include/api/odp_packet_io.h \
+		  $(top_srcdir)/platform/linux-generic/include/api/odp_queue.h \
+		  $(top_srcdir)/platform/linux-generic/include/api/odp_rwlock.h \
+		  $(top_srcdir)/platform/linux-generic/include/api/odp_schedule.h \
+		  $(top_srcdir)/platform/linux-generic/include/api/odp_shared_memory.h \
+		  $(top_srcdir)/platform/linux-generic/include/api/odp_spinlock.h \
+		  $(top_srcdir)/platform/linux-generic/include/api/odp_std_types.h \
+		  $(top_srcdir)/platform/linux-generic/include/api/odp_sync.h \
+		  $(top_srcdir)/platform/linux-generic/include/api/odp_system_info.h \
+		  $(top_srcdir)/platform/linux-generic/include/api/odp_thread.h \
+		  $(top_srcdir)/platform/linux-generic/include/api/odp_ticketlock.h \
+		  $(top_srcdir)/platform/linux-generic/include/api/odp_time.h \
+		  $(top_srcdir)/platform/linux-generic/include/api/odp_timer.h \
+		  $(top_srcdir)/platform/linux-generic/include/api/odp_version.h \
+		  $(srcdir)/include/api/odp_pktio_types.h \
+		  $(srcdir)/include/api/odp_pktio_netmap.h \
+		  $(top_srcdir)/platform/linux-generic/include/api/odp_pktio_socket.h
+
+subdirheadersdir = $(includedir)/helper
+subdirheaders_HEADERS = \
+			$(top_srcdir)/helper/include/odph_chksum.h \
+			$(top_srcdir)/helper/include/odph_eth.h \
+			$(top_srcdir)/helper/include/odph_ip.h \
+			$(top_srcdir)/helper/include/odph_linux.h \
+			$(top_srcdir)/helper/include/odph_packet.h \
+			$(top_srcdir)/helper/include/odph_ring.h \
+			$(top_srcdir)/helper/include/odph_udp.h
+
+__LIB__libodp_la_SOURCES = \
+			   ../linux-generic/odp_barrier.c \
+			   ../linux-generic/odp_buffer.c \
+			   ../linux-generic/odp_buffer_pool.c \
+			   ../linux-generic/odp_coremask.c \
+			   ../linux-generic/odp_crypto.c \
+			   ../linux-generic/odp_init.c \
+			   ../linux-generic/odp_linux.c \
+			   ../linux-generic/odp_packet.c \
+			   ../linux-generic/odp_packet_flags.c \
+			   odp_packet_io.c \
+			   ../linux-generic/odp_packet_socket.c \
+			   ../linux-generic/odp_queue.c \
+			   ../linux-generic/odp_ring.c \
+			   ../linux-generic/odp_rwlock.c \
+			   ../linux-generic/odp_schedule.c \
+			   ../linux-generic/odp_shared_memory.c \
+			   ../linux-generic/odp_spinlock.c \
+			   ../linux-generic/odp_system_info.c \
+			   ../linux-generic/odp_thread.c \
+			   ../linux-generic/odp_ticketlock.c \
+			   ../linux-generic/odp_time.c \
+			   ../linux-generic/odp_timer.c \
+			   odp_packet_netmap.c
+
diff --git a/platform/linux-netmap/Makefile.inc b/platform/linux-netmap/Makefile.inc
new file mode 100644
index 0000000..e69de29
diff --git a/platform/linux-netmap/README b/platform/linux-netmap/README
new file mode 100644
index 0000000..6021445
--- /dev/null
+++ b/platform/linux-netmap/README
@@ -0,0 +1,90 @@
+Copyright (c) 2014, Linaro Limited
+All rights reserved.
+
+SPDX-License-Identifier:        BSD-3-Clause
+
+1. Intro
+==================================
+
+OpenDataPlane implementation using netmap accelerated packet I/O.
+The current implementation does not yet take advantage of the full
+performance of netmap I/O, the data is copied between netmap slots and
+ODP buffers.
+
+The implementation is based almost entirely on the linux-generic platform
+implementation, only the packet I/O differs to work with netmap enabled
+interfaces.
+
+Also the linux-netmap implementation allows hybrid pktio instances, which
+means that the applications can decide to use either sockets or netmap I/O
+at runtime.
+
+2. Build
+==================================
+
+2.1 Building netmap
+--------------------
+
+Netmap is currently hosted on https://code.google.com/p/netmap/ but
+ODP only works at this point with netmap API version 10 so you will need
+a specific revision of netmap.
+
+    git clone https://code.google.com/p/netmap/
+    cd netmap
+    git reset --hard 1f462ef
+
+Netmap consists of a core kernel module (netmap_lin.ko) and optional modified
+device drivers.
+
+Netmap builds as an out-of-tree kernel module, you need matching kernel sources
+to compile it. General build instructions can be found in the packet README:
+http://code.google.com/p/netmap/source/browse/README
+
+2.1.1 Building netmap on Ubuntu with stock kernel
+
+If you are running Ubuntu/Debian with the stock kernel and you want to compile
+both netmap_lin.ko and modified drivers, these steps will guide you through it.
+
+You will need the linux-headers and linux-source packages for this.
+
+    sudo apt-get install linux-headers
+    sudo apt-get install linux-source
+
+The source archive will be placed in /usr/src/linux-source-<kernel-version>
+You will need to locate it and extract it to a convenient place.
+
+Now compile netmap:
+
+    cd LINUX
+    make SRC=<path_to_kernel_source>
+
+2.1.2 Building netmap for kernel built from sources
+
+    cd LINUX
+    make KSRC=<path_to_kernel_source>
+
+2.2 Building ODP
+----------------
+
+    ./bootstrap
+    ./configure --with-platform=linux-netmap --with-sdk-install-path=<netmap>
+    make
+
+3. Running the example application
+==================================
+
+The example application for netmap-linux is odp_pktio_netmap. The main purpose
+of the example application is to show how to implement a simple packet loopback
+application using ODP. The example also implements a bridge between the Linux
+IP stack and the physical interfaces since netmap disrupts the communication
+between the two. The bridging is achieved by passing packets between the
+"software" ring attached to the host stack and the physical rings of the NIC.
+More information on netmap architecture and software rings can be found in the
+"netmap: a novel framework for fast packet I/O" paper by Luigi Rizzo on his
+research page: http://info.iet.unipi.it/~luigi/research.html
+
+    sudo ./odp_pktio_netmap -i eth0 -m 1
+
+From another machine you can simply run ping and observe the duplicate ICMP 
+replies. Also the host running the example application should still have network
+connectivity due to the bridging performed as explained above.
diff --git a/platform/linux-netmap/include/api/odp_pktio_netmap.h b/platform/linux-netmap/include/api/odp_pktio_netmap.h
new file mode 100644
index 0000000..294a13d
--- /dev/null
+++ b/platform/linux-netmap/include/api/odp_pktio_netmap.h
@@ -0,0 +1,39 @@
+
+/* Copyright (c) 2013, Linaro Limited
+ * All rights reserved.
+ *
+ * SPDX-License-Identifier:     BSD-3-Clause
+ */
+
+/**
+ * @file
+ *
+ * ODP packet input/output netmap
+ */
+
+#ifndef ODP_PKTIO_NETMAP_H
+#define ODP_PKTIO_NETMAP_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <odp_pktio_types.h>
+
+#define ODP_NETMAP_MODE_HW	0 /**< Netmap mode in hardware */
+#define ODP_NETMAP_MODE_SW	1 /**< Netmap mode in software */
+
+/**
+ * Netmap parameters
+ */
+typedef struct {
+	odp_pktio_type_t type;	/**< Packet IO type */
+	int netmap_mode;	/**< Netmap Mode */
+	uint16_t ringid;	/**< Ring identifiers */
+} netmap_params_t;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/platform/linux-netmap/include/api/odp_pktio_types.h b/platform/linux-netmap/include/api/odp_pktio_types.h
new file mode 100644
index 0000000..b7c6341
--- /dev/null
+++ b/platform/linux-netmap/include/api/odp_pktio_types.h
@@ -0,0 +1,51 @@
+/* Copyright (c) 2013, Linaro Limited
+ * All rights reserved.
+ *
+ * SPDX-License-Identifier:     BSD-3-Clause
+ */
+
+/**
+ * @file
+ *
+ * ODP packet input/output types
+ */
+
+#ifndef ODP_PKTIO_TYPES_H
+#define ODP_PKTIO_TYPES_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* We should ensure that future enum values will never overlap, otherwise
+ * applications that want netmap suport might get in trouble if the odp lib
+ * was not built with netmap support and there are more types define below
+ */
+
+/**
+ * Packet IO types
+ */
+typedef enum {
+	ODP_PKTIO_TYPE_SOCKET_BASIC = 0x1,
+	ODP_PKTIO_TYPE_SOCKET_MMSG,
+	ODP_PKTIO_TYPE_SOCKET_MMAP,
+	ODP_PKTIO_TYPE_NETMAP,
+} odp_pktio_type_t;
+
+#include <odp_pktio_socket.h>
+#include <odp_pktio_netmap.h>
+
+/**
+ * Packet IO parameters
+ */
+typedef union odp_pktio_params_t {
+	odp_pktio_type_t type;		/**< Packet IO type */
+	socket_params_t sock_params;	/**< Socket parameter */
+	netmap_params_t nm_params;
+} odp_pktio_params_t;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/platform/linux-netmap/include/odp_packet_io_internal.h b/platform/linux-netmap/include/odp_packet_io_internal.h
new file mode 100644
index 0000000..a11ca31
--- /dev/null
+++ b/platform/linux-netmap/include/odp_packet_io_internal.h
@@ -0,0 +1,45 @@
+/* Copyright (c) 2013, Linaro Limited
+ * All rights reserved.
+ *
+ * SPDX-License-Identifier:     BSD-3-Clause
+ */
+
+
+/**
+ * @file
+ *
+ * ODP packet IO - implementation internal
+ */
+
+#ifndef ODP_PACKET_IO_INTERNAL_H_
+#define ODP_PACKET_IO_INTERNAL_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <odp_spinlock.h>
+#include <odp_packet_socket.h>
+#include <odp_packet_netmap.h>
+
+struct pktio_entry {
+	odp_spinlock_t lock;		/**< entry spinlock */
+	int taken;			/**< is entry taken(1) or free(0) */
+	odp_queue_t inq_default;	/**< default input queue, if set */
+	odp_queue_t outq_default;	/**< default out queue */
+	odp_pktio_params_t params;	/**< pktio parameters */
+	pkt_sock_t pkt_sock;		/**< using socket API for IO */
+	pkt_sock_mmap_t pkt_sock_mmap;	/**< using socket mmap API for IO */
+	pkt_netmap_t pkt_nm;		/**< using netmap API for IO */
+};
+
+typedef union {
+	struct pktio_entry s;
+	uint8_t pad[ODP_CACHE_LINE_SIZE_ROUNDUP(sizeof(struct pktio_entry))];
+} pktio_entry_t;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/platform/linux-netmap/odp_packet_io.c b/platform/linux-netmap/odp_packet_io.c
new file mode 100644
index 0000000..8d6e7d6
--- /dev/null
+++ b/platform/linux-netmap/odp_packet_io.c
@@ -0,0 +1,523 @@
+/* Copyright (c) 2013, Linaro Limited
+ * All rights reserved.
+ *
+ * SPDX-License-Identifier:     BSD-3-Clause
+ */
+
+#include <odp_packet_io.h>
+#include <odp_packet_io_internal.h>
+#include <odp_packet_io_queue.h>
+#include <odp_packet.h>
+#include <odp_packet_internal.h>
+#include <odp_internal.h>
+#include <odp_spinlock.h>
+#include <odp_shared_memory.h>
+#include <odp_packet_socket.h>
+#include <odp_packet_netmap.h>
+#include <odp_hints.h>
+#include <odp_config.h>
+#include <odp_queue_internal.h>
+#include <odp_schedule_internal.h>
+#include <odp_debug.h>
+
+#include <odp_pktio_socket.h>
+
+#include <string.h>
+
+typedef struct {
+	pktio_entry_t entries[ODP_CONFIG_PKTIO_ENTRIES];
+} pktio_table_t;
+
+static pktio_table_t *pktio_tbl;
+
+
+static pktio_entry_t *get_entry(odp_pktio_t id)
+{
+	if (odp_unlikely(id == ODP_PKTIO_INVALID ||
+			 id > ODP_CONFIG_PKTIO_ENTRIES))
+		return NULL;
+
+	return &pktio_tbl->entries[id - 1];
+}
+
+int odp_pktio_init_global(void)
+{
+	char name[ODP_QUEUE_NAME_LEN];
+	pktio_entry_t *pktio_entry;
+	queue_entry_t *queue_entry;
+	odp_queue_t qid;
+	int id;
+	odp_shm_t shm;
+
+	shm = odp_shm_reserve("odp_pktio_entries",
+			      sizeof(pktio_table_t),
+			      sizeof(pktio_entry_t), 0);
+	pktio_tbl = odp_shm_addr(shm);
+
+	if (pktio_tbl == NULL)
+		return -1;
+
+	memset(pktio_tbl, 0, sizeof(pktio_table_t));
+
+	for (id = 1; id <= ODP_CONFIG_PKTIO_ENTRIES; ++id) {
+		pktio_entry = get_entry(id);
+
+		odp_spinlock_init(&pktio_entry->s.lock);
+
+		/* Create a default output queue for each pktio resource */
+		snprintf(name, sizeof(name), "%i-pktio_outq_default", (int)id);
+		name[ODP_QUEUE_NAME_LEN-1] = '\0';
+
+		qid = odp_queue_create(name, ODP_QUEUE_TYPE_PKTOUT, NULL);
+		if (qid == ODP_QUEUE_INVALID)
+			return -1;
+		pktio_entry->s.outq_default = qid;
+
+		queue_entry = queue_to_qentry(qid);
+		queue_entry->s.pktout = id;
+	}
+
+	return 0;
+}
+
+int odp_pktio_init_local(void)
+{
+	return 0;
+}
+
+static int is_free(pktio_entry_t *entry)
+{
+	return (entry->s.taken == 0);
+}
+
+static void set_free(pktio_entry_t *entry)
+{
+	entry->s.taken = 0;
+}
+
+static void set_taken(pktio_entry_t *entry)
+{
+	entry->s.taken = 1;
+}
+
+static void lock_entry(pktio_entry_t *entry)
+{
+	odp_spinlock_lock(&entry->s.lock);
+}
+
+static void unlock_entry(pktio_entry_t *entry)
+{
+	odp_spinlock_unlock(&entry->s.lock);
+}
+
+static void init_pktio_entry(pktio_entry_t *entry, odp_pktio_params_t *params)
+{
+	set_taken(entry);
+	entry->s.inq_default = ODP_QUEUE_INVALID;
+	switch (params->type) {
+	case ODP_PKTIO_TYPE_SOCKET_BASIC:
+	case ODP_PKTIO_TYPE_SOCKET_MMSG:
+	case ODP_PKTIO_TYPE_SOCKET_MMAP:
+		memset(&entry->s.pkt_sock, 0, sizeof(entry->s.pkt_sock));
+		memset(&entry->s.pkt_sock_mmap, 0,
+		      sizeof(entry->s.pkt_sock_mmap));
+		break;
+	case ODP_PKTIO_TYPE_NETMAP:
+		memset(&entry->s.pkt_nm, 0, sizeof(entry->s.pkt_nm));
+		break;
+	default:
+		ODP_ERR("Packet I/O type not supported. Please recompile\n");
+		break;
+	}
+	/* Save pktio parameters, type is the most useful */
+	memcpy(&entry->s.params, params, sizeof(*params));
+}
+
+static odp_pktio_t alloc_lock_pktio_entry(odp_pktio_params_t *params)
+{
+	odp_pktio_t id;
+	pktio_entry_t *entry;
+	int i;
+
+	for (i = 0; i < ODP_CONFIG_PKTIO_ENTRIES; ++i) {
+		entry = &pktio_tbl->entries[i];
+		if (is_free(entry)) {
+			lock_entry(entry);
+			if (is_free(entry)) {
+				init_pktio_entry(entry, params);
+				id = i + 1;
+				return id; /* return with entry locked! */
+			}
+			unlock_entry(entry);
+		}
+	}
+
+	return ODP_PKTIO_INVALID;
+}
+
+static int free_pktio_entry(odp_pktio_t id)
+{
+	pktio_entry_t *entry = get_entry(id);
+
+	if (entry == NULL)
+		return -1;
+
+	set_free(entry);
+
+	return 0;
+}
+
+odp_pktio_t odp_pktio_open(const char *dev, odp_buffer_pool_t pool,
+			   odp_pktio_params_t *params)
+{
+	odp_pktio_t id;
+	pktio_entry_t *pktio_entry;
+	int res;
+
+	if (params == NULL) {
+		ODP_ERR("Invalid pktio params\n");
+		return ODP_PKTIO_INVALID;
+	}
+
+	switch (params->type) {
+	case ODP_PKTIO_TYPE_SOCKET_BASIC:
+	case ODP_PKTIO_TYPE_SOCKET_MMSG:
+	case ODP_PKTIO_TYPE_SOCKET_MMAP:
+		ODP_DBG("Allocating socket pktio\n");
+		break;
+	case ODP_PKTIO_TYPE_NETMAP:
+		ODP_DBG("Allocating netmap pktio\n");
+		break;
+	default:
+		ODP_ERR("Invalid pktio type: %02x\n", params->type);
+		return ODP_PKTIO_INVALID;
+	}
+
+	id = alloc_lock_pktio_entry(params);
+	if (id == ODP_PKTIO_INVALID) {
+		ODP_ERR("No resources available.\n");
+		return ODP_PKTIO_INVALID;
+	}
+	/* if successful, alloc_pktio_entry() returns with the entry locked */
+
+	pktio_entry = get_entry(id);
+
+	switch (params->type) {
+	case ODP_PKTIO_TYPE_SOCKET_BASIC:
+	case ODP_PKTIO_TYPE_SOCKET_MMSG:
+		res = setup_pkt_sock(&pktio_entry->s.pkt_sock, dev, pool);
+		if (res == -1) {
+			close_pkt_sock(&pktio_entry->s.pkt_sock);
+			free_pktio_entry(id);
+			id = ODP_PKTIO_INVALID;
+		}
+		break;
+	case ODP_PKTIO_TYPE_SOCKET_MMAP:
+		res = setup_pkt_sock_mmap(&pktio_entry->s.pkt_sock_mmap, dev,
+				pool, params->sock_params.fanout);
+		if (res == -1) {
+			close_pkt_sock_mmap(&pktio_entry->s.pkt_sock_mmap);
+			free_pktio_entry(id);
+			id = ODP_PKTIO_INVALID;
+		}
+		break;
+	case ODP_PKTIO_TYPE_NETMAP:
+
+		res = setup_pkt_netmap(&pktio_entry->s.pkt_nm, dev,
+				pool, &params->nm_params);
+		if (res == -1) {
+			close_pkt_netmap(&pktio_entry->s.pkt_nm);
+			free_pktio_entry(id);
+			id = ODP_PKTIO_INVALID;
+		}
+		break;
+	default:
+		free_pktio_entry(id);
+		id = ODP_PKTIO_INVALID;
+		ODP_ERR("This type of I/O is not supported. Please recompile.\n");
+		break;
+	}
+
+	unlock_entry(pktio_entry);
+	return id;
+}
+
+int odp_pktio_close(odp_pktio_t id)
+{
+	pktio_entry_t *entry;
+	int res = -1;
+
+	entry = get_entry(id);
+	if (entry == NULL)
+		return -1;
+
+	lock_entry(entry);
+	if (!is_free(entry)) {
+		switch (entry->s.params.type) {
+		case ODP_PKTIO_TYPE_SOCKET_BASIC:
+		case ODP_PKTIO_TYPE_SOCKET_MMSG:
+			res  = close_pkt_sock(&entry->s.pkt_sock);
+			break;
+		case ODP_PKTIO_TYPE_SOCKET_MMAP:
+			res  = close_pkt_sock_mmap(&entry->s.pkt_sock_mmap);
+			break;
+		case ODP_PKTIO_TYPE_NETMAP:
+			res  = close_pkt_netmap(&entry->s.pkt_nm);
+			break;
+		default:
+			break;
+		res |= free_pktio_entry(id);
+		}
+	}
+	unlock_entry(entry);
+
+	if (res != 0)
+		return -1;
+
+	return 0;
+}
+
+void odp_pktio_set_input(odp_packet_t pkt, odp_pktio_t pktio)
+{
+	odp_packet_hdr(pkt)->input = pktio;
+}
+
+odp_pktio_t odp_pktio_get_input(odp_packet_t pkt)
+{
+	return odp_packet_hdr(pkt)->input;
+}
+
+int odp_pktio_recv(odp_pktio_t id, odp_packet_t pkt_table[], unsigned len)
+{
+	pktio_entry_t *pktio_entry = get_entry(id);
+	int pkts;
+	int i;
+
+	if (pktio_entry == NULL)
+		return -1;
+
+	lock_entry(pktio_entry);
+	switch (pktio_entry->s.params.type) {
+	case ODP_PKTIO_TYPE_SOCKET_BASIC:
+		pkts = recv_pkt_sock_basic(&pktio_entry->s.pkt_sock,
+				pkt_table, len);
+		break;
+	case ODP_PKTIO_TYPE_SOCKET_MMSG:
+		pkts = recv_pkt_sock_mmsg(&pktio_entry->s.pkt_sock,
+				pkt_table, len);
+		break;
+	case ODP_PKTIO_TYPE_SOCKET_MMAP:
+		pkts = recv_pkt_sock_mmap(&pktio_entry->s.pkt_sock_mmap,
+				pkt_table, len);
+		break;
+	case ODP_PKTIO_TYPE_NETMAP:
+		pkts = recv_pkt_netmap(&pktio_entry->s.pkt_nm, pkt_table, len);
+		break;
+	default:
+		pkts = -1;
+		break;
+	}
+
+	unlock_entry(pktio_entry);
+	if (pkts < 0)
+		return pkts;
+
+	for (i = 0; i < pkts; ++i)
+		odp_pktio_set_input(pkt_table[i], id);
+
+	return pkts;
+}
+
+int odp_pktio_send(odp_pktio_t id, odp_packet_t pkt_table[], unsigned len)
+{
+	pktio_entry_t *pktio_entry = get_entry(id);
+	int pkts;
+
+	if (pktio_entry == NULL)
+		return -1;
+
+	lock_entry(pktio_entry);
+	switch (pktio_entry->s.params.type) {
+	case ODP_PKTIO_TYPE_SOCKET_BASIC:
+		pkts = send_pkt_sock_basic(&pktio_entry->s.pkt_sock,
+				pkt_table, len);
+		break;
+	case ODP_PKTIO_TYPE_SOCKET_MMSG:
+		pkts = send_pkt_sock_mmsg(&pktio_entry->s.pkt_sock,
+				pkt_table, len);
+		break;
+	case ODP_PKTIO_TYPE_SOCKET_MMAP:
+		pkts = send_pkt_sock_mmap(&pktio_entry->s.pkt_sock_mmap,
+				pkt_table, len);
+		break;
+	case ODP_PKTIO_TYPE_NETMAP:
+		pkts = send_pkt_netmap(&pktio_entry->s.pkt_nm,
+				pkt_table, len);
+		break;
+	default:
+		pkts = -1;
+	}
+	unlock_entry(pktio_entry);
+
+	return pkts;
+}
+
+int odp_pktio_inq_setdef(odp_pktio_t id, odp_queue_t queue)
+{
+	pktio_entry_t *pktio_entry = get_entry(id);
+	queue_entry_t *qentry = queue_to_qentry(queue);
+
+	if (pktio_entry == NULL || qentry == NULL)
+		return -1;
+
+	if (qentry->s.type != ODP_QUEUE_TYPE_PKTIN)
+		return -1;
+
+	lock_entry(pktio_entry);
+	pktio_entry->s.inq_default = queue;
+	unlock_entry(pktio_entry);
+
+	queue_lock(qentry);
+	qentry->s.pktin = id;
+	qentry->s.status = QUEUE_STATUS_SCHED;
+	queue_unlock(qentry);
+
+	odp_schedule_queue(queue, qentry->s.param.sched.prio);
+
+	return 0;
+}
+
+int odp_pktio_inq_remdef(odp_pktio_t id)
+{
+	return odp_pktio_inq_setdef(id, ODP_QUEUE_INVALID);
+}
+
+odp_queue_t odp_pktio_inq_getdef(odp_pktio_t id)
+{
+	pktio_entry_t *pktio_entry = get_entry(id);
+
+	if (pktio_entry == NULL)
+		return ODP_QUEUE_INVALID;
+
+	return pktio_entry->s.inq_default;
+}
+
+odp_queue_t odp_pktio_outq_getdef(odp_pktio_t id)
+{
+	pktio_entry_t *pktio_entry = get_entry(id);
+
+	if (pktio_entry == NULL)
+		return ODP_QUEUE_INVALID;
+
+	return pktio_entry->s.outq_default;
+}
+
+int pktout_enqueue(queue_entry_t *qentry, odp_buffer_hdr_t *buf_hdr)
+{
+	odp_packet_t pkt = odp_packet_from_buffer(buf_hdr->handle.handle);
+	int len = 1;
+	int nbr;
+
+	nbr = odp_pktio_send(qentry->s.pktout, &pkt, len);
+	return (nbr == len ? 0 : -1);
+}
+
+odp_buffer_hdr_t *pktout_dequeue(queue_entry_t *qentry)
+{
+	(void)qentry;
+	return NULL;
+}
+
+int pktout_enq_multi(queue_entry_t *qentry, odp_buffer_hdr_t *buf_hdr[],
+		     int num)
+{
+	odp_packet_t pkt_tbl[QUEUE_MULTI_MAX];
+	int nbr;
+	int i;
+
+	for (i = 0; i < num; ++i)
+		pkt_tbl[i] = odp_packet_from_buffer(buf_hdr[i]->handle.handle);
+
+	nbr = odp_pktio_send(qentry->s.pktout, pkt_tbl, num);
+	return (nbr == num ? 0 : -1);
+}
+
+int pktout_deq_multi(queue_entry_t *qentry, odp_buffer_hdr_t *buf_hdr[],
+		     int num)
+{
+	(void)qentry;
+	(void)buf_hdr;
+	(void)num;
+
+	return 0;
+}
+
+int pktin_enqueue(queue_entry_t *qentry, odp_buffer_hdr_t *buf_hdr)
+{
+	/* Use default action */
+	return queue_enq(qentry, buf_hdr);
+}
+
+odp_buffer_hdr_t *pktin_dequeue(queue_entry_t *qentry)
+{
+	odp_buffer_hdr_t *buf_hdr;
+
+	buf_hdr = queue_deq(qentry);
+
+	if (buf_hdr == NULL) {
+		odp_packet_t pkt;
+		odp_buffer_t buf;
+		odp_packet_t pkt_tbl[QUEUE_MULTI_MAX];
+		odp_buffer_hdr_t *tmp_hdr_tbl[QUEUE_MULTI_MAX];
+		int pkts, i, j;
+
+		pkts = odp_pktio_recv(qentry->s.pktin, pkt_tbl,
+				      QUEUE_MULTI_MAX);
+
+		if (pkts > 0) {
+			pkt = pkt_tbl[0];
+			buf = odp_buffer_from_packet(pkt);
+			buf_hdr = odp_buf_to_hdr(buf);
+
+			for (i = 1, j = 0; i < pkts; ++i) {
+				buf = odp_buffer_from_packet(pkt_tbl[i]);
+				tmp_hdr_tbl[j++] = odp_buf_to_hdr(buf);
+			}
+			queue_enq_multi(qentry, tmp_hdr_tbl, j);
+		}
+	}
+
+	return buf_hdr;
+}
+
+int pktin_enq_multi(queue_entry_t *qentry, odp_buffer_hdr_t *buf_hdr[], int num)
+{
+	/* Use default action */
+	return queue_enq_multi(qentry, buf_hdr, num);
+}
+
+int pktin_deq_multi(queue_entry_t *qentry, odp_buffer_hdr_t *buf_hdr[], int num)
+{
+	int nbr;
+
+	nbr = queue_deq_multi(qentry, buf_hdr, num);
+
+	if (nbr < num) {
+		odp_packet_t pkt_tbl[QUEUE_MULTI_MAX];
+		odp_buffer_hdr_t *tmp_hdr_tbl[QUEUE_MULTI_MAX];
+		odp_buffer_t buf;
+		int pkts, i;
+
+		pkts = odp_pktio_recv(qentry->s.pktin, pkt_tbl,
+				      QUEUE_MULTI_MAX);
+		if (pkts > 0) {
+			for (i = 0; i < pkts; ++i) {
+				buf = odp_buffer_from_packet(pkt_tbl[i]);
+				tmp_hdr_tbl[i] = odp_buf_to_hdr(buf);
+			}
+			queue_enq_multi(qentry, tmp_hdr_tbl, pkts);
+		}
+	}
+
+	return nbr;
+}
diff --git a/platform/linux-netmap/odp_packet_netmap.c b/platform/linux-netmap/odp_packet_netmap.c
new file mode 100644
index 0000000..524492d
--- /dev/null
+++ b/platform/linux-netmap/odp_packet_netmap.c
@@ -0,0 +1,453 @@
+/* Copyright (c) 2013, Linaro Limited
+ * Copyright (c) 2013, Nokia Solutions and Networks
+ * All rights reserved.
+ *
+ * SPDX-License-Identifier:     BSD-3-Clause
+ */
+
+/*
+ * NETMAP I/O code inspired by the pkt-gen example application in netmap by:
+ * Copyright (C) 2011-2014 Matteo Landi, Luigi Rizzo. All rights reserved.
+ * Copyright (C) 2013-2014 Universita` di Pisa. All rights reserved.
+ */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <poll.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <linux/ethtool.h>
+#include <linux/sockios.h>
+
+#include <odp_packet_internal.h>
+#include <odp_hints.h>
+#include <odp_thread.h>
+
+#include <odph_eth.h>
+#include <odph_ip.h>
+#include <odph_packet.h>
+
+#define NETMAP_WITH_LIBS
+#include <odp_packet_netmap.h>
+
+/** Eth buffer start offset from u32-aligned address to make sure the following
+ * header (e.g. IP) starts at a 32-bit aligned address.
+ */
+#define ETHBUF_OFFSET (ODP_ALIGN_ROUNDUP(ODPH_ETHHDR_LEN, sizeof(uint32_t)) \
+				- ODPH_ETHHDR_LEN)
+
+/** Round up buffer address to get a properly aliged eth buffer, i.e. aligned
+ * so that the next header always starts at a 32bit aligned address.
+ */
+#define ETHBUF_ALIGN(buf_ptr) ((uint8_t *)ODP_ALIGN_ROUNDUP_PTR((buf_ptr), \
+				sizeof(uint32_t)) + ETHBUF_OFFSET)
+
+#define ETH_PROMISC  1 /* TODO: maybe this should be exported to the user */
+#define WAITLINK_TMO 2
+#define POLL_TMO     50
+
+static int nm_do_ioctl(pkt_netmap_t * const pkt_nm, unsigned long cmd,
+		       int subcmd)
+{
+	struct ethtool_value eval;
+	struct ifreq ifr;
+	int error;
+	int fd;
+
+	fd = socket(AF_INET, SOCK_DGRAM, 0);
+	if (fd < 0) {
+		ODP_ERR("Error: cannot get device control socket\n");
+		return -1;
+	}
+
+	memset(&ifr, 0, sizeof(ifr));
+	strncpy(ifr.ifr_name, pkt_nm->ifname, sizeof(ifr.ifr_name));
+
+	switch (cmd) {
+	case SIOCSIFFLAGS:
+		ifr.ifr_flags = pkt_nm->if_flags & 0xffff;
+		break;
+	case SIOCETHTOOL:
+		eval.cmd = subcmd;
+		eval.data = 0;
+		ifr.ifr_data = (caddr_t)&eval;
+		break;
+	default:
+		break;
+	}
+	error = ioctl(fd, cmd, &ifr);
+	if (error)
+		goto done;
+
+	switch (cmd) {
+	case SIOCGIFFLAGS:
+		pkt_nm->if_flags = (ifr.ifr_flags << 16) |
+			(0xffff & ifr.ifr_flags);
+		ODP_DBG("flags are 0x%x\n", pkt_nm->if_flags);
+		break;
+	default:
+		break;
+	}
+done:
+	close(fd);
+	if (error)
+		ODP_ERR("ioctl err %d %lu: %s\n", error, cmd, strerror(errno));
+
+	return error;
+}
+
+int setup_pkt_netmap(pkt_netmap_t * const pkt_nm, const char *netdev,
+		     odp_buffer_pool_t pool, netmap_params_t *nm_params)
+{
+	char qname[ODP_QUEUE_NAME_LEN];
+	char ifname[32];
+	odp_packet_t pkt;
+	odp_buffer_t token;
+	uint8_t *pkt_buf;
+	uint16_t ringid;
+	uint8_t *l2_hdr;
+	int ret;
+
+	if (pool == ODP_BUFFER_POOL_INVALID)
+		return -1;
+	pkt_nm->pool = pool;
+
+	pkt = odph_packet_alloc(pool);
+	if (!odph_packet_is_valid(pkt))
+		return -1;
+
+	pkt_buf = odp_packet_buf_addr(pkt);
+	l2_hdr = ETHBUF_ALIGN(pkt_buf);
+	/* Store eth buffer offset for buffers from this pool */
+	pkt_nm->frame_offset = (uintptr_t)l2_hdr - (uintptr_t)pkt_buf;
+	/* pkt buffer size */
+	pkt_nm->buf_size = odph_packet_buf_size(pkt);
+	/* max frame len taking into account the l2-offset */
+	pkt_nm->max_frame_len = pkt_nm->buf_size - pkt_nm->frame_offset;
+	/* save netmap_mode for later use */
+	pkt_nm->netmap_mode = nm_params->netmap_mode;
+
+	odph_packet_free(pkt);
+
+	if (nm_params->netmap_mode == ODP_NETMAP_MODE_SW)
+		ringid = NETMAP_SW_RING;
+	else
+		ringid = nm_params->ringid;
+
+	strncpy(pkt_nm->ifname, netdev, sizeof(pkt_nm->ifname));
+	snprintf(ifname, sizeof(ifname), "netmap:%s", netdev);
+	pkt_nm->nm_desc = nm_open(ifname, NULL, ringid, 0);
+
+	if (pkt_nm->nm_desc == NULL) {
+		ODP_ERR("Error opening nm interface: %s\n", strerror(errno));
+		return -1;
+	}
+
+	ODP_DBG("thread %d mode %s mmap addr %p\n",
+		odp_thread_id(),
+		nm_params->netmap_mode == ODP_NETMAP_MODE_SW ? "SW" : "HW",
+		pkt_nm->nm_desc->mem);
+
+	if (nm_params->netmap_mode == ODP_NETMAP_MODE_SW) {
+		pkt_nm->begin  = pkt_nm->nm_desc->req.nr_rx_rings;
+		pkt_nm->end    = pkt_nm->begin + 1;
+		pkt_nm->rxring = NETMAP_RXRING(pkt_nm->nm_desc->nifp,
+				    pkt_nm->nm_desc->req.nr_rx_rings);
+		pkt_nm->txring = NETMAP_TXRING(pkt_nm->nm_desc->nifp,
+				     pkt_nm->nm_desc->req.nr_tx_rings);
+	} else if (nm_params->ringid & NETMAP_HW_RING) {
+		pkt_nm->begin  = nm_params->ringid & NETMAP_RING_MASK;
+		pkt_nm->end    = pkt_nm->begin + 1;
+		pkt_nm->rxring = NETMAP_RXRING(pkt_nm->nm_desc->nifp,
+				     pkt_nm->begin);
+		pkt_nm->txring = NETMAP_TXRING(pkt_nm->nm_desc->nifp,
+				     pkt_nm->begin);
+	} else {
+		pkt_nm->begin  = 0;
+		pkt_nm->end    = pkt_nm->nm_desc->req.nr_rx_rings;
+		pkt_nm->rxring = NETMAP_RXRING(pkt_nm->nm_desc->nifp, 0);
+		pkt_nm->txring = NETMAP_TXRING(pkt_nm->nm_desc->nifp, 0);
+	}
+
+	/* Set TX checksumming if hardware rings */
+	if (nm_params->netmap_mode == ODP_NETMAP_MODE_HW) {
+		ret = nm_do_ioctl(pkt_nm, SIOCGIFFLAGS, 0);
+		if (ret)
+			return ret;
+		if ((pkt_nm->if_flags & IFF_UP) == 0) {
+			ODP_DBG("%s is down, bringing up...\n", pkt_nm->ifname);
+			pkt_nm->if_flags |= IFF_UP;
+		}
+		if (ETH_PROMISC) {
+			pkt_nm->if_flags |= IFF_PROMISC;
+			nm_do_ioctl(pkt_nm, SIOCSIFFLAGS, 0);
+		}
+		ret = nm_do_ioctl(pkt_nm, SIOCETHTOOL, ETHTOOL_SGSO);
+		if (ret)
+			ODP_DBG("ETHTOOL_SGSO not supported\n");
+
+		ret = nm_do_ioctl(pkt_nm, SIOCETHTOOL, ETHTOOL_STSO);
+		if (ret)
+			ODP_DBG("ETHTOOL_STSO not supported\n");
+		/* TODO: This seems to cause the app to not receive frames
+		 * first time it is launched after netmap driver is inserted.
+		 * Should be investigated further.
+		 */
+		/*
+		nm_do_ioctl(pkt_nm, SIOCETHTOOL, ETHTOOL_SRXCSUM);
+		*/
+		ret = nm_do_ioctl(pkt_nm, SIOCETHTOOL, ETHTOOL_STXCSUM);
+		if (ret)
+			ODP_DBG("ETHTOOL_STXCSUM not supported\n");
+	}
+
+	/* Set up the TX access queue */
+	snprintf(qname, sizeof(qname), "%s:%s-pktio_tx_access", netdev,
+		 nm_params->netmap_mode == ODP_NETMAP_MODE_SW ? "SW" : "HW");
+	pkt_nm->tx_access = odp_queue_create(qname, ODP_QUEUE_TYPE_POLL, NULL);
+	if (pkt_nm->tx_access == ODP_QUEUE_INVALID) {
+		ODP_ERR("Error: pktio queue creation failed\n");
+		return -1;
+	}
+	token = odp_buffer_alloc(pool);
+	if (!odp_buffer_is_valid(token)) {
+		ODP_ERR("Error: token creation failed\n");
+		return -1;
+	}
+
+	odp_queue_enq(pkt_nm->tx_access, token);
+
+	ODP_DBG("Wait for link to come up\n");
+	sleep(WAITLINK_TMO);
+	ODP_DBG("Done\n");
+
+	return 0;
+}
+
+int close_pkt_netmap(pkt_netmap_t * const pkt_nm)
+{
+	if (pkt_nm->nm_desc != NULL) {
+		nm_close(pkt_nm->nm_desc);
+		pkt_nm->nm_desc = NULL;
+	}
+
+	return 0;
+}
+
+int recv_pkt_netmap(pkt_netmap_t * const pkt_nm, odp_packet_t pkt_table[],
+		    unsigned len)
+{
+	struct netmap_ring *rxring = pkt_nm->rxring;
+	int fd;
+	unsigned nb_rx = 0;
+	uint32_t limit, rx;
+	uint32_t ringid = pkt_nm->begin;
+	odp_packet_t pkt = ODP_PACKET_INVALID;
+#ifdef NETMAP_BLOCKING_IO
+	struct pollfd fds[1];
+	int ret;
+#endif
+
+	fd = pkt_nm->nm_desc->fd;
+#ifdef NETMAP_BLOCKING_IO
+	fds[0].fd = fd;
+	fds[0].events = POLLIN;
+#endif
+
+	while (nb_rx < len) {
+#ifdef NETMAP_BLOCKING_IO
+		ret = poll(&fds[0], 1, POLL_TMO);
+		if (ret <= 0 || (fds[0].revents & POLLERR))
+			break;
+#else
+		ioctl(fd, NIOCRXSYNC, NULL);
+#endif
+
+		/* Find first ring not empty */
+		while (nm_ring_empty(rxring)) {
+			ringid++;
+
+			/* Return to scheduler if no more data to meet the
+			   requested amount (len) */
+			if (ringid == pkt_nm->end) {
+				ODP_DBG("No more data on the wire\n");
+				break;
+			}
+
+			rxring = NETMAP_RXRING(pkt_nm->nm_desc->nifp, ringid);
+		}
+
+		limit = len - nb_rx;
+		if (nm_ring_space(rxring) < limit)
+			limit = nm_ring_space(rxring);
+
+		ODP_DBG("receiving %d frames out of %u\n", limit, len);
+
+		for (rx = 0; rx < limit; rx++) {
+			struct netmap_slot *rslot;
+			char *p;
+			uint16_t frame_len;
+			uint8_t *pkt_buf;
+			uint8_t *l2_hdr;
+			uint32_t cur;
+
+			if (odp_likely(pkt == ODP_PACKET_INVALID)) {
+				pkt = odph_packet_alloc(pkt_nm->pool);
+				if (odp_unlikely(pkt == ODP_PACKET_INVALID))
+					break;
+			}
+
+			cur = rxring->cur;
+			rslot = &rxring->slot[cur];
+			p = NETMAP_BUF(rxring, rslot->buf_idx);
+			frame_len = rslot->len;
+
+			rxring->head = nm_ring_next(rxring, cur);
+			rxring->cur = rxring->head;
+
+			pkt_buf = odp_packet_buf_addr(pkt);
+			l2_hdr = pkt_buf + pkt_nm->frame_offset;
+
+			if (frame_len > pkt_nm->max_frame_len) {
+				ODP_ERR("RX: frame too big %u %lu!\n",
+					frame_len, pkt_nm->max_frame_len);
+				/* drop the frame, reuse pkt next interation */
+				continue;
+			}
+			if (odp_unlikely(frame_len < ODPH_ETH_LEN_MIN)) {
+				if (odp_unlikely(pkt_nm->netmap_mode !=
+						 ODP_NETMAP_MODE_SW)) {
+					ODP_ERR("RX: Frame truncated: %u\n",
+						(unsigned)frame_len);
+					continue;
+				}
+				memset(l2_hdr + frame_len, 0,
+				       ODPH_ETH_LEN_MIN - frame_len);
+				frame_len = ODPH_ETH_LEN_MIN;
+			}
+
+			/* For now copy the data in the mbuf,
+			   worry about zero-copy later */
+			memcpy(l2_hdr, p, frame_len);
+
+			/* Initialize, parse and set packet header data */
+			odp_packet_init(pkt);
+			odp_packet_parse(pkt, frame_len, pkt_nm->frame_offset);
+
+			pkt_table[nb_rx] = pkt;
+			pkt = ODP_PACKET_INVALID;
+			nb_rx++;
+		}
+
+		if (odp_unlikely(pkt == ODP_PACKET_INVALID))
+			break;
+	}
+
+	if (odp_unlikely(pkt != ODP_PACKET_INVALID))
+		odp_buffer_free((odp_buffer_t) pkt);
+
+	if (nb_rx)
+		ODP_DBG("<=== rcvd %03u frames from netmap adapter\n", nb_rx);
+
+	return nb_rx;
+}
+
+int send_pkt_netmap(pkt_netmap_t * const pkt_nm, odp_packet_t pkt_table[],
+		    unsigned len)
+{
+	struct netmap_ring *txring = pkt_nm->txring;
+	int                 fd;
+	unsigned            nb_tx = 0;
+	uint32_t            limit, tx;
+	uint32_t            ringid = pkt_nm->begin;
+	odp_packet_t        pkt;
+	odp_buffer_t        token;
+
+#ifdef NETMAP_BLOCKING_IO
+	struct pollfd fds[2];
+	int ret;
+#endif
+
+	fd = pkt_nm->nm_desc->fd;
+#ifdef NETMAP_BLOCKING_IO
+	fds[0].fd = fd;
+	fds[0].events = POLLOUT;
+#endif
+
+	token = odp_queue_deq(pkt_nm->tx_access);
+
+	while (nb_tx < len) {
+#ifdef NETMAP_BLOCKING_IO
+		ret = poll(&fds[0], 1, POLL_TMO);
+		if (ret <= 0 || (fds[0].revents & POLLERR))
+			break;
+#else
+		ioctl(fd, NIOCTXSYNC, NULL);
+#endif
+
+		/* Find first ring not empty */
+		while (nm_ring_empty(txring)) {
+			ringid++;
+
+			/* Return to scheduler if no more space to meet the
+			   requested amount (len) */
+			if (ringid == pkt_nm->end) {
+				ODP_DBG("No more space in TX rings\n");
+				break;
+			}
+
+			txring = NETMAP_TXRING(pkt_nm->nm_desc->nifp, ringid);
+		}
+
+		limit = len - nb_tx;
+		if (nm_ring_space(txring) < limit)
+			limit = nm_ring_space(txring);
+
+		ODP_DBG("Sending %d packets out of %d to netmap %p %u\n",
+			limit, len, txring, txring->cur);
+
+		for (tx = 0; tx < limit; tx++) {
+			struct netmap_slot *tslot;
+			size_t frame_len;
+			uint32_t cur;
+			uint8_t *frame;
+			void *txbuf;
+
+			cur = txring->cur;
+			tslot = &txring->slot[cur];
+			txbuf = NETMAP_BUF(txring, tslot->buf_idx);
+
+			pkt = pkt_table[nb_tx];
+			frame = odp_packet_start(pkt);
+			frame_len = odp_packet_get_len(pkt);
+
+			memcpy(txbuf, frame, frame_len);
+			tslot->len = frame_len;
+			txring->head = nm_ring_next(txring, cur);
+			txring->cur = txring->head;
+			nb_tx++;
+		}
+	}
+
+	odp_queue_enq(pkt_nm->tx_access, token);
+
+#ifndef NETMAP_BLOCKING_IO
+	ioctl(fd, NIOCTXSYNC, NULL);
+#endif
+
+	if (nb_tx)
+		ODP_DBG("===> sent %03u frames to netmap adapter\n", nb_tx);
+
+	for (tx = 0; tx < len; tx++)
+		odph_packet_free(pkt_table[tx]);
+
+	return nb_tx;
+}
diff --git a/platform/linux-netmap/odp_packet_netmap.h b/platform/linux-netmap/odp_packet_netmap.h
new file mode 100644
index 0000000..1ab50d0
--- /dev/null
+++ b/platform/linux-netmap/odp_packet_netmap.h
@@ -0,0 +1,67 @@
+/* Copyright (c) 2013, Linaro Limited
+ * All rights reserved.
+ *
+ * SPDX-License-Identifier:     BSD-3-Clause
+ */
+
+#ifndef ODP_PACKET_NETMAP_H
+#define ODP_PACKET_NETMAP_H
+
+#include <stdint.h>
+
+#include <net/if.h>
+#include <net/netmap.h>
+#include <net/netmap_user.h>
+
+#include <odp_align.h>
+#include <odp_debug.h>
+#include <odp_buffer_pool.h>
+#include <odp_packet.h>
+
+#include <odp_pktio_netmap.h>
+
+#define ODP_NETMAP_MODE_HW	0
+#define ODP_NETMAP_MODE_SW	1
+
+#define NETMAP_BLOCKING_IO
+
+/** Packet socket using netmap mmaped rings for both Rx and Tx */
+typedef struct {
+	odp_buffer_pool_t pool;
+	size_t max_frame_len; /**< max frame len = buf_size - sizeof(pkt_hdr) */
+	size_t frame_offset; /**< frame start offset from start of pkt buf */
+	size_t buf_size; /**< size of buffer payload in 'pool' */
+	int netmap_mode;
+	struct nm_desc_t *nm_desc;
+	uint32_t begin;
+	uint32_t end;
+	struct netmap_ring *rxring;
+	struct netmap_ring *txring;
+	odp_queue_t tx_access; /* Used for exclusive access to send packets */
+	uint32_t if_flags;
+	char ifname[32];
+} pkt_netmap_t;
+
+/**
+ * Configure an interface to work in netmap mode
+ */
+int setup_pkt_netmap(pkt_netmap_t * const pkt_nm, const char *netdev,
+		     odp_buffer_pool_t pool, netmap_params_t *nm_params);
+
+/**
+ * Switch interface from netmap mode to normal mode
+ */
+int close_pkt_netmap(pkt_netmap_t * const pkt_nm);
+
+/**
+ * Receive packets using netmap
+ */
+int recv_pkt_netmap(pkt_netmap_t * const pkt_nm, odp_packet_t pkt_table[],
+		    unsigned len);
+
+/**
+ * Send packets using netmap
+ */
+int send_pkt_netmap(pkt_netmap_t * const pkt_nm, odp_packet_t pkt_table[],
+		    unsigned len);
+#endif
