diff mbox

[PATCHv4,2/4] validation: pktio: initial pktio tests

Message ID 1418900657-21427-3-git-send-email-maxim.uvarov@linaro.org
State New
Headers show

Commit Message

Maxim Uvarov Dec. 18, 2014, 11:04 a.m. UTC
From: Stuart Haslam <stuart.haslam@arm.com>

This is primarily sunny day basic functional testing, all tests are
single threaded.

On platforms other than linux-generic testing will be performed using
the "loop" interface by default.

For linux-generic, since we don't have a working "loop" device, a
wrapper script is run that attempts to create a pair of virtual
Ethernet interfaces for testing. If creating the interfaces fails
the test is skipped and "make check" reports it as such.

Signed-off-by: Stuart Haslam <stuart.haslam@arm.com>
Reviewed-and-tested-by: Maxim Uvarov <maxim.uvarov@linaro.org>
---
 test/validation/.gitignore    |   1 +
 test/validation/Makefile.am   |   7 +-
 test/validation/odp_pktio.c   | 501 ++++++++++++++++++++++++++++++++++++++++++
 test/validation/odp_pktio_run | 132 +++++++++++
 4 files changed, 639 insertions(+), 2 deletions(-)
 create mode 100644 test/validation/odp_pktio.c
 create mode 100755 test/validation/odp_pktio_run
diff mbox

Patch

diff --git a/test/validation/.gitignore b/test/validation/.gitignore
index 32834ae..c727223 100644
--- a/test/validation/.gitignore
+++ b/test/validation/.gitignore
@@ -5,3 +5,4 @@  odp_queue
 odp_crypto
 odp_schedule
 odp_shm
+odp_pktio
diff --git a/test/validation/Makefile.am b/test/validation/Makefile.am
index d0b5426..cbeef5d 100644
--- a/test/validation/Makefile.am
+++ b/test/validation/Makefile.am
@@ -3,10 +3,12 @@  include $(top_srcdir)/test/Makefile.inc
 AM_CFLAGS += -I$(srcdir)/common
 AM_LDFLAGS += -static
 
+AM_TESTS_ENVIRONMENT = ODP_PLATFORM=${with_platform}
+
 if ODP_CUNIT_ENABLED
-TESTS = ${bin_PROGRAMS}
+TESTS = odp_init odp_queue odp_crypto odp_shm odp_schedule odp_pktio_run
 check_PROGRAMS = ${bin_PROGRAMS}
-bin_PROGRAMS = odp_init odp_queue odp_crypto odp_shm odp_schedule
+bin_PROGRAMS = odp_init odp_queue odp_crypto odp_shm odp_schedule odp_pktio
 odp_init_LDFLAGS = $(AM_LDFLAGS)
 odp_queue_LDFLAGS = $(AM_LDFLAGS)
 odp_crypto_CFLAGS = $(AM_CFLAGS) -I$(srcdir)/crypto
@@ -18,6 +20,7 @@  odp_schedule_LDFLAGS = $(AM_LDFLAGS)
 endif
 
 dist_odp_init_SOURCES = odp_init.c
+dist_odp_pktio_SOURCES = odp_pktio.c common/odp_cunit_common.c
 dist_odp_queue_SOURCES = odp_queue.c common/odp_cunit_common.c
 dist_odp_crypto_SOURCES = crypto/odp_crypto_test_async_inp.c \
 			  crypto/odp_crypto_test_sync_inp.c \
