diff --git a/lib/s390x/asm/time.h b/lib/s390x/asm/time.h
new file mode 100644
index 0000000..856facd
--- /dev/null
+++ b/lib/s390x/asm/time.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2017 Red Hat Inc
+ *
+ * Authors:
+ *  Thomas Huth <thuth@redhat.com>
+ *  David Hildenbrand <david@redhat.com>
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Library General Public License version 2.
+ */
+#ifndef __ASMS390X_TIME_H
+#define __ASMS390X_TIME_H
+
+static inline uint64_t get_clock_ms(void)
+{
+	uint64_t clk;
+
+	asm volatile(" stck %0 " : : "Q"(clk) : "memory");
+
+	/* Bit 51 is incrememented each microsecond */
+	return (clk >> (63 - 51)) / 1000;
+}
+
+#endif
diff --git a/lib/testrun.c b/lib/testrun.c
new file mode 100644
index 0000000..6034767
--- /dev/null
+++ b/lib/testrun.c
@@ -0,0 +1,89 @@
+/*
+ * Utility functions for running/configuring a set of tests
+ *
+ * Copyright (c) 2017 Red Hat Inc
+ *
+ * Authors:
+ *  Thomas Huth <thuth@redhat.com>
+ *  David Hildenbrand <david@redhat.com>
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Library General Public License version 2.
+ */
+
+#include <testrun.h>
+#include <asm/time.h>
+
+void parse_test_args(const int argc, const char **argv, struct test *test)
+{
+	bool run_all = true;
+	int i, ti;
+
+	test->iterations = 0;
+	test->time_to_run = 0;
+
+	for (i = 1; i < argc; i++) {
+		if (!strcmp("-i", argv[i])) {
+			i++;
+			if (i >= argc)
+				report_abort("-i needs a parameter");
+			test->iterations = atol(argv[i]);
+		} else if (!strcmp("-t", argv[i])) {
+			i++;
+			if (i >= argc)
+				report_abort("-t needs a parameter");
+			test->time_to_run = atol(argv[i]);
+		} else for (ti = 0; test->test_cases[ti].name != NULL; ti++) {
+			if (!strcmp(test->test_cases[ti].name, argv[i])) {
+				run_all = false;
+				test->test_cases[ti].run_it = true;
+				break;
+			} else if (test->test_cases[ti + 1].name == NULL) {
+				report_abort("Unsupported parameter '%s'",
+					     argv[i]);
+			}
+		}
+	}
+
+	if (test->iterations == 0 && test->time_to_run == 0)
+		test->iterations = 1;
+
+	if ((test->iterations > 1 || test->time_to_run) &&
+	    !test->multirun_supported)
+		report_abort("Test does not support multiruns.");
+
+	if (run_all) {
+		for (ti = 0; test->test_cases[ti].name != NULL; ti++)
+			test->test_cases[ti].run_it = true;
+	}
+}
+
+int run_test(struct test *test)
+{
+	unsigned long iterations = test->iterations;
+	unsigned long time_to_run = test->time_to_run;
+	uint64_t startclk;
+	int ti;
+
+	report_prefix_push(test->name);
+	startclk = get_clock_ms();
+	for (;;) {
+		for (ti = 0; test->test_cases[ti].name != NULL; ti++) {
+			report_prefix_push(test->test_cases[ti].name);
+			if (test->test_cases[ti].run_it)
+				test->test_cases[ti].func();
+			report_prefix_pop();
+		}
+		if (iterations) {
+			iterations -= 1;
+			if (iterations == 0)
+				break;
+		}
+		if (time_to_run) {
+			if (get_clock_ms() - startclk > time_to_run)
+				break;
+		}
+	}
+	report_prefix_pop();
+	return report_summary();
+}
diff --git a/lib/testrun.h b/lib/testrun.h
new file mode 100644
index 0000000..8f9a2d7
--- /dev/null
+++ b/lib/testrun.h
@@ -0,0 +1,41 @@
+/*
+ * Utility functions for running/configuring a set of test cases
+ *
+ * Copyright (c) 2017 Red Hat Inc
+ *
+ * Authors:
+ *  Thomas Huth <thuth@redhat.com>
+ *  David Hildenbrand <david@redhat.com>
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Library General Public License version 2.
+ */
+#ifndef __TESTRUN_H
+#define __TESTRUN_H
+
+#include <libcflat.h>
+
+struct test_case {
+	const char *name;
+	void (*func)(void);
+	/* initialized by parse_test_args() */
+	bool run_it;
+};
+
+#define DEFINE_TEST_CASE(_name, _func) { _name, _func, false }
+#define DEFINE_TEST_CASE_END_OF_LIST() { }
+
+struct test {
+	/* to be initialized by the test */
+	const char *name;
+	bool multirun_supported;
+	struct test_case *test_cases;
+	/* initialized by parse_test_args() */
+	unsigned long iterations;
+	unsigned long time_to_run;
+};
+
+void parse_test_args(int argc, const char **argv, struct test *test);
+int run_test(struct test *test);
+
+#endif /* __TESTRUN_H */
diff --git a/s390x/Makefile b/s390x/Makefile
index bc099da..e174895 100644
--- a/s390x/Makefile
+++ b/s390x/Makefile
@@ -21,6 +21,7 @@ include $(SRCDIR)/scripts/asm-offsets.mak
 
 cflatobjs += lib/util.o
 cflatobjs += lib/alloc.o
+cflatobjs += lib/testrun.o
 cflatobjs += lib/s390x/io.o
 cflatobjs += lib/s390x/stack.o
 cflatobjs += lib/s390x/sclp-ascii.o
diff --git a/s390x/intercept.c b/s390x/intercept.c
index 59e5fca..15e50f6 100644
--- a/s390x/intercept.c
+++ b/s390x/intercept.c
@@ -10,15 +10,13 @@
  * under the terms of the GNU Library General Public License version 2.
  */
 #include <libcflat.h>
+#include <testrun.h>
 #include <asm/asm-offsets.h>
 #include <asm/interrupt.h>
 #include <asm/page.h>
 
 static uint8_t pagebuf[PAGE_SIZE * 2] __attribute__((aligned(PAGE_SIZE * 2)));
 
-static unsigned long nr_iterations;
-static unsigned long time_to_run;
-
 /* Test the STORE PREFIX instruction */
 static void test_stpx(void)
 {
@@ -159,95 +157,21 @@ static void test_testblock(void)
 	check_pgm_int_code(PGM_INT_CODE_ADDRESSING);
 }
 
-static uint64_t get_clock_ms(void)
-{
-	uint64_t clk;
-
-	asm volatile(" stck %0 " : : "Q"(clk) : "memory");
-
-	/* Bit 51 is incrememented each microsecond */
-	return (clk >> (63 - 51)) / 1000;
-}
-
-struct {
-	const char *name;
-	void (*func)(void);
-	bool run_it;
-} tests[] = {
-	{ "stpx", test_stpx, false },
-	{ "spx", test_spx, false },
-	{ "stap", test_stap, false },
-	{ "stidp", test_stidp, false },
-	{ "testblock", test_testblock, false },
-	{ NULL, NULL, false }
-};
-
-static void parse_intercept_test_args(int argc, char **argv)
-{
-	int i, ti;
-	bool run_all = true;
-
-	for (i = 1; i < argc; i++) {
-		if (!strcmp("-i", argv[i])) {
-			i++;
-			if (i >= argc)
-				report_abort("-i needs a parameter");
-			nr_iterations = atol(argv[i]);
-		} else if (!strcmp("-t", argv[i])) {
-			i++;
-			if (i >= argc)
-				report_abort("-t needs a parameter");
-			time_to_run = atol(argv[i]);
-		} else for (ti = 0; tests[ti].name != NULL; ti++) {
-			if (!strcmp(tests[ti].name, argv[i])) {
-				run_all = false;
-				tests[ti].run_it = true;
-				break;
-			} else if (tests[ti + 1].name == NULL) {
-				report_abort("Unsupported parameter '%s'",
-					     argv[i]);
-			}
-		}
-	}
-
-	if (run_all) {
-		for (ti = 0; tests[ti].name != NULL; ti++)
-			tests[ti].run_it = true;
+static struct test test = {
+	.name = "intercept",
+	.multirun_supported = true,
+	.test_cases = (struct test_case[]) {
+		DEFINE_TEST_CASE("stpx", test_stpx),
+		DEFINE_TEST_CASE("spx", test_spx),
+		DEFINE_TEST_CASE("stap", test_stap),
+		DEFINE_TEST_CASE("stidp", test_stidp),
+		DEFINE_TEST_CASE("testblock", test_testblock),
+		DEFINE_TEST_CASE_END_OF_LIST()
 	}
-}
+};
 
-int main(int argc, char **argv)
+int main(int argc, const char **argv)
 {
-	uint64_t startclk;
-	int ti;
-
-	parse_intercept_test_args(argc, argv);
-
-	if (nr_iterations == 0 && time_to_run == 0)
-		nr_iterations = 1;
-
-	report_prefix_push("intercept");
-
-	startclk = get_clock_ms();
-	for (;;) {
-		for (ti = 0; tests[ti].name != NULL; ti++) {
-			report_prefix_push(tests[ti].name);
-			if (tests[ti].run_it)
-				tests[ti].func();
-			report_prefix_pop();
-		}
-		if (nr_iterations) {
-			nr_iterations -= 1;
-			if (nr_iterations == 0)
-				break;
-		}
-		if (time_to_run) {
-			if (get_clock_ms() - startclk > time_to_run)
-				break;
-		}
-	}
-
-	report_prefix_pop();
-
-	return report_summary();
+	parse_test_args(argc, argv, &test);
+	return run_test(&test);
 }
