selftests/powerpc/64: Test exception cases in copy_tofrom_user
authorMichael Ellerman <mpe@ellerman.id.au>
Fri, 3 Aug 2018 10:13:05 +0000 (20:13 +1000)
committerMichael Ellerman <mpe@ellerman.id.au>
Tue, 7 Aug 2018 14:32:35 +0000 (00:32 +1000)
This adds a set of test cases to test the behaviour of
copy_tofrom_user when exceptions are encountered accessing the
source or destination.  Currently, copy_tofrom_user does not always
copy as many bytes as possible when an exception occurs on a store
to the destination, and that is reflected in failures in these tests.

Based on a test program from Anton Blanchard.

[paulus@ozlabs.org - test all three paths, wrote commit description,
 made EX_TABLE create an exception table.]

Signed-off-by: Paul Mackerras <paulus@ozlabs.org>
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
tools/testing/selftests/powerpc/copyloops/.gitignore
tools/testing/selftests/powerpc/copyloops/Makefile
tools/testing/selftests/powerpc/copyloops/asm/ppc_asm.h
tools/testing/selftests/powerpc/copyloops/copy_tofrom_user_reference.S [new file with mode: 0644]
tools/testing/selftests/powerpc/copyloops/exc_validate.c [new file with mode: 0644]
tools/testing/selftests/powerpc/copyloops/stubs.S [new file with mode: 0644]

index bc6c4ba153af9c712a72e6c1df47158f83561478..ce12cd0e2967adaaeb763709550d82bb0f576887 100644 (file)
@@ -8,3 +8,6 @@ memcpy_64_t1
 memcpy_64_t2
 memcpy_power7_t0
 memcpy_power7_t1
+copyuser_64_exc_t0
+copyuser_64_exc_t1
+copyuser_64_exc_t2
index 498d8d875ab86ab445a9aff6d95b119eee253539..1cf89a34d97ca35299bf62b588b07d61ecbbb8a8 100644 (file)
@@ -12,9 +12,10 @@ ASFLAGS = $(CFLAGS) -Wa,-mpower4
 TEST_GEN_PROGS := copyuser_64_t0 copyuser_64_t1 copyuser_64_t2 \
                copyuser_p7_t0 copyuser_p7_t1 \
                memcpy_64_t0 memcpy_64_t1 memcpy_64_t2 \
-               memcpy_p7_t0 memcpy_p7_t1
+               memcpy_p7_t0 memcpy_p7_t1 \
+               copyuser_64_exc_t0 copyuser_64_exc_t1 copyuser_64_exc_t2
 
-EXTRA_SOURCES := validate.c ../harness.c
+EXTRA_SOURCES := validate.c ../harness.c stubs.S
 
 include ../../lib.mk
 
@@ -42,3 +43,10 @@ $(OUTPUT)/memcpy_p7_t%:      memcpy_power7.S $(EXTRA_SOURCES)
                -D COPY_LOOP=test_memcpy_power7 \
                -D SELFTEST_CASE=$(subst memcpy_p7_t,,$(notdir $@)) \
                -o $@ $^
+
+$(OUTPUT)/copyuser_64_exc_t%: copyuser_64.S exc_validate.c ../harness.c \
+               copy_tofrom_user_reference.S stubs.S
+       $(CC) $(CPPFLAGS) $(CFLAGS) \
+               -D COPY_LOOP=test___copy_tofrom_user_base \
+               -D SELFTEST_CASE=$(subst copyuser_64_exc_t,,$(notdir $@)) \
+               -o $@ $^
index 91bc40392eb3a0bd5a2685cc5fe2fc16f867388e..0605df80759329b6865ffc1382fa13f18933db65 100644 (file)
@@ -1,4 +1,6 @@
 /* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __SELFTESTS_POWERPC_PPC_ASM_H
+#define __SELFTESTS_POWERPC_PPC_ASM_H
 #include <ppc-asm.h>
 
 #define CONFIG_ALTIVEC
 
 #define PPC_MTOCRF(A, B)       mtocrf A, B
 
-#define EX_TABLE(x, y)
-
-FUNC_START(enter_vmx_usercopy)
-       li      r3,1
-       blr
-
-FUNC_START(exit_vmx_usercopy)
-       li      r3,0
-       blr
-
-FUNC_START(enter_vmx_ops)
-       li      r3,1
-       blr
-
-FUNC_START(exit_vmx_ops)
-       blr
-
-FUNC_START(__copy_tofrom_user_base)
-       blr
+#define EX_TABLE(x, y)                 \
+       .section __ex_table,"a";        \
+       .8byte  x, y;                   \
+       .previous
 
 #define BEGIN_FTR_SECTION              .if test_feature
 #define FTR_SECTION_ELSE               .else
@@ -56,3 +43,5 @@ FUNC_START(__copy_tofrom_user_base)
 
 /* Default to taking the first of any alternative feature sections */
 test_feature = 1
