diff --git a/platform/linux-generic/Makefile.am b/platform/linux-generic/Makefile.am
index 4c79730..8d14477 100644
--- a/platform/linux-generic/Makefile.am
+++ b/platform/linux-generic/Makefile.am
@@ -173,3 +173,7 @@ EXTRA_DIST = \
 	     arch/linux/odp_time_cycles.c \
 	     arch/mips64/odp_time_cycles.c \
 	     arch/x86/odp_time_cycles.c
+
+if HAVE_PCAP
+__LIB__libodp_la_SOURCES += pktio/pcap.c
+endif
diff --git a/platform/linux-generic/include/odp_packet_io_internal.h b/platform/linux-generic/include/odp_packet_io_internal.h
index bdffc15..7faa4f0 100644
--- a/platform/linux-generic/include/odp_packet_io_internal.h
+++ b/platform/linux-generic/include/odp_packet_io_internal.h
@@ -38,6 +38,21 @@ typedef struct {
 	odp_bool_t promisc;		/**< promiscuous mode state */
 } pkt_loop_t;
 
+#ifdef HAVE_PCAP
+typedef struct {
+	char *fname_rx;		/**< name of pcap file for rx */
+	char *fname_tx;		/**< name of pcap file for tx */
+	void *rx;		/**< rx pcap handle */
+	void *tx;		/**< tx pcap handle */
+	void *tx_dump;		/**< tx pcap dumper handle */
+	odp_pool_t pool;	/**< rx pool */
+	unsigned char *buf;	/**< per-pktio temp buffer */
+	odp_bool_t promisc;	/**< promiscuous mode requested state */
+	int loops;		/**< number of times to loop rx pcap */
+	int loop_cnt;		/**< number of loops completed */
+} pkt_pcap_t;
+#endif
+
 struct pktio_entry {
 	const struct pktio_if_ops *ops; /**< Implementation specific methods */
 	odp_spinlock_t lock;		/**< entry spinlock */
@@ -51,6 +66,9 @@ struct pktio_entry {
 		pkt_sock_t pkt_sock;		/**< using socket API for IO */
 		pkt_sock_mmap_t pkt_sock_mmap;	/**< using socket mmap
 						 *   API for IO */
+#ifdef HAVE_PCAP
+		pkt_pcap_t pkt_pcap;		/**< Using pcap for IO */
+#endif
 	};
 	enum {
 		STATE_START = 0,
@@ -116,6 +134,9 @@ int pktin_poll(pktio_entry_t *entry);
 extern const pktio_if_ops_t sock_mmsg_pktio_ops;
 extern const pktio_if_ops_t sock_mmap_pktio_ops;
 extern const pktio_if_ops_t loopback_pktio_ops;
+#ifdef HAVE_PCAP
+extern const pktio_if_ops_t pcap_pktio_ops;
+#endif
 extern const pktio_if_ops_t * const pktio_if_ops[];
 
 #ifdef __cplusplus
diff --git a/platform/linux-generic/m4/configure.m4 b/platform/linux-generic/m4/configure.m4
index 9658274..4e4fa00 100644
--- a/platform/linux-generic/m4/configure.m4
+++ b/platform/linux-generic/m4/configure.m4
@@ -22,3 +22,19 @@ m4_include([platform/linux-generic/m4/odp_openssl.m4])
 AC_CONFIG_FILES([platform/linux-generic/Makefile
 		 platform/linux-generic/test/Makefile
 		 platform/linux-generic/test/pktio/Makefile])
+
+#########################################################################
+# Check for libpcap availability
+#########################################################################
+have_pcap=no
+AC_CHECK_HEADER(pcap/pcap.h,
+    [AC_CHECK_HEADER(pcap/bpf.h,
+        [AC_CHECK_LIB(pcap, pcap_open_offline, have_pcap=yes, [])],
+    [])],
+[])
+
+AM_CONDITIONAL([HAVE_PCAP], [test $have_pcap = yes])
+if test $have_pcap == yes; then
+    AM_CFLAGS="$AM_CFLAGS -DHAVE_PCAP"
+    LIBS="$LIBS -lpcap"
+fi
diff --git a/platform/linux-generic/pktio/io_ops.c b/platform/linux-generic/pktio/io_ops.c
index 1d47e74..bd53a5c 100644
--- a/platform/linux-generic/pktio/io_ops.c
+++ b/platform/linux-generic/pktio/io_ops.c
@@ -12,6 +12,9 @@
  * Array must be NULL terminated */
 const pktio_if_ops_t * const pktio_if_ops[]  = {
 	&loopback_pktio_ops,
+#ifdef HAVE_PCAP
+	&pcap_pktio_ops,
+#endif
 	&sock_mmap_pktio_ops,
 	&sock_mmsg_pktio_ops,
 	NULL
diff --git a/platform/linux-generic/pktio/pcap.c b/platform/linux-generic/pktio/pcap.c
new file mode 100644
index 0000000..d4173f9
--- /dev/null
+++ b/platform/linux-generic/pktio/pcap.c
@@ -0,0 +1,334 @@
+/* Copyright (c) 2015, Linaro Limited
+ * All rights reserved.
+ *
+ * SPDX-License-Identifier:     BSD-3-Clause
+ */
+
+/**
+ * @file
+ *
+ * PCAP pktio type
+ *
+ * This file provides a pktio interface that allows for reading from
+ * and writing to pcap capture files. It is intended to be used as
+ * simple way of injecting test packets into an application for the
+ * purpose of functional testing.
+ *
+ * To use this interface the name passed to odp_pktio_open() must begin
+ * with "pcap:" and be in the format;
+ *
+ * pcap:in=test.pcap:out=test_out.pcap:loops=10
+ *
+ *   in      the name of the input pcap file. If no input file is given
+ *           attempts to receive from the pktio will just return no
+ *           packets. If an input file is specified it must exist and be
+ *           a readable pcap file with a link type of DLT_EN10MB.
+ *   out     the name of the output pcap file. If no output file is
+ *           given any packets transmitted over the interface will just
+ *           be freed. If an output file is specified and the file
+ *           doesn't exist it will be created, if it does exist it will
+ *           be overwritten.
+ *   loops   the number of times to iterate through the input file, set
+ *           to 0 to loop indefinitely. The default value is 1.
+ *
+ * The total length of the string is limited by PKTIO_NAME_LEN.
+ */
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <odp.h>
+#include <odp_packet_internal.h>
+#include <odp_packet_io_internal.h>
+
+#include <odp/helper/eth.h>
+
+#include <pcap/pcap.h>
+#include <pcap/bpf.h>
+
+#define PKTIO_PCAP_MTU (64 * 1024)
+
+static int _pcapif_parse_devname(pkt_pcap_t *pcap, const char *devname)
+{
+	char *tok;
+	char in[PKTIO_NAME_LEN];
+
+	if (strncmp(devname, "pcap:", 5) != 0)
+		return -1;
+
+	snprintf(in, sizeof(in), "%s", devname);
+
+	for (tok = strtok(in + 5, ":"); tok; tok = strtok(NULL, ":")) {
+		if (strncmp(tok, "in=", 3) == 0 && !pcap->fname_rx) {
+			tok += 3;
+			pcap->fname_rx = strdup(tok);
+		} else if (strncmp(tok, "out=", 4) == 0 && !pcap->fname_tx) {
+			tok += 4;
+			pcap->fname_tx = strdup(tok);
+		} else if (strncmp(tok, "loops=", 6) == 0) {
+			pcap->loops = atoi(tok + 6);
+			if (pcap->loops < 0) {
+				ODP_ERR("invalid loop count\n");
+				return -1;
+			}
+		}
+	}
+
+	return 0;
+}
+
+static int _pcapif_init_rx(pkt_pcap_t *pcap)
+{
+	char errbuf[PCAP_ERRBUF_SIZE];
+	int linktype;
+
+	pcap->rx = pcap_open_offline(pcap->fname_rx, errbuf);
+	if (!pcap->rx) {
+		ODP_ERR("failed to open pcap file %s (%s)\n",
+			pcap->fname_rx, errbuf);
+		return -1;
+	}
+
+	linktype = pcap_datalink(pcap->rx);
+	if (linktype != DLT_EN10MB) {
+		ODP_ERR("unsupported datalink type: %d\n", linktype);
+		return -1;
+	}
+
+	return 0;
+}
+
+static int _pcapif_init_tx(pkt_pcap_t *pcap)
+{
+	pcap_t *tx = pcap->rx;
+
+	if (!tx) {
+		/* if there is no rx pcap_t already open for rx, a dummy
+		 * one needs to be opened for writing the dump */
+		tx = pcap_open_dead(DLT_EN10MB, PKTIO_PCAP_MTU);
+		if (!tx) {
+			ODP_ERR("failed to open TX dump\n");
+			return -1;
+		}
+
+		pcap->tx = tx;
+	}
+
+	pcap->buf = malloc(PKTIO_PCAP_MTU);
+	if (!pcap->buf) {
+		ODP_ERR("failed to malloc temp buffer\n");
+		return -1;
+	}
+
+	pcap->tx_dump = pcap_dump_open(tx, pcap->fname_tx);
+	if (!pcap->tx_dump) {
+		ODP_ERR("failed to open dump file %s (%s)\n",
+			pcap->fname_tx, pcap_geterr(tx));
+		return -1;
+	}
+
+	return pcap_dump_flush(pcap->tx_dump);
+}
+
+static int pcapif_init(odp_pktio_t id ODP_UNUSED, pktio_entry_t *pktio_entry,
+		       const char *devname, odp_pool_t pool)
+{
+	pkt_pcap_t *pcap = &pktio_entry->s.pkt_pcap;
+	int ret;
+
+	memset(pcap, 0, sizeof(pkt_pcap_t));
+	pcap->loop_cnt = 1;
+	pcap->loops = 1;
+	pcap->pool = pool;
+
+	ret = _pcapif_parse_devname(pcap, devname);
+
+	if (ret == 0 && pcap->fname_rx)
+		ret = _pcapif_init_rx(pcap);
+
+	if (ret == 0 && pcap->fname_tx)
+		ret = _pcapif_init_tx(pcap);
+
+	if (ret == 0 && (!pcap->rx && !pcap->tx_dump))
+		ret = -1;
+
+	return ret;
+}
+
+static int pcapif_close(pktio_entry_t *pktio_entry)
+{
+	pkt_pcap_t *pcap = &pktio_entry->s.pkt_pcap;
+
+	if (pcap->tx_dump)
+		pcap_dump_close(pcap->tx_dump);
+
+	if (pcap->tx)
+		pcap_close(pcap->tx);
+
+	if (pcap->rx)
+		pcap_close(pcap->rx);
+
+	free(pcap->buf);
+	free(pcap->fname_rx);
+	free(pcap->fname_tx);
+
+	return 0;
+}
+
+static int _pcapif_reopen(pkt_pcap_t *pcap)
+{
+	char errbuf[PCAP_ERRBUF_SIZE];
+
+	if (pcap->loops != 0 && ++pcap->loop_cnt >= pcap->loops)
+		return 1;
+
+	if (pcap->rx)
+		pcap_close(pcap->rx);
+
+	pcap->rx = pcap_open_offline(pcap->fname_rx, errbuf);
+	if (!pcap->rx) {
+		ODP_ERR("failed to reopen pcap file %s (%s)\n",
+			pcap->fname_rx, errbuf);
+		return 1;
+	}
+
+	return 0;
+}
+
+static int pcapif_recv_pkt(pktio_entry_t *pktio_entry, odp_packet_t pkts[],
+			   unsigned len)
+{
+	unsigned i;
+	struct pcap_pkthdr *hdr;
+	const u_char *data;
+	pkt_pcap_t *pcap = &pktio_entry->s.pkt_pcap;
+
+	ODP_ASSERT(pktio_entry->s.state == STATE_START);
+
+	if (!pcap->rx)
+		return 0;
+
+	for (i = 0; i < len; ) {
+		int ret = pcap_next_ex(pcap->rx, &hdr, &data);
+
+		/* end of file, attempt to reopen if within loop limit */
+		if (ret == -2 && _pcapif_reopen(pcap) == 0)
+			continue;
+
+		if (ret != 1)
+			break;
+
+		pkts[i] = odp_packet_alloc(pcap->pool, hdr->caplen);
+		if (odp_unlikely(pkts[i] == ODP_PACKET_INVALID))
+			break;
+
+		if (odp_packet_copydata_in(pkts[i], 0, hdr->caplen, data) != 0)
+			break;
+
+		_odp_packet_reset_parse(pkts[i]);
+
+		i++;
+	}
+
+	return i;
+}
+
+static int _pcapif_dump_pkt(pkt_pcap_t *pcap, odp_packet_t pkt)
+{
+	struct pcap_pkthdr hdr;
+
+	if (!pcap->tx_dump)
+		return 0;
+
+	hdr.caplen = odp_packet_len(pkt);
+	hdr.len = hdr.caplen;
+	(void)gettimeofday(&hdr.ts, NULL);
+
+	if (odp_packet_copydata_out(pkt, 0, hdr.len, pcap->buf) != 0)
+		return -1;
+
+	pcap_dump(pcap->tx_dump, &hdr, pcap->buf);
+	(void)pcap_dump_flush(pcap->tx_dump);
+
+	return 0;
+}
+
+static int pcapif_send_pkt(pktio_entry_t *pktio_entry, odp_packet_t pkts[],
+			   unsigned len)
+{
+	pkt_pcap_t *pcap = &pktio_entry->s.pkt_pcap;
+	unsigned i;
+
+	ODP_ASSERT(pktio_entry->s.state == STATE_START);
+
+	for (i = 0; i < len; ++i) {
+		if (odp_packet_len(pkts[i]) > PKTIO_PCAP_MTU) {
+			if (i == 0)
+				return -1;
+			break;
+		}
+
+		if (_pcapif_dump_pkt(pcap, pkts[i]) != 0)
+			break;
+
+		odp_packet_free(pkts[i]);
+	}
+
+	return i;
+}
+
+static int pcapif_mtu_get(pktio_entry_t *pktio_entry ODP_UNUSED)
+{
+	return PKTIO_PCAP_MTU;
+}
+
+static int pcapif_mac_addr_get(pktio_entry_t *pktio_entry ODP_UNUSED,
+			       void *mac_addr)
+{
+	/* this isn't used for anything, it's just here to keep callers of this
+	 * API happy in cases where the pcap interface is used as a test mock */
+	static const char pcap_mac[] = {0x02, 0xe9, 0x34, 0x80, 0x73, 0x02};
+
+	memcpy(mac_addr, pcap_mac, ODPH_ETHADDR_LEN);
+
+	return ODPH_ETHADDR_LEN;
+}
+
+static int pcapif_promisc_mode_set(pktio_entry_t *pktio_entry,
+				   odp_bool_t enable)
+{
+	pktio_entry->s.pkt_pcap.promisc = enable;
+
+	return 0;
+}
+
+static int pcapif_promisc_mode_get(pktio_entry_t *pktio_entry)
+{
+	return pktio_entry->s.pkt_pcap.promisc;
+}
+
+static int pcapif_start(pktio_entry_t *pktio_entry)
+{
+	pktio_entry->s.state = STATE_START;
+	return 0;
+}
+
+static int pcapif_stop(pktio_entry_t *pktio_entry)
+{
+	pktio_entry->s.state = STATE_STOP;
+	return 0;
+}
+
+const pktio_if_ops_t pcap_pktio_ops = {
+	.open = pcapif_init,
+	.close = pcapif_close,
+	.recv = pcapif_recv_pkt,
+	.send = pcapif_send_pkt,
+	.mtu_get = pcapif_mtu_get,
+	.promisc_mode_set = pcapif_promisc_mode_set,
+	.promisc_mode_get = pcapif_promisc_mode_get,
+	.mac_get = pcapif_mac_addr_get,
+	.start = pcapif_start,
+	.stop = pcapif_stop
+};
