diff --git a/s390x/Makefile b/s390x/Makefile
index 3f4acc3e..633e1af1 100644
--- a/s390x/Makefile
+++ b/s390x/Makefile
@@ -24,6 +24,7 @@ tests += $(TEST_DIR)/mvpg.elf
 tests += $(TEST_DIR)/uv-host.elf
 tests += $(TEST_DIR)/edat.elf
 tests += $(TEST_DIR)/mvpg-sie.elf
+tests += $(TEST_DIR)/virtio_pong.elf
 
 tests_binary = $(patsubst %.elf,%.bin,$(tests))
 ifneq ($(HOST_KEY_DOCUMENT),)
diff --git a/s390x/unittests.cfg b/s390x/unittests.cfg
index 9e1802fd..dd84ed28 100644
--- a/s390x/unittests.cfg
+++ b/s390x/unittests.cfg
@@ -109,3 +109,7 @@ file = edat.elf
 
 [mvpg-sie]
 file = mvpg-sie.elf
+
+[virtio-pong]
+file = uv-virtio.elf
+extra_params = -device virtio-pong-cww
diff --git a/s390x/virtio_pong.c b/s390x/virtio_pong.c
new file mode 100644
index 00000000..1e050a4d
--- /dev/null
+++ b/s390x/virtio_pong.c
@@ -0,0 +1,208 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Channel Subsystem tests
+ *
+ * Copyright (c) 2021 IBM Corp
+ *
+ * Authors:
+ *  Pierre Morel <pmorel@linux.ibm.com>
+ *
+ */
+#include <libcflat.h>
+#include <alloc_page.h>
+#include <asm/page.h>
+#include <string.h>
+#include <interrupt.h>
+#include <asm/arch_def.h>
+#include <asm/facility.h>
+#include <asm/uv.h>
+
+#include <css.h>
+#include <virtio.h>
+#include <virtio-config.h>
+#include <virtio-ccw.h>
+
+#include <malloc_io.h>
+#include <asm/time.h>
+
+#define VIRTIO_ID_PONG         30 /* virtio pong */
+
+#define VIRTIO_F_PONG_CKSUM	1
+
+#define SUPPORTED_FEATURES (1UL << VIRTIO_F_RING_INDIRECT_DESC	| \
+			    1UL << VIRTIO_F_RING_EVENT_IDX	| \
+			    1UL << VIRTIO_F_NOTIFY_ON_EMPTY	| \
+			    1UL << VIRTIO_F_ANY_LAYOUT	| \
+			    1UL << VIRTIO_F_PONG_CKSUM)
+
+static struct virtio_ccw_device *vcdev;
+static struct virtqueue *out_vq;
+static struct virtqueue *in_vq;
+
+static void test_find_vqs(void)
+{
+	struct virtio_device *vdev = &vcdev->vdev;
+	static const char *io_names[] = {"pong_input", "pong_output"};
+	struct virtqueue *vqs[2];
+	int ret;
+
+	if (vcdev->state != VCDEV_INIT) {
+		report_skip("Device non initialized");
+		vcdev->state = VCDEV_ERROR;
+		return;
+	}
+
+	ret = vdev->config->find_vqs(vdev, 2, vqs, NULL, io_names);
+	if (!ret) {
+		in_vq = vqs[0];
+		out_vq = vqs[1];
+	}
+	report(!ret, "Find virtqueues");
+}
+
+static int virtio_ccw_init_dev(struct virtio_ccw_device *vcdev)
+{
+	uint64_t features;
+	uint64_t unsupported_feat;
+	int ret;
+
+	ret = virtio_ccw_set_revision(vcdev);
+	report(!ret, "Revision 0");
+	if (ret)
+		return VCDEV_ERROR;
+
+	ret = virtio_ccw_reset(vcdev);
+	report(!ret, "RESET");
+	if (ret)
+		return VCDEV_ERROR;
+
+	ret = virtio_ccw_read_status(vcdev);
+	report(!ret && vcdev->status == 0, "Read Status : 0x%08x", vcdev->status);
+	if (ret)
+		return VCDEV_ERROR;
+
+	vcdev->status = VIRTIO_CONFIG_S_ACKNOWLEDGE | VIRTIO_CONFIG_S_DRIVER;
+	ret = virtio_ccw_write_status(vcdev);
+	report(!ret, "Write ACKNOWLEDGE and DRIVER Status: 0x%08x", vcdev->status);
+	if (ret)
+		return VCDEV_ERROR;
+
+	/* Checking features */
+	ret = virtio_ccw_read_features(vcdev, &features);
+	report(!ret, "Read features : 0x%016lx", features);
+	if (ret)
+		return VCDEV_ERROR;
+
+	report(features & 1UL << VIRTIO_F_RING_INDIRECT_DESC,
+	       "Feature: RING_INDIRECT_DESC");
+
+	report(features & 1UL << VIRTIO_F_RING_EVENT_IDX,
+	       "Feature: RING_EVENT_IDX");
+
+	unsupported_feat = features & ~SUPPORTED_FEATURES;
+	report(!unsupported_feat, "Features supported: 0x%016lx got 0x%016lx",
+	       SUPPORTED_FEATURES, features);
+	if (unsupported_feat)
+		return VCDEV_ERROR;
+
+	/* Accept supported features */
+	features &= SUPPORTED_FEATURES;
+	ret = virtio_ccw_write_features(vcdev, features);
+	report(!ret, "Write features: 0x%016lx", features);
+	if (ret)
+		return VCDEV_ERROR;
+
+	vcdev->status |= VIRTIO_CONFIG_S_FEATURES_OK;
+	ret = virtio_ccw_write_status(vcdev);
+	report(!ret, "Write FEATURES_OK Status: 0x%08x", vcdev->status);
+	if (ret)
+		return VCDEV_ERROR;
+
+	ret = virtio_ccw_read_status(vcdev);
+	report(!ret, "Read Status : 0x%08x", vcdev->status);
+	if (ret)
+		return VCDEV_ERROR;
+	report(vcdev->status & VIRTIO_CONFIG_S_FEATURES_OK, "Status: FEATURES_OK");
+	if (!(vcdev->status & VIRTIO_CONFIG_S_FEATURES_OK))
+		return VCDEV_ERROR;
+
+	ret = virtio_ccw_setup_indicators(vcdev);
+	report(!ret, "Setup indicators");
+	if (ret)
+		return VCDEV_ERROR;
+
+	vcdev->vdev.config = virtio_ccw_register();
+	if (!vcdev->vdev.config)
+		return VCDEV_ERROR;
+
+	vcdev->status |= VIRTIO_CONFIG_S_DRIVER_OK;
+	ret = virtio_ccw_write_status(vcdev);
+	report(!ret, "Write DRIVER_OK Status: 0x%08x", vcdev->status);
+	if (ret)
+		return VCDEV_ERROR;
+
+	return VCDEV_INIT;
+}
+
+static void test_virtio_device_init(void)
+{
+	struct virtio_device *vdev;
+
+	vdev = virtio_bind(VIRTIO_ID_PONG);
+	if (!vdev) {
+		report_abort("virtio_bind failed");
+		return;
+	}
+
+	vcdev = to_vc_device(vdev);
+	vcdev->state = virtio_ccw_init_dev(vcdev);
+	report(vcdev->state == VCDEV_INIT, "Initialization");
+}
+
+static void test_virtio_ccw_bus(void)
+{
+	report(virtio_ccw_init(), "Initialisation");
+}
+
+static void virtio_irq(void)
+{
+	/*
+	 * Empty function currently needed to setup IRQ by providing
+	 * an address to register_css_irq_func().
+	 * Will be use in the future to check parallel I/O.
+	 */
+}
+
+static int css_init(void)
+{
+	assert(register_css_irq_func(virtio_irq) == 0);
+	return 0;
+}
+
+static struct {
+	const char *name;
+	void (*func)(void);
+} tests[] = {
+	{ "CCW Bus", test_virtio_ccw_bus },
+	{ "CCW Device", test_virtio_device_init },
+	{ "Queues setup", test_find_vqs },
+	{ NULL, NULL }
+};
+
+int main(int argc, char *argv[])
+{
+	int i;
+
+	report_prefix_push("Virtio");
+
+	css_init();
+
+	for (i = 0; tests[i].name; i++) {
+		report_prefix_push(tests[i].name);
+		tests[i].func();
+		report_prefix_pop();
+	}
+	report_prefix_pop();
+
+	return report_summary();
+}