+
+#endif /* __SELFTESTS_POWERPC_PPC_ASM_H */
diff --git a/tools/testing/selftests/powerpc/copyloops/copy_tofrom_user_reference.S b/tools/testing/selftests/powerpc/copyloops/copy_tofrom_user_reference.S
new file mode 100644 (file)
index 0000000..3363b86
--- /dev/null
@@ -0,0 +1,24 @@
+#include <asm/ppc_asm.h>
+
+_GLOBAL(copy_tofrom_user_reference)
+       cmpdi   r5,0
+       beq     4f
+
+       mtctr   r5
+
+1:     lbz     r6,0(r4)
+2:     stb     r6,0(r3)
+       addi    r3,r3,1
+       addi    r4,r4,1
+       bdnz    1b
+
+3:     mfctr   r3
+       blr
+
+4:     mr      r3,r5
+       blr
+
+.section __ex_table,"a"
+       .llong  1b,3b
+       .llong  2b,3b
+.text
diff --git a/tools/testing/selftests/powerpc/copyloops/exc_validate.c b/tools/testing/selftests/powerpc/copyloops/exc_validate.c
new file mode 100644 (file)
index 0000000..c896ea9
--- /dev/null
@@ -0,0 +1,124 @@
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <signal.h>
+#include <unistd.h>
+#include <sys/mman.h>
+
+#include "utils.h"
+
+extern char __start___ex_table[];
+extern char __stop___ex_table[];
+
+#if defined(__powerpc64__)
+#define UCONTEXT_NIA(UC)       (UC)->uc_mcontext.gp_regs[PT_NIP]
+#elif defined(__powerpc__)
+#define UCONTEXT_NIA(UC)       (UC)->uc_mcontext.uc_regs->gregs[PT_NIP]
+#else
+#error implement UCONTEXT_NIA
+#endif
+
+static void segv_handler(int signr, siginfo_t *info, void *ptr)
+{
+       ucontext_t *uc = (ucontext_t *)ptr;
+       unsigned long addr = (unsigned long)info->si_addr;
+       unsigned long *ip = &UCONTEXT_NIA(uc);
+       unsigned long *ex_p = (unsigned long *)__start___ex_table;
+
+       while (ex_p < (unsigned long *)__stop___ex_table) {
+               unsigned long insn, fixup;
+
+               insn = *ex_p++;
+               fixup = *ex_p++;
+
+               if (insn == *ip) {
+                       *ip = fixup;
+                       return;
+               }
+       }
+
+       printf("No exception table match for NIA %lx ADDR %lx\n", *ip, addr);
+       abort();
+}
+
+static void setup_segv_handler(void)
+{
+       struct sigaction action;
+
+       memset(&action, 0, sizeof(action));
+       action.sa_sigaction = segv_handler;
+       action.sa_flags = SA_SIGINFO;
+       sigaction(SIGSEGV, &action, NULL);
+}
+
+unsigned long COPY_LOOP(void *to, const void *from, unsigned long size);
+unsigned long test_copy_tofrom_user_reference(void *to, const void *from, unsigned long size);
+
+static int total_passed;
+static int total_failed;
+
+static void do_one_test(char *dstp, char *srcp, unsigned long len)
+{
+       unsigned long got, expected;
+
+       got = COPY_LOOP(dstp, srcp, len);
+       expected = test_copy_tofrom_user_reference(dstp, srcp, len);
+
+       if (got != expected) {
+               total_failed++;
+               printf("FAIL from=%p to=%p len=%ld returned %ld, expected %ld\n",
+                      srcp, dstp, len, got, expected);
+               //abort();
+       } else
+               total_passed++;
+}
+
+//#define MAX_LEN 512
+#define MAX_LEN 16
+
+int test_copy_exception(void)
+{
+       int page_size;
+       static char *p, *q;
+       unsigned long src, dst, len;
+
+       page_size = getpagesize();
+       p = mmap(NULL, page_size * 2, PROT_READ|PROT_WRITE,
+               MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
+
+       if (p == MAP_FAILED) {
+               perror("mmap");
+               exit(1);
+       }
+
+       memset(p, 0, page_size);
+
+       setup_segv_handler();
+
+       if (mprotect(p + page_size, page_size, PROT_NONE)) {
+               perror("mprotect");
+               exit(1);
+       }
+
+       q = p + page_size - MAX_LEN;
+
+       for (src = 0; src < MAX_LEN; src++) {
+               for (dst = 0; dst < MAX_LEN; dst++) {
+                       for (len = 0; len < MAX_LEN+1; len++) {
+                               // printf("from=%p to=%p len=%ld\n", q+dst, q+src, len);
+                               do_one_test(q+dst, q+src, len);
+                       }
+               }
+       }
+
+       printf("Totals:\n");
+       printf("  Pass: %d\n", total_passed);
+       printf("  Fail: %d\n", total_failed);
+
+       return 0;
+}
+
+int main(void)
+{
+       return test_harness(test_copy_exception, str(COPY_LOOP));
+}
diff --git a/tools/testing/selftests/powerpc/copyloops/stubs.S b/tools/testing/selftests/powerpc/copyloops/stubs.S
new file mode 100644 (file)
index 0000000..ec8bcf2
--- /dev/null
@@ -0,0 +1,19 @@
+#include <asm/ppc_asm.h>
+
+FUNC_START(enter_vmx_usercopy)
+       li      r3,1
+       blr
+
+FUNC_START(exit_vmx_usercopy)
+       li      r3,0
+       blr
+
+FUNC_START(enter_vmx_ops)
+       li      r3,1
+       blr
+
+FUNC_START(exit_vmx_ops)
+       blr
+
+FUNC_START(__copy_tofrom_user_base)
+       blr