@@ -20,6 +20,7 @@ tests += $(TEST_DIR)/sclp.elf
tests += $(TEST_DIR)/css.elf
tests += $(TEST_DIR)/uv-guest.elf
tests += $(TEST_DIR)/sie.elf
+tests += $(TEST_DIR)/mvpg.elf
tests_binary = $(patsubst %.elf,%.bin,$(tests))
ifneq ($(HOST_KEY_DOCUMENT),)
new file mode 100644
@@ -0,0 +1,266 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Move Page instruction tests
+ *
+ * Copyright (c) 2021 IBM Corp
+ *
+ * Authors:
+ * Claudio Imbrenda <imbrenda@linux.ibm.com>
+ */
+#include <libcflat.h>
+#include <asm/asm-offsets.h>
+#include <asm-generic/barrier.h>
+#include <asm/interrupt.h>
+#include <asm/pgtable.h>
+#include <mmu.h>
+#include <asm/page.h>
+#include <asm/facility.h>
+#include <asm/mem.h>
+#include <asm/sigp.h>
+#include <smp.h>
+#include <alloc_page.h>
+#include <bitops.h>
+
+/* Used to build the appropriate test values for register 0 */
+#define KFC(x) ((x) << 10)
+#define CCO 0x100
+
+/* How much memory to allocate for the test */
+#define MEM_ORDER 12
+/* How many iterations to perform in the loops */
+#define ITER 8
+
+/* Used to generate the simple pattern */
+#define MAGIC 42
+
+static uint8_t source[PAGE_SIZE] __attribute__((aligned(PAGE_SIZE)));
+static uint8_t buffer[PAGE_SIZE] __attribute__((aligned(PAGE_SIZE)));
+
+/* Keep track of fresh memory */
+static uint8_t *fresh;
+
+static inline int mvpg(unsigned long r0, void *dest, void *src)
+{
+ register unsigned long reg0 asm ("0") = r0;
+ int cc;
+
+ asm volatile(" mvpg %1,%2\n"
+ " ipm %0\n"
+ " srl %0,28"
+ : "=&d" (cc) : "a" (dest), "a" (src), "d" (reg0)
+ : "memory", "cc");
+ return cc;
+}
+
+/*
+ * Initialize a page with a simple pattern
+ */
+static void init_page(uint8_t *p)
+{
+ int i;
+
+ for (i = 0; i < PAGE_SIZE; i++)
+ p[i] = i + MAGIC;
+}
+
+/*
+ * Check if the given page contains the simple pattern
+ */
+static int page_ok(const uint8_t *p)
+{
+ int i;
+
+ for (i = 0; i < PAGE_SIZE; i++)
+ if (p[i] != (uint8_t)(i + MAGIC))
+ return 0;
+ return 1;
+}
+
+static void test_exceptions(void)
+{
+ int i, expected;
+
+ report_prefix_push("exceptions");
+
+ /*
+ * Key Function Control values 4 and 5 are allowed only in supervisor
+ * state, and even then, only if the move-page-and-set-key facility
+ * is present (STFLE bit 149)
+ */
+ report_prefix_push("privileged");
+ if (test_facility(149)) {
+ expected = PGM_INT_CODE_PRIVILEGED_OPERATION;
+ for (i = 4; i < 6; i++) {
+ expect_pgm_int();
+ enter_pstate();
+ mvpg(KFC(i), buffer, source);
+ report(clear_pgm_int() == expected, "Key Function Control value %d", i);
+ }
+ } else {
+ report_skip("Key Function Control value %d", 4);
+ report_skip("Key Function Control value %d", 5);
+ i = 4;
+ }
+ report_prefix_pop();
+
+ /*
+ * Invalid values of the Key Function Control, or setting the
+ * reserved bits, should result in a specification exception
+ */
+ report_prefix_push("specification");
+ expected = PGM_INT_CODE_SPECIFICATION;
+ expect_pgm_int();
+ mvpg(KFC(3), buffer, source);
+ report(clear_pgm_int() == expected, "Key Function Control value 3");
+ for (; i < 32; i++) {
+ expect_pgm_int();
+ mvpg(KFC(i), buffer, source);
+ report(clear_pgm_int() == expected, "Key Function Control value %d", i);
+ }
+ report_prefix_pop();
+
+ /* Operands outside memory result in addressing exceptions, as usual */
+ report_prefix_push("addressing");
+ expected = PGM_INT_CODE_ADDRESSING;
+ expect_pgm_int();
+ mvpg(0, buffer, (void *)PAGE_MASK);
+ report(clear_pgm_int() == expected, "Second operand outside memory");
+
+ expect_pgm_int();
+ mvpg(0, (void *)PAGE_MASK, source);
+ report(clear_pgm_int() == expected, "First operand outside memory");
+ report_prefix_pop();
+
+ report_prefix_pop();
+}
+
+static void test_success(void)
+{
+ int cc;
+
+ report_prefix_push("success");
+ /* Test successful scenarios, both in supervisor and problem state */
+ cc = mvpg(0, buffer, source);
+ report(page_ok(buffer) && !cc, "Supervisor state MVPG successful");
+
+ enter_pstate();
+ cc = mvpg(0, buffer, source);
+ leave_pstate();
+ report(page_ok(buffer) && !cc, "Problem state MVPG successful");
+
+ report_prefix_pop();
+}
+
+static void test_small_loop(const void *string)
+{
+ uint8_t *dest;
+ int i, cc;
+
+ /* Looping over cold and warm pages helps catch VSIE bugs */
+ report_prefix_push(string);
+ dest = fresh;
+ for (i = 0; i < ITER; i++) {
+ cc = mvpg(0, fresh, source);
+ report(page_ok(fresh) && !cc, "cold: %p, %p", source, fresh);
+ fresh += PAGE_SIZE;
+ }
+
+ for (i = 0; i < ITER; i++) {
+ memset(dest, 0, PAGE_SIZE);
+ cc = mvpg(0, dest, source);
+ report(page_ok(dest) && !cc, "warm: %p, %p", source, dest);
+ dest += PAGE_SIZE;
+ }
+ report_prefix_pop();
+}
+
+static void test_mmu_prot(void)
+{
+ int cc;
+
+ report_prefix_push("protection");
+ report_prefix_push("cco=0");
+
+ /* MVPG should still succeed when the source is read-only */
+ protect_page(source, PAGE_ENTRY_P);
+ cc = mvpg(0, fresh, source);
+ report(page_ok(fresh) && !cc, "source read only");
+ unprotect_page(source, PAGE_ENTRY_P);
+ fresh += PAGE_SIZE;
+
+ /*
+ * When the source or destination are invalid, a page translation
+ * exception should be raised; when the destination is read-only,
+ * a protection exception should be raised.
+ */
+ protect_page(fresh, PAGE_ENTRY_P);
+ expect_pgm_int();
+ mvpg(0, fresh, source);
+ report(clear_pgm_int() == PGM_INT_CODE_PROTECTION, "destination read only");
+ fresh += PAGE_SIZE;
+
+ protect_page(source, PAGE_ENTRY_I);
+ expect_pgm_int();
+ mvpg(0, fresh, source);
+ report(clear_pgm_int() == PGM_INT_CODE_PAGE_TRANSLATION, "source invalid");
+ unprotect_page(source, PAGE_ENTRY_I);
+ fresh += PAGE_SIZE;
+
+ protect_page(fresh, PAGE_ENTRY_I);
+ expect_pgm_int();
+ mvpg(0, fresh, source);
+ report(clear_pgm_int() == PGM_INT_CODE_PAGE_TRANSLATION, "destination invalid");
+ fresh += PAGE_SIZE;
+
+ report_prefix_pop();
+ report_prefix_push("cco=1");
+ /*
+ * Setting the CCO bit should suppress page translation exceptions,
+ * but not protection exceptions.
+ */
+ protect_page(fresh, PAGE_ENTRY_P);
+ expect_pgm_int();
+ mvpg(CCO, fresh, source);
+ report(clear_pgm_int() == PGM_INT_CODE_PROTECTION, "destination read only");
+ fresh += PAGE_SIZE;
+
+ protect_page(fresh, PAGE_ENTRY_I);
+ cc = mvpg(CCO, fresh, source);
+ report(cc == 1, "destination invalid");
+ fresh += PAGE_SIZE;
+
+ protect_page(source, PAGE_ENTRY_I);
+ cc = mvpg(CCO, fresh, source);
+ report(cc == 2, "source invalid");
+ fresh += PAGE_SIZE;
+
+ protect_page(fresh, PAGE_ENTRY_I);
+ cc = mvpg(CCO, fresh, source);
+ report(cc == 2, "source and destination invalid");
+ fresh += PAGE_SIZE;
+
+ unprotect_page(source, PAGE_ENTRY_I);
+ report_prefix_pop();
+ report_prefix_pop();
+}
+
+int main(void)
+{
+ report_prefix_push("mvpg");
+
+ init_page(source);
+ fresh = alloc_pages_flags(MEM_ORDER, FLAG_DONTZERO | FLAG_FRESH);
+ assert(fresh);
+
+ test_exceptions();
+ test_success();
+ test_small_loop("nommu");
+
+ setup_vm();
+
+ test_small_loop("mmu");
+ test_mmu_prot();
+
+ report_prefix_pop();
+ return report_summary();
+}
@@ -99,3 +99,7 @@ file = uv-guest.elf
[sie]
file = sie.elf
+
+[mvpg]
+file = mvpg.elf
+timeout = 10