diff --git a/lib/s390x/asm/interrupt.h b/lib/s390x/asm/interrupt.h
index 383d312..926f858 100644
--- a/lib/s390x/asm/interrupt.h
+++ b/lib/s390x/asm/interrupt.h
@@ -14,5 +14,6 @@
 void handle_pgm_int(void);
 void expect_pgm_int(void);
 void check_pgm_int_code(uint16_t code);
+uint16_t get_pgm_int_code(void);
 
 #endif
diff --git a/lib/s390x/interrupt.c b/lib/s390x/interrupt.c
index 8d861a2..b5cc7ce 100644
--- a/lib/s390x/interrupt.c
+++ b/lib/s390x/interrupt.c
@@ -23,6 +23,11 @@ void expect_pgm_int(void)
 	mb();
 }
 
+uint16_t get_pgm_int_code(void)
+{
+	return lc->pgm_int_code;
+}
+
 void check_pgm_int_code(uint16_t code)
 {
 	mb();
diff --git a/s390x/Makefile b/s390x/Makefile
index b48f8ab..a61e163 100644
--- a/s390x/Makefile
+++ b/s390x/Makefile
@@ -1,4 +1,5 @@
 tests = $(TEST_DIR)/selftest.elf
+tests += $(TEST_DIR)/intercept.elf
 
 all: directories test_cases
 
diff --git a/s390x/intercept.c b/s390x/intercept.c
new file mode 100644
index 0000000..4e3fb57
--- /dev/null
+++ b/s390x/intercept.c
@@ -0,0 +1,170 @@
+/*
+ * Interception tests - for s390x CPU instruction that cause a VM exit
+ *
+ * Copyright (c) 2017 Red Hat Inc
+ *
+ * Authors:
+ *  Thomas Huth <thuth@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 <libcflat.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)));
+
+/* Enable or disable low-address protection */
+static void set_low_prot(bool enable)
+{
+	uint64_t cr0;
+
+	asm volatile (" stctg 0,0,%0 " : : "Q"(cr0));
+	if (enable)
+		cr0 |= 1ULL << (63-35);
+	else
+		cr0 &= ~(1ULL << (63-35));
+	asm volatile (" lctlg 0,0,%0 " : : "Q"(cr0));
+}
+
+/* Test the SET PREFIX and STORE PREFIX instructions */
+static void test_prefix(void)
+{
+	uint32_t old_prefix = -1U, tst_prefix = -1U;
+	uint32_t new_prefix = (uint32_t)(intptr_t)pagebuf;
+
+	memset(pagebuf, 0, PAGE_SIZE * 2);
+
+	/*
+	 * Temporarily change the prefix page to our buffer, and store
+	 * some facility bits there ... at least some of them should be
+	 * set in our buffer afterwards.
+	 */
+	asm volatile (
+		" stpx	%0\n"
+		" spx	%2\n"
+		" stfl	0\n"
+		" stpx	%1\n"
+		" spx	%0\n"
+		: "+Q"(old_prefix), "+Q"(tst_prefix)
+		: "Q"(new_prefix)
+		: "memory");
+	report("spx + stfl", pagebuf[GEN_LC_STFL] != 0 &&
+			     old_prefix == 0 && tst_prefix == new_prefix);
+
+	expect_pgm_int();
+	asm volatile(" spx 0(%0) " : : "r"(1));
+	report("spx alignment",
+	       get_pgm_int_code() == PGM_INT_CODE_SPECIFICATION);
+
+	expect_pgm_int();
+	asm volatile(" spx 0(%0) " : : "r"(-8));
+	report("spx addressing",
+	       get_pgm_int_code() == PGM_INT_CODE_ADDRESSING);
+
+	expect_pgm_int();
+	set_low_prot(true);
+	asm volatile(" stpx 0(%0) " : : "r"(8));
+	set_low_prot(false);
+	report("stpx low-address protection",
+	       get_pgm_int_code() == PGM_INT_CODE_PROTECTION);
+
+	expect_pgm_int();
+	asm volatile(" stpx 0(%0) " : : "r"(1));
+	report("stpx alignment",
+	       get_pgm_int_code() == PGM_INT_CODE_SPECIFICATION);
+
+	expect_pgm_int();
+	asm volatile(" stpx 0(%0) " : : "r"(-8));
+	report("stpx addressing",
+	       get_pgm_int_code() == PGM_INT_CODE_ADDRESSING);
+}
+
+/* Test the STORE CPU ADDRESS instruction */
+static void test_stap(void)
+{
+	uint16_t cpuid = 0xffff;
+
+	asm volatile ("stap %0\n" : "+Q"(cpuid));
+	report("get cpu id", cpuid != 0xffff);
+
+	expect_pgm_int();
+	set_low_prot(true);
+	asm volatile ("stap 0(%0)\n" : : "r"(8));
+	set_low_prot(false);
+	report("low-address protection",
+	       get_pgm_int_code() == PGM_INT_CODE_PROTECTION);
+
+	expect_pgm_int();
+	asm volatile ("stap 0(%0)\n" : : "r"(1));
+	report("alignment", get_pgm_int_code() == PGM_INT_CODE_SPECIFICATION);
+
+	expect_pgm_int();
+	asm volatile ("stap 0(%0)\n" : : "r"(-8));
+	report("addressing", get_pgm_int_code() == PGM_INT_CODE_ADDRESSING);
+}
+
+/* Test the TEST BLOCK instruction */
+static void test_testblock(void)
+{
+	int cc;
+
+	memset(pagebuf, 0xaa, PAGE_SIZE);
+
+	asm volatile (
+		" lghi	0,0\n"
+		" tb	%1\n"
+		" ipm	%0\n"
+		" srl	%0,28\n"
+		: "=d" (cc)
+		: "a"(pagebuf + 0x123)
+		: "memory", "0", "cc");
+	report("page cleared",
+	       cc == 0 && pagebuf[0] == 0 &&  pagebuf[PAGE_SIZE - 1] == 0);
+
+	expect_pgm_int();
+	set_low_prot(true);
+	asm volatile (" tb %0 " : : "r"(4096));
+	set_low_prot(false);
+	report("low-address protection",
+	       get_pgm_int_code() == PGM_INT_CODE_PROTECTION);
+
+	expect_pgm_int();
+	asm volatile (" tb %0 " : : "r"(-4096));
+	report("addressing", get_pgm_int_code() == PGM_INT_CODE_ADDRESSING);
+}
+
+struct {
+	const char *name;
+	void (*func)(void);
+} tests[] = {
+	{ "prefix", test_prefix },
+	{ "stap", test_stap },
+	{ "testblock", test_testblock },
+	{ NULL, NULL }
+};
+
+int main(int argc, char **argv)
+{
+	int all = 0;
+	int i;
+
+	report_prefix_push("intercept");
+
+	if (argc < 2 || (argc == 2 && !strcmp(argv[1], "all")))
+		all = 1;
+
+	for (i = 0; tests[i].name != NULL; i++) {
+		report_prefix_push(tests[i].name);
+		if (all || strcmp(argv[1], tests[i].name) == 0) {
+			tests[i].func();
+		}
+		report_prefix_pop();
+	}
+
+	report_prefix_pop();
+
+	return report_summary();
+}
diff --git a/s390x/unittests.cfg b/s390x/unittests.cfg
index 92e01ab..3b6b892 100644
--- a/s390x/unittests.cfg
+++ b/s390x/unittests.cfg
@@ -22,3 +22,6 @@
 file = selftest.elf
 groups = selftest
 extra_params = -append 'test 123'
+
+[intercept]
+file = intercept.elf
