diff --git a/lib/s390x/asm/mem.h b/lib/s390x/asm/mem.h
new file mode 100644
index 0000000..909e6d4
--- /dev/null
+++ b/lib/s390x/asm/mem.h
@@ -0,0 +1,52 @@
+/*
+ * Physical memory management related functions and definitions.
+ *
+ * Copyright IBM Corp. 2018
+ * Author(s): Janosch Frank <frankja@de.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 _ASM_S390_MEM_H
+#define _ASM_S390_MEM_H
+
+union skey {
+	struct {
+		uint8_t acc : 4;
+		uint8_t fp : 1;
+		uint8_t rf : 1;
+		uint8_t ch : 1;
+		uint8_t pad : 1;
+	} str;
+	uint8_t val;
+};
+
+static inline void set_storage_key(unsigned long addr,
+				   unsigned char skey,
+				   int nq)
+{
+	if (nq)
+		asm volatile(".insn rrf,0xb22b0000,%0,%1,8,0"
+			     : : "d" (skey), "a" (addr));
+	else
+		asm volatile("sske %0,%1" : : "d" (skey), "a" (addr));
+}
+
+static inline unsigned long set_storage_key_mb(unsigned long addr,
+					       unsigned char skey)
+{
+	assert(test_facility(8));
+
+	asm volatile(".insn rrf,0xb22b0000,%[skey],%[addr],1,0"
+		     : [addr] "+a" (addr) : [skey] "d" (skey));
+	return addr;
+}
+
+static inline unsigned char get_storage_key(unsigned long addr)
+{
+	unsigned char skey;
+
+	asm volatile("iske %0,%1" : "=d" (skey) : "a" (addr));
+	return skey;
+}
+#endif
diff --git a/s390x/Makefile b/s390x/Makefile
index f68d8a7..c860ed8 100644
--- a/s390x/Makefile
+++ b/s390x/Makefile
@@ -3,6 +3,7 @@ tests += $(TEST_DIR)/intercept.elf
 tests += $(TEST_DIR)/emulator.elf
 tests += $(TEST_DIR)/sieve.elf
 tests += $(TEST_DIR)/sthyi.elf
+tests += $(TEST_DIR)/skey.elf
 
 all: directories test_cases
 
diff --git a/s390x/skey.c b/s390x/skey.c
new file mode 100644
index 0000000..e4c2a43
--- /dev/null
+++ b/s390x/skey.c
@@ -0,0 +1,94 @@
+/*
+ * Storage key tests
+ *
+ * Copyright (c) 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 <asm/mem.h>
+
+
+static uint8_t pagebuf[PAGE_SIZE * 2] __attribute__((aligned(PAGE_SIZE * 2)));
+const unsigned long page0 = (unsigned long)pagebuf;
+const unsigned long page1 = (unsigned long)(pagebuf + PAGE_SIZE);
+
+static void test_set_mb(void)
+{
+	union skey skey, ret1, ret2;
+	unsigned long addr = 0x10000 - 2 * PAGE_SIZE;
+	unsigned long end = 0x10000;
+
+	/* Multi block support came with EDAT 1 */
+	if (!test_facility(8))
+		return;
+
+	skey.val = 0x30;
+	while (addr < end)
+		addr = set_storage_key_mb(addr, skey.val);
+
+	ret1.val = get_storage_key(end - PAGE_SIZE);
+	ret2.val = get_storage_key(end - PAGE_SIZE * 2);
+	report("multi block", ret1.val == ret2.val && ret1.val == skey.val);
+}
+
+static void test_chg(void)
+{
+	union skey skey1, skey2;
+
+	skey1.val = 0x30;
+	set_storage_key(page0, skey1.val, 0);
+	skey1.val = get_storage_key(page0);
+	pagebuf[0] = 3;
+	skey2.val = get_storage_key(page0);
+	report("chg bit test", !skey1.str.ch && skey2.str.ch);
+}
+
+static void test_set(void)
+{
+	union skey skey, ret;
+
+	skey.val = 0x30;
+	ret.val = get_storage_key(page0);
+	set_storage_key(page0, skey.val, 0);
+	ret.val = get_storage_key(page0);
+	report("set key test", skey.val == ret.val);
+}
+
+static void test_priv(void)
+{
+	union skey skey;
+
+	memset(pagebuf, 0, PAGE_SIZE * 2);
+	expect_pgm_int();
+	enter_pstate();
+	set_storage_key(page0, 0x30, 0);
+	check_pgm_int_code(PGM_INT_CODE_PRIVILEGED_OPERATION);
+
+	skey.val = get_storage_key(page0);
+	report("skey did not change on exception", skey.str.acc != 3);
+
+	expect_pgm_int();
+	enter_pstate();
+	get_storage_key(page0);
+	check_pgm_int_code(PGM_INT_CODE_PRIVILEGED_OPERATION);
+}
+
+int main(void)
+{
+	report_prefix_push("skey");
+	test_priv();
+	test_set();
+	test_set_mb();
+	test_chg();
+	report_prefix_pop();
+	return report_summary();
+}
diff --git a/s390x/unittests.cfg b/s390x/unittests.cfg
index 258189e..c98cddd 100644
--- a/s390x/unittests.cfg
+++ b/s390x/unittests.cfg
@@ -37,3 +37,6 @@ timeout = 600
 
 [sthyi]
 file = sthyi.elf
+
+[skey]
+file = skey.elf