diff --git a/test/validation/odp_pktio.c b/test/validation/odp_pktio.c
new file mode 100644
index 0000000..75a9859
--- /dev/null
+++ b/test/validation/odp_pktio.c
@@ -0,0 +1,501 @@ 
+/* Copyright (c) 2014, Linaro Limited
+ * All rights reserved.
+ *
+ * SPDX-License-Identifier:     BSD-3-Clause
+ */
+#include <odp.h>
+#include <odp_cunit_common.h>
+#include <odp_packet.h>
+
+#include <odph_eth.h>
+#include <odph_ip.h>
+#include <odph_udp.h>
+
+#include <stdlib.h>
+
+#define SHM_PKT_POOL_SIZE      (32*2048)
+#define SHM_PKT_POOL_BUF_SIZE  1856
+#define MAX_NUM_IFACES         2
+#define TEST_SEQ_INVALID       ((uint32_t)~0)
+#define TEST_SEQ_MAGIC         0x92749451
+
+/** interface names used for testing */
+static const char *iface_name[MAX_NUM_IFACES];
+
+/** number of interfaces being used (1=loopback, 2=pair) */
+static int num_ifaces;
+
+/** local container for pktio attributes */
+typedef struct {
+	const char *name;
+	odp_pktio_t id;
+	odp_queue_t outq;
+	odp_queue_t inq;
+} pktio_info_t;
+
+/** structure of test packet UDP payload */
+typedef struct {
+	uint32be_t magic;
+	uint32be_t seq;
+} pkt_test_data_t;
+
+/** default packet pool */
+odp_buffer_pool_t default_pkt_pool = ODP_BUFFER_POOL_INVALID;
+
+/** sequence number of IP packets */
+odp_atomic_u32_t ip_seq;
+
+static void pktio_pkt_set_macs(odp_packet_t pkt,
+			       pktio_info_t *src, pktio_info_t *dst)
+{
+	uint32_t len;
+	odph_ethhdr_t *eth = (odph_ethhdr_t *)odp_packet_l2_ptr(pkt, &len);
+	int ret;
+
+	ret = odp_pktio_mac_addr(src->id, &eth->src, sizeof(eth->src));
+	CU_ASSERT(ret == ODPH_ETHADDR_LEN);
+
+	ret = odp_pktio_mac_addr(dst->id, &eth->dst, sizeof(eth->dst));
+	CU_ASSERT(ret == ODPH_ETHADDR_LEN);
+}
+
+static int pktio_pkt_set_seq(odp_packet_t pkt)
+{
+	static uint32_t tstseq;
+	size_t l4_off;
+	pkt_test_data_t data;
+
+	data.magic = TEST_SEQ_MAGIC;
+	data.seq   = tstseq;
+
+	l4_off = odp_packet_l4_offset(pkt);
+	if (!l4_off) {
+		CU_FAIL("packet L4 offset not set");
+		return -1;
+	}
+
+	odp_packet_copydata_in(pkt, l4_off+ODPH_UDPHDR_LEN,
+			       sizeof(data), &data);
+
+	tstseq++;
+
+	return 0;
+}
+
+static uint32_t pktio_pkt_seq(odp_packet_t pkt)
+{
+	size_t l4_off;
+	pkt_test_data_t data;
+
+	l4_off = odp_packet_l4_offset(pkt);
+	if (l4_off) {
+		odp_packet_copydata_out(pkt, l4_off+ODPH_UDPHDR_LEN,
+					sizeof(data), &data);
+
+		if (data.magic == TEST_SEQ_MAGIC)
+			return data.seq;
+	}
+
+	return TEST_SEQ_INVALID;
+}
+
+static odp_packet_t pktio_create_packet(void)
+{
+	odp_packet_t pkt;
+	odph_ethhdr_t *eth;
+	odph_ipv4hdr_t *ip;
+	odph_udphdr_t *udp;
+	char *buf;
+	uint16_t seq;
+	size_t payload_len = sizeof(pkt_test_data_t);
+	uint8_t mac[ODPH_ETHADDR_LEN] = {0};
+
+	pkt = odp_packet_alloc(default_pkt_pool, payload_len + ODPH_UDPHDR_LEN +
+			       ODPH_IPV4HDR_LEN + ODPH_ETHHDR_LEN);
+	if (pkt == ODP_PACKET_INVALID) {
+		CU_FAIL("failed to allocate packet buffer");
+		return ODP_PACKET_INVALID;
+	}
+	buf = odp_packet_data(pkt);
+
+	/* Ethernet */
+	odp_packet_l2_offset_set(pkt, 0);
+	eth = (odph_ethhdr_t *)buf;
+	memcpy(eth->src.addr, mac, ODPH_ETHADDR_LEN);
+	memcpy(eth->dst.addr, mac, ODPH_ETHADDR_LEN);
+	eth->type = odp_cpu_to_be_16(ODPH_ETHTYPE_IPV4);
+
+	/* IP */
+	odp_packet_l3_offset_set(pkt, ODPH_ETHHDR_LEN);
+	ip = (odph_ipv4hdr_t *)(buf + ODPH_ETHHDR_LEN);
+	ip->dst_addr = odp_cpu_to_be_32(0);
+	ip->src_addr = odp_cpu_to_be_32(0);
+	ip->ver_ihl = ODPH_IPV4 << 4 | ODPH_IPV4HDR_IHL_MIN;
+	ip->tot_len = odp_cpu_to_be_16(payload_len + ODPH_UDPHDR_LEN +
+				       ODPH_IPV4HDR_LEN);
+	ip->ttl = 128;
+	ip->proto = ODPH_IPPROTO_UDP;
+	seq = odp_atomic_fetch_inc_u32(&ip_seq);
+	ip->id = odp_cpu_to_be_16(seq);
+	ip->chksum = 0;
+	odph_ipv4_csum_update(pkt);
+
+	/* UDP */
+	odp_packet_l4_offset_set(pkt, ODPH_ETHHDR_LEN + ODPH_IPV4HDR_LEN);
+	udp = (odph_udphdr_t *)(buf + ODPH_ETHHDR_LEN + ODPH_IPV4HDR_LEN);
+	udp->src_port = odp_cpu_to_be_16(0);
+	udp->dst_port = odp_cpu_to_be_16(0);
+	udp->length = odp_cpu_to_be_16(payload_len + ODPH_UDPHDR_LEN);
+	udp->chksum = 0;
+
+	if (pktio_pkt_set_seq(pkt) != 0) {
+		odp_packet_free(pkt);
+		return ODP_PACKET_INVALID;
+	}
+
+	return pkt;
+}
+
+static int pktio_fixup_checksums(odp_packet_t pkt)
+{
+	odph_ipv4hdr_t *ip;
+	odph_udphdr_t *udp;
+	uint32_t len;
+
+	ip = (odph_ipv4hdr_t *)odp_packet_l3_ptr(pkt, &len);
+
+	if (ip->proto != ODPH_IPPROTO_UDP) {
+		CU_FAIL("unexpected L4 protocol");
+		return -1;
+	}
+
+	udp = (odph_udphdr_t *)odp_packet_l4_ptr(pkt, &len);
+
+	ip->chksum = 0;
+	odph_ipv4_csum_update(pkt);
+	udp->chksum = 0;
+	udp->chksum = odph_ipv4_udp_chksum(pkt);
+
+	return 0;
+}
+
+static int default_pool_create(void)
+{
+	odp_buffer_pool_param_t params;
+
+	if (default_pkt_pool != ODP_BUFFER_POOL_INVALID)
+		return -1;
+
+	params.buf_size  = SHM_PKT_POOL_BUF_SIZE;
+	params.buf_align = 0;
+	params.num_bufs  = SHM_PKT_POOL_SIZE/SHM_PKT_POOL_BUF_SIZE;
+	params.buf_type  = ODP_BUFFER_TYPE_PACKET;
+
+	default_pkt_pool = odp_buffer_pool_create("pkt_pool_default",
+						  ODP_SHM_NULL, &params);
+	if (default_pkt_pool == ODP_BUFFER_POOL_INVALID)
+		return -1;
+
+	return 0;
+}
+
+static odp_pktio_t create_pktio(const char *iface)
+{
+	odp_buffer_pool_t pool;
+	odp_pktio_t pktio;
+	char pool_name[ODP_BUFFER_POOL_NAME_LEN];
+	odp_buffer_pool_param_t params;
+
+	params.buf_size  = SHM_PKT_POOL_BUF_SIZE;
+	params.buf_align = 0;
+	params.num_bufs  = SHM_PKT_POOL_SIZE/SHM_PKT_POOL_BUF_SIZE;
+	params.buf_type  = ODP_BUFFER_TYPE_PACKET;
+
+	snprintf(pool_name, sizeof(pool_name), "pkt_pool_%s", iface);
+	pool = odp_buffer_pool_lookup(pool_name);
+	if (pool == ODP_BUFFER_POOL_INVALID)
+		pool = odp_buffer_pool_create(pool_name, ODP_SHM_NULL, &params);
+	CU_ASSERT(pool != ODP_BUFFER_POOL_INVALID);
+
+	pktio = odp_pktio_open(iface, pool);
+	CU_ASSERT(pktio != ODP_PKTIO_INVALID);
+
+	return pktio;
+}
+
+static int create_inq(odp_pktio_t pktio)
+{
+	odp_queue_param_t qparam;
+	odp_queue_t inq_def;
+	char inq_name[ODP_QUEUE_NAME_LEN];
+
+	qparam.sched.prio  = ODP_SCHED_PRIO_DEFAULT;
+	qparam.sched.sync  = ODP_SCHED_SYNC_ATOMIC;
+	qparam.sched.group = ODP_SCHED_GROUP_DEFAULT;
+
+	snprintf(inq_name, sizeof(inq_name), "inq-pktio-%d", pktio);
+	inq_def = odp_queue_lookup(inq_name);
+	if (inq_def == ODP_QUEUE_INVALID)
+		inq_def = odp_queue_create(inq_name,
+				ODP_QUEUE_TYPE_PKTIN, &qparam);
+	CU_ASSERT(inq_def != ODP_QUEUE_INVALID);
+
+	return odp_pktio_inq_setdef(pktio, inq_def);
+}
+
+static odp_buffer_t queue_deq_wait_time(odp_queue_t queue, uint64_t ns)
+{
+	uint64_t start, now, diff;
+	odp_buffer_t buf;
+
+	start = odp_time_cycles();
+
+	do {
+		buf = odp_queue_deq(queue);
+		if (buf != ODP_BUFFER_INVALID)
+			return buf;
+		now = odp_time_cycles();
+		diff = odp_time_diff_cycles(start, now);
+	} while (odp_time_cycles_to_ns(diff) < ns);
+
+	return ODP_BUFFER_INVALID;
+}
+
+static odp_packet_t wait_for_packet(odp_queue_t queue,
+				    uint32_t seq, uint64_t ns)
+{
+	uint64_t start, now, diff;
+	odp_buffer_t buf;
+	odp_packet_t pkt = ODP_PACKET_INVALID;
+
+	start = odp_time_cycles();
+
+	do {
+		if (queue != ODP_QUEUE_INVALID)
+			buf = queue_deq_wait_time(queue, ns);
+		else
+			buf = odp_schedule(NULL, ns);
+
+		if (buf != ODP_BUFFER_INVALID &&
+		    odp_buffer_type(buf) == ODP_BUFFER_TYPE_PACKET) {
+			pkt = odp_packet_from_buffer(buf);
+			if (pktio_pkt_seq(pkt) == seq)
+				return pkt;
+		}
+
+		now = odp_time_cycles();
+		diff = odp_time_diff_cycles(start, now);
+	} while (odp_time_cycles_to_ns(diff) < ns);
+
+	CU_FAIL("failed to receive transmitted packet");
+
+	return ODP_PACKET_INVALID;
+}
+
+static void pktio_txrx_multi(pktio_info_t *pktio_a, pktio_info_t *pktio_b,
+			     int num_pkts)
+{
+	odp_packet_t tx_pkt[num_pkts];
+	odp_buffer_t tx_buf[num_pkts];
+	odp_packet_t rx_pkt;
+	uint32_t tx_seq[num_pkts];
+	int i, ret;
+
+	/* generate test packets to send */
+	for (i = 0; i < num_pkts; ++i) {
+		tx_pkt[i] = pktio_create_packet();
+		if (tx_pkt[i] == ODP_PACKET_INVALID)
+			break;
+
+		tx_seq[i] = pktio_pkt_seq(tx_pkt[i]);
+		if (tx_seq[i] == TEST_SEQ_INVALID)
+			break;
+
+		pktio_pkt_set_macs(tx_pkt[i], pktio_a, pktio_b);
+		if (pktio_fixup_checksums(tx_pkt[i]) != 0)
+			break;
+
+		tx_buf[i] = odp_packet_to_buffer(tx_pkt[i]);
+	}
+
+	if (i != num_pkts) {
+		CU_FAIL("failed to generate test packets");
+		return;
+	}
+
+	/* send packet(s) out */
+	if (num_pkts == 1)
+		ret = odp_queue_enq(pktio_a->outq, tx_buf[0]);
+	else
+		ret = odp_queue_enq_multi(pktio_a->outq, tx_buf, num_pkts);
+
+	if (ret != 0) {
+		CU_FAIL("failed to enqueue test packets");
+		return;
+	}
+
+	/* and wait for them to arrive back */
+	for (i = 0; i < num_pkts; ++i) {
+		rx_pkt = wait_for_packet(pktio_b->inq, tx_seq[i], ODP_TIME_SEC);
+
+		if (rx_pkt == ODP_PACKET_INVALID)
+			break;
+		CU_ASSERT(odp_packet_input(rx_pkt) == pktio_b->id);
+		CU_ASSERT(odp_packet_error(rx_pkt) == 0);
+		odp_packet_free(rx_pkt);
+	}
+
+	CU_ASSERT(i == num_pkts);
+}
+
+static void pktio_test_txrx(odp_queue_type_t q_type, int num_pkts)
+{
+	int ret, i, if_b;
+	pktio_info_t pktios[MAX_NUM_IFACES];
+	pktio_info_t *io;
+
+	/* create pktios and associate input/output queues */
+	for (i = 0; i < num_ifaces; ++i) {
+		io = &pktios[i];
+
+		io->name = iface_name[i];
+		io->id   = create_pktio(iface_name[i]);
+		if (io->id == ODP_PKTIO_INVALID) {
+			CU_FAIL("failed to open iface");
+			return;
+		}
+		create_inq(io->id);
+		io->outq = odp_pktio_outq_getdef(io->id);
+		if (q_type == ODP_QUEUE_TYPE_POLL)
+			io->inq = odp_pktio_inq_getdef(io->id);
+		else
+			io->inq = ODP_QUEUE_INVALID;
+	}
+
+	/* if we have two interfaces then send through one and receive on
+	 * another but if there's only one assume it's a loopback */
+	if_b = (num_ifaces == 1) ? 0 : 1;
+	pktio_txrx_multi(&pktios[0], &pktios[if_b], num_pkts);
+
+	for (i = 0; i < num_ifaces; ++i) {
+		ret = odp_pktio_close(pktios[i].id);
+		CU_ASSERT(ret == 0);
+	}
+}
+
+static void test_odp_pktio_poll_queue(void)
+{
+	pktio_test_txrx(ODP_QUEUE_TYPE_POLL, 1);
+}
+
+static void test_odp_pktio_poll_multi(void)
+{
+	pktio_test_txrx(ODP_QUEUE_TYPE_POLL, 4);
+}
+
+static void test_odp_pktio_sched_queue(void)
+{
+	pktio_test_txrx(ODP_QUEUE_TYPE_SCHED, 1);
+}
+
+static void test_odp_pktio_sched_multi(void)
+{
+	pktio_test_txrx(ODP_QUEUE_TYPE_SCHED, 4);
+}
+
+static void test_odp_pktio_open(void)
+{
+	odp_pktio_t pktio;
+	int i;
+
+	/* test the sequence open->close->open->close() */
+	for (i = 0; i < 2; ++i) {
+		pktio = create_pktio(iface_name[0]);
+		CU_ASSERT(pktio != ODP_PKTIO_INVALID);
+		CU_ASSERT(odp_pktio_close(pktio) == 0);
+	}
+
+	pktio = odp_pktio_open("nothere", default_pkt_pool);
+	CU_ASSERT(pktio == ODP_PKTIO_INVALID);
+}
+
+static void test_odp_pktio_inq(void)
+{
+	odp_pktio_t pktio;
+
+	pktio = create_pktio(iface_name[0]);
+	CU_ASSERT(pktio != ODP_PKTIO_INVALID);
+
+	CU_ASSERT(create_inq(pktio) == 0);
+
+	CU_ASSERT(odp_pktio_close(pktio) == 0);
+}
+
+static void test_odp_pktio_outq(void)
+{
+	odp_queue_t testq;
+
+	testq = odp_pktio_outq_getdef(ODP_PKTIO_INVALID);
+	CU_ASSERT(testq == ODP_QUEUE_INVALID);
+}
+
+static void test_odp_pktio_close(void)
+{
+	int res;
+
+	res = odp_pktio_close(ODP_PKTIO_INVALID);
+	CU_ASSERT_EQUAL(res, -1);
+}
+
+static int init_pktio_suite(void)
+{
+	iface_name[0] = getenv("ODP_PKTIO_IF0");
+	iface_name[1] = getenv("ODP_PKTIO_IF1");
+	num_ifaces = 1;
+
+	if (!iface_name[0]) {
+		printf("No interfaces specified, using default \"loop\".\n");
+		iface_name[0] = "loop";
+	} else if (!iface_name[1]) {
+		printf("Using loopback interface: %s\n", iface_name[0]);
+	} else {
+		num_ifaces = 2;
+		printf("Using paired interfaces: %s %s\n",
+		       iface_name[0], iface_name[1]);
+	}
+
+	if (default_pool_create() != 0) {
+		fprintf(stderr, "error: failed to create default pool\n");
+		return -1;
+	}
+
+	return 0;
+}
+
+static int term_pktio_suite(void)
+{
+	if (odp_buffer_pool_destroy(default_pkt_pool) != 0) {
+		fprintf(stderr, "error: failed to destroy default pool\n");
+		return -1;
+	}
+
+	return 0;
+}
+
+CU_TestInfo pktio_tests[] = {
+	{"pktio open",         test_odp_pktio_open},
+	{"pktio close",        test_odp_pktio_close},
+	{"pktio inq",          test_odp_pktio_inq},
+	{"pktio outq",         test_odp_pktio_outq},
+	{"pktio poll queues",  test_odp_pktio_poll_queue},
+	{"pktio poll multi",   test_odp_pktio_poll_multi},
+	{"pktio sched queues", test_odp_pktio_sched_queue},
+	{"pktio sched multi",  test_odp_pktio_sched_multi},
+	CU_TEST_INFO_NULL
+};
+
+CU_SuiteInfo odp_testsuites[] = {
+	{"odp_pktio",
+		init_pktio_suite, term_pktio_suite, NULL, NULL, pktio_tests},
+	CU_SUITE_INFO_NULL
+};
diff --git a/test/validation/odp_pktio_run b/test/validation/odp_pktio_run
new file mode 100755
index 0000000..d4d6b2c
--- /dev/null
+++ b/test/validation/odp_pktio_run
@@ -0,0 +1,132 @@ 
+#!/bin/sh
+#
+# Test script wrapper for running ODP pktio tests on linux-generic.
+#
+# For platforms other than linux-generic this script does nothing other
+# than running the odp_pktio binary, odp_pktio will then attempt to
+# open and use the special device named "loop" for testing.
+#
+# For linux-generic the default behaviour is to create a pair of
+# virtual Ethernet interfaces and provide the names of these via
+# environment variables to odp_pktio, the interfaces will be removed
+# before the script exits. Note that the creation of virtual Ethernet
+# devices depends on having CONFIG_VETH enabled in the kernel, if not
+# enabled the test will be skipped.
+#
+# The evironment variable ODP_PLATFORM is used to determine the
+# platform under test, when this script is invoked via 'make check'
+# this variable is set automatically.
+#
+# It's also possible to split up the steps, which makes it easier when
+# debugging, for example;
+#
+# export ODP_PLATFORM=linux-generic
+# odp_pktio_run setup
+# wireshark -i pktio-p0 -k &
+# odp_pktio_run
+# (repeat running test multiple times..)
+# odp_pktio_run cleanup
+#
+TEST_DIR=$(dirname $0)
+IF0=pktio-p0
+IF1=pktio-p1
+
+# exit codes expected by automake for skipped tests
+TEST_SKIPPED=77
+
+setup_env1()
+{
+	ip link show $IF0 2> /dev/null
+	if [ $? = 0 ]; then
+		ip link show $IF1 2> /dev/null
+		if [ $? = 0 ]; then
+			echo "pktio: interfaces $IF0 and $IF1 already exist"
+			return
+		fi
+	fi
+
+	echo "pktio: setting up test interfaces $IF0 and $IF1"
+
+	if [ "$1" = "clean" ]; then
+		trap cleanup_env1 EXIT
+	fi
+
+	ip link add $IF0 type veth peer name $IF1
+	if [ $? != 0 ]; then
+		echo "pktio: error: unable to create veth pair"
+		exit $TEST_SKIPPED
+	fi
+	ip link set $IF0 up
+	ip link set $IF1 up
+
+	# network needs a little time to come up
+	sleep 1
+}
+
+cleanup_env1()
+{
+	echo "pktio: removing test interfaces $IF0 and $IF1"
+	ip link del $IF0 2> /dev/null
+	ip link del $IF1 2> /dev/null
+}
+
+run_test()
+{
+	local ret=0
+
+	# the linux-generic implementation uses environment variables to
+	# control which socket method is used, so try each combination to
+	# ensure decent coverage.
+	for distype in MMAP MMSG BASIC; do
+		unset ODP_PKTIO_DISABLE_SOCKET_${distype}
+	done
+
+	for distype in SKIP MMAP MMSG; do
+		if [ "$disabletype" != "SKIP" ]; then
+			export ODP_PKTIO_DISABLE_SOCKET_${distype}=y
+		fi
+		$TEST_DIR/odp_pktio
+		if [ $? != 0 ]; then
+			ret=1
+		fi
+	done
+
+	if [ $ret != 0 ]; then
+		echo "!!! FAILED !!!"
+	fi
+
+	exit $ret
+}
+
+run()
+{
+	if [ "$ODP_PLATFORM" != "linux-generic" ]; then
+		echo "pktio: using 'loop' device"
+		$TEST_DIR/odp_pktio
+		exit $?
+	elif [ "$ODP_PKTIO_IF0" = "" ]; then
+		# no interfaces specified on linux-generic, use defaults
+		setup_env1 clean
+		export ODP_PKTIO_IF0=$IF0
+		export ODP_PKTIO_IF1=$IF1
+	fi
+
+	run_test
+}
+
+if [ "$ODP_PLATFORM" = "" ]; then
+	echo "pktio: error: ODP_PLATFORM must be defined"
+	# not skipped as this should never happen via "make check"
+	exit 1
+fi
+
+if [ "$(id -u)" != "0" ]; then
+	echo "pktio: error: must be run as root"
+	exit $TEST_SKIPPED
+fi
+
+case "$1" in
+	setup)   setup_env1   ;;
+	cleanup) cleanup_env1 ;;
+	*)       run          ;;
+esac