diff --git a/s390x/Makefile b/s390x/Makefile
index fe92f65..f68d8a7 100644
--- a/s390x/Makefile
+++ b/s390x/Makefile
@@ -2,6 +2,7 @@ tests = $(TEST_DIR)/selftest.elf
 tests += $(TEST_DIR)/intercept.elf
 tests += $(TEST_DIR)/emulator.elf
 tests += $(TEST_DIR)/sieve.elf
+tests += $(TEST_DIR)/sthyi.elf
 
 all: directories test_cases
 
diff --git a/s390x/sthyi.c b/s390x/sthyi.c
new file mode 100644
index 0000000..0b1b4ec
--- /dev/null
+++ b/s390x/sthyi.c
@@ -0,0 +1,169 @@
+/*
+ * Tests exceptions and data validity for the emulated sthyi
+ * instruction.
+ *
+ * Copyright 2018 IBM Corp.
+ *
+ * Authors:
+ *    Janosch Frank <frankja@linux.vnet.ibm.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>
+#include <asm/facility.h>
+
+#include "sthyi.h"
+
+static uint8_t pagebuf[PAGE_SIZE] __attribute__((aligned(PAGE_SIZE)));
+static char null_buf[32] = {};
+
+static inline int sthyi(uint64_t vaddr, uint64_t fcode, uint64_t *rc,
+			unsigned int r1, unsigned int r2)
+{
+	register uint64_t code asm("0") = fcode;
+	register uint64_t addr asm("2") = vaddr;
+	register uint64_t rc3 asm("3") = 0;
+	int cc = 0;
+
+	asm volatile(".insn rre,0xB2560000,%[r1],%[r2]\n"
+		     "ipm	 %[cc]\n"
+		     "srl	 %[cc],28\n"
+		     : [cc] "=d" (cc)
+		     : [code] "d" (code), [addr] "a" (addr), [r1] "i" (r1),
+		       [r2] "i" (r2)
+		     : "memory", "cc", "r3");
+	if (rc)
+		*rc = rc3;
+	return cc;
+}
+
+static void test_exception_addr(void)
+{
+	report_prefix_push("Illegal address check");
+	expect_pgm_int();
+	sthyi(42042, 0, NULL, 0, 2);
+	check_pgm_int_code(PGM_INT_CODE_SPECIFICATION);
+	report_prefix_pop();
+}
+
+static void test_exception_reg_odd(void)
+{
+	report_prefix_push("Register check odd");
+	expect_pgm_int();
+	sthyi((uint64_t)pagebuf, 0, NULL, 1, 2);
+	check_pgm_int_code(PGM_INT_CODE_SPECIFICATION);
+	expect_pgm_int();
+	sthyi((uint64_t)pagebuf, 0, NULL, 0, 3);
+	check_pgm_int_code(PGM_INT_CODE_SPECIFICATION);
+	report_prefix_pop();
+}
+
+static void test_exception_reg_equal(void)
+{
+	report_prefix_push("Register check equal");
+	expect_pgm_int();
+	sthyi((uint64_t)pagebuf, 0, NULL, 0, 0);
+	check_pgm_int_code(PGM_INT_CODE_SPECIFICATION);
+	report_prefix_pop();
+}
+
+static void test_function_code(uint64_t addr)
+{
+	uint64_t urc = 0;
+	int cc = sthyi((uint64_t)pagebuf, 42, &urc, 0, 2);
+
+	report("Ill. fcode", cc == 3 && urc == CODE_UNSUPP);
+}
+
+static void test_fcode0_hdr(struct sthyi_hdr_sctn *hdr)
+{
+	report("HDR length", (hdr->INFHDLN >= sizeof(*hdr)
+			      && !(hdr->INFHDLN % 8)));
+	report("MACH sctn length", (hdr->INFMLEN >= sizeof(struct sthyi_mach_sctn)
+				    && !(hdr->INFMLEN % 8)));
+	report("PAR sctn length", (hdr->INFPLEN >= sizeof(struct sthyi_par_sctn)
+				   && !(hdr->INFPLEN % 8)));
+
+	report("MACH offset", hdr->INFMOFF >= hdr->INFHDLN);
+	report("PAR offset", hdr->INFPOFF >= hdr->INFHDLN);
+}
+
+static void test_fcode0_mach(struct sthyi_mach_sctn *mach)
+{
+	int sum = mach->INFMSCPS + mach->INFMDCPS + mach->INFMSIFL + mach->INFMDIFL;
+
+	if (mach->INFMVAL1 & MACH_ID_VLD) {
+		report("MACH type", memcmp(mach->INFMTYPE, null_buf, sizeof(mach->INFMTYPE)));
+		report("MACH manu", memcmp(mach->INFMMANU, null_buf, sizeof(mach->INFMMANU)));
+		report("MACH seq", memcmp(mach->INFMSEQ, null_buf, sizeof(mach->INFMSEQ)));
+		report("MACH plant", memcmp(mach->INFMPMAN, null_buf, sizeof(mach->INFMPMAN)));
+	}
+
+	if (mach->INFMVAL1 & MACH_NAME_VLD)
+		report("MACH name", memcmp(mach->INFMNAME, null_buf,
+					   sizeof(mach->INFMNAME)));
+
+	if (mach->INFMVAL1 & MACH_CNT_VLD)
+		report("MACH core counts", sum);
+}
+
+static void test_fcode0_par(struct sthyi_par_sctn *par)
+{
+	int sum = par->INFPSCPS + par->INFPDCPS + par->INFPSIFL + par->INFPDIFL;
+
+	if (par->INFPVAL1 & PART_CNT_VLD)
+		report("PAR core counts", sum);
+
+	if (par->INFPVAL1 & PART_STSI_SUC) {
+		report("PAR number", par->INFPPNUM);
+		report("PAR name", memcmp(par->INFPPNAM, null_buf, sizeof(par->INFPPNAM)));
+	}
+}
+
+static void test_fcode0(void)
+{
+	struct sthyi_hdr_sctn *hdr;
+	struct sthyi_mach_sctn *mach;
+	struct sthyi_par_sctn *par;
+
+	/* Zero destination memory. */
+	memset(pagebuf, 0, PAGE_SIZE);
+
+	sthyi((uint64_t)pagebuf, 0, NULL, 0, 2);
+	hdr = (void *)pagebuf;
+	mach = (void *)pagebuf + hdr->INFMOFF;
+	par = (void *)pagebuf + hdr->INFPOFF;
+
+	test_fcode0_hdr(hdr);
+	test_fcode0_mach(mach);
+	test_fcode0_par(par);
+}
+
+int main(void)
+{
+	bool has_sthyi = test_facility(74);
+
+	report_prefix_push("sthyi");
+
+	/* Test for availability */
+	report_xfail("STHYI available", !has_sthyi, has_sthyi);
+	if (!has_sthyi)
+		goto done;
+
+	/* Test register/argument checking. */
+	test_exception_addr();
+	test_exception_reg_odd();
+	test_exception_reg_equal();
+	test_function_code((uint64_t) pagebuf);
+
+	/* Test function code 0 - CP and IFL Capacity Information */
+	test_fcode0();
+
+done:
+	report_prefix_pop();
+	return report_summary();
+}
diff --git a/s390x/sthyi.h b/s390x/sthyi.h
new file mode 100644
index 0000000..06f757f
--- /dev/null
+++ b/s390x/sthyi.h
@@ -0,0 +1,131 @@
+/*
+ * STHYI related flags and structure definitions.
+ *
+ * Copyright 2018 IBM Corp.
+ *
+ * Authors:
+ *    Janosch Frank <frankja@linux.vnet.ibm.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 _STHYI_H_
+#define _STHYI_H_
+
+#include <stdint.h>
+
+enum sthyi_rtn_code {
+	CODE_UNSUPP = 0x04, /* with cc = 3 */
+	CODE_SUCCES = 0x00, /* with cc = 0 */
+};
+
+enum sthyi_hdr_flags {
+	HDR_PERF_UNAV  = 0x80,
+	HDR_STSI_UNAV  = 0x40,
+	HDR_STACK_INCM = 0x20,
+	HDR_NOT_LPAR   = 0x10,
+};
+
+enum sthyi_mach_validity {
+	MACH_CNT_VLD  = 0x80,
+	MACH_ID_VLD   = 0x40,
+	MACH_NAME_VLD = 0x20,
+};
+
+enum sthyi_par_flag {
+	PART_MT_EN = 0x80,
+};
+
+enum sthyi_par_validity {
+	PART_CNT_VLD  = 0x80,
+	PART_WGHT_CAP = 0x40,
+	PART_ABS_CAP  = 0x20,
+	PART_STSI_SUC = 0x10,
+	PART_GRP_VLD  = 0x08,
+};
+
+struct sthyi_hdr_sctn {
+	uint8_t INFHFLG1;
+	uint8_t INFHFLG2; /* reserved */
+	uint8_t INFHVAL1; /* reserved */
+	uint8_t INFHVAL2; /* reserved */
+	uint8_t reserved[3];
+	uint8_t INFHYGCT;
+	uint16_t INFHTOTL;
+	uint16_t INFHDLN;
+	uint16_t INFMOFF;
+	uint16_t INFMLEN;
+	uint16_t INFPOFF;
+	uint16_t INFPLEN;
+	uint16_t INFHOFF1;
+	uint16_t INFHLEN1;
+	uint16_t INFGOFF1;
+	uint16_t INFGLEN1;
+	uint16_t INFHOFF2;
+	uint16_t INFHLEN2;
+	uint16_t INFGOFF2;
+	uint16_t INFGLEN2;
+	uint16_t INFHOFF3;
+	uint16_t INFHLEN3;
+	uint16_t INFGOFF3;
+	uint16_t INFGLEN3;
+	uint8_t reserved2[4];
+} __attribute__((packed));
+
+struct sthyi_mach_sctn {
+	uint8_t INFMFLG1; /* reserved */
+	uint8_t INFMFLG2; /* reserved */
+	uint8_t INFMVAL1;
+	uint8_t INFMVAL2; /* reserved */
+	uint16_t INFMSCPS;
+	uint16_t INFMDCPS;
+	uint16_t INFMSIFL;
+	uint16_t INFMDIFL;
+	char INFMNAME[8];
+	char INFMTYPE[4];
+	char INFMMANU[16];
+	char INFMSEQ[16];
+	char INFMPMAN[4];
+	uint8_t reserved[4];
+} __attribute__((packed));
+
+struct sthyi_par_sctn {
+	uint8_t INFPFLG1;
+	uint8_t INFPFLG2; /* reserved */
+	uint8_t INFPVAL1;
+	uint8_t INFPVAL2; /* reserved */
+	uint16_t INFPPNUM;
+	uint16_t INFPSCPS;
+	uint16_t INFPDCPS;
+	uint16_t INFPSIFL;
+	uint16_t INFPDIFL;
+	uint16_t reserved;
+	char INFPPNAM[8];
+	uint32_t INFPWBCP;
+	uint32_t INFPABCP;
+	uint32_t INFPWBIF;
+	uint32_t INFPABIF;
+} __attribute__((packed));
+
+struct sthyi_par_sctn_ext {
+	uint8_t INFPFLG1;
+	uint8_t INFPFLG2; /* reserved */
+	uint8_t INFPVAL1;
+	uint8_t INFPVAL2; /* reserved */
+	uint16_t INFPPNUM;
+	uint16_t INFPSCPS;
+	uint16_t INFPDCPS;
+	uint16_t INFPSIFL;
+	uint16_t INFPDIFL;
+	uint16_t reserved;
+	char INFPPNAM[8];
+	uint32_t INFPWBCP;
+	uint32_t INFPABCP;
+	uint32_t INFPWBIF;
+	uint32_t INFPABIF;
+	char INFPLGNM[8];
+	uint32_t INFPLGCP;
+	uint32_t INFPLGIF;
+} __attribute__((packed));
+
+#endif
diff --git a/s390x/unittests.cfg b/s390x/unittests.cfg
index 7506d06..258189e 100644
--- a/s390x/unittests.cfg
+++ b/s390x/unittests.cfg
@@ -34,3 +34,6 @@ file = sieve.elf
 groups = selftest
 # can take fairly long when KVM is nested inside z/VM
 timeout = 600
+
+[sthyi]
+file = sthyi.elf
