// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2015-2020 ARM Limited. * Original author: Dave Martin */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../../kselftest.h" #define EXPECTED_TESTS 19 /* and don't like each other, so: */ #ifndef NT_ARM_SVE #define NT_ARM_SVE 0x405 #endif static void dump(const void *buf, size_t size) { size_t i; const unsigned char *p = buf; for (i = 0; i < size; ++i) printf(" %.2x", *p++); } static int do_child(void) { if (ptrace(PTRACE_TRACEME, -1, NULL, NULL)) ksft_exit_fail_msg("PTRACE_TRACEME", strerror(errno)); if (raise(SIGSTOP)) ksft_exit_fail_msg("raise(SIGSTOP)", strerror(errno)); return EXIT_SUCCESS; } static struct user_sve_header *get_sve(pid_t pid, void **buf, size_t *size) { struct user_sve_header *sve; void *p; size_t sz = sizeof *sve; struct iovec iov; while (1) { if (*size < sz) { p = realloc(*buf, sz); if (!p) { errno = ENOMEM; goto error; } *buf = p; *size = sz; } iov.iov_base = *buf; iov.iov_len = sz; if (ptrace(PTRACE_GETREGSET, pid, NT_ARM_SVE, &iov)) goto error; sve = *buf; if (sve->size <= sz) break; sz = sve->size; } return sve; error: return NULL; } static int set_sve(pid_t pid, const struct user_sve_header *sve) { struct iovec iov; iov.iov_base = (void *)sve; iov.iov_len = sve->size; return ptrace(PTRACE_SETREGSET, pid, NT_ARM_SVE, &iov); } static void dump_sve_regs(const struct user_sve_header *sve, unsigned int num, unsigned int vlmax) { unsigned int vq; unsigned int i; if ((sve->flags & SVE_PT_REGS_MASK) != SVE_PT_REGS_SVE) ksft_exit_fail_msg("Dumping non-SVE register\n"); if (vlmax > sve->vl) vlmax = sve->vl; vq = sve_vq_from_vl(sve->vl); for (i = 0; i < num; ++i) { printf("# z%u:", i); dump((const char *)sve + SVE_PT_SVE_ZREG_OFFSET(vq, i), vlmax); printf("%s\n", vlmax == sve->vl ? "" : " ..."); } } static int do_parent(pid_t child) { int ret = EXIT_FAILURE; pid_t pid; int status; siginfo_t si; void *svebuf = NULL, *newsvebuf; size_t svebufsz = 0, newsvebufsz; struct user_sve_header *sve, *new_sve; struct user_fpsimd_state *fpsimd; unsigned int i, j; unsigned char *p; unsigned int vq; /* Attach to the child */ while (1) { int sig; pid = wait(&status); if (pid == -1) { perror("wait"); goto error; } /* * This should never happen but it's hard to flag in * the framework. */ if (pid != child) continue; if (WIFEXITED(status) || WIFSIGNALED(status)) ksft_exit_fail_msg("Child died unexpectedly\n"); if (!WIFSTOPPED(status)) goto error; sig = WSTOPSIG(status); if (ptrace(PTRACE_GETSIGINFO, pid, NULL, &si)) { if (errno == ESRCH) goto disappeared; if (errno == EINVAL) { sig = 0; /* bust group-stop */ goto cont; } ksft_test_result_fail("PTRACE_GETSIGINFO: %s\n", strerror(errno)); goto error; } if (sig == SIGSTOP && si.si_code == SI_TKILL && si.si_pid == pid) break; cont: if (ptrace(PTRACE_CONT, pid, NULL, sig)) { if (errno == ESRCH) goto disappeared; ksft_test_result_fail("PTRACE_CONT: %s\n", strerror(errno)); goto error; } } sve = get_sve(pid, &svebuf, &svebufsz); if (!sve) { int e = errno; ksft_test_result_fail("get_sve: %s\n", strerror(errno)); if (e == ESRCH) goto disappeared; goto error; } else { ksft_test_result_pass("get_sve\n"); } ksft_test_result((sve->flags & SVE_PT_REGS_MASK) == SVE_PT_REGS_FPSIMD, "FPSIMD registers\n"); if ((sve->flags & SVE_PT_REGS_MASK) != SVE_PT_REGS_FPSIMD) goto error; fpsimd = (struct user_fpsimd_state *)((char *)sve + SVE_PT_FPSIMD_OFFSET); for (i = 0; i < 32; ++i) { p = (unsigned char *)&fpsimd->vregs[i]; for (j = 0; j < sizeof fpsimd->vregs[i]; ++j) p[j] = j; } if (set_sve(pid, sve)) { int e = errno; ksft_test_result_fail("set_sve(FPSIMD): %s\n", strerror(errno)); if (e == ESRCH) goto disappeared; goto error; } vq = sve_vq_from_vl(sve->vl); newsvebufsz = SVE_PT_SVE_ZREG_OFFSET(vq, 1); new_sve = newsvebuf = malloc(newsvebufsz); if (!new_sve) { errno = ENOMEM; perror(NULL); goto error; } *new_sve = *sve; new_sve->flags &= ~SVE_PT_REGS_MASK; new_sve->flags |= SVE_PT_REGS_SVE; memset((char *)new_sve + SVE_PT_SVE_ZREG_OFFSET(vq, 0), 0, SVE_PT_SVE_ZREG_SIZE(vq)); new_sve->size = SVE_PT_SVE_ZREG_OFFSET(vq, 1); if (set_sve(pid, new_sve)) { int e = errno; ksft_test_result_fail("set_sve(ZREG): %s\n", strerror(errno)); if (e == ESRCH) goto disappeared; goto error; } new_sve = get_sve(pid, &newsvebuf, &newsvebufsz); if (!new_sve) { int e = errno; ksft_test_result_fail("get_sve(ZREG): %s\n", strerror(errno)); if (e == ESRCH) goto disappeared; goto error; } ksft_test_result((new_sve->flags & SVE_PT_REGS_MASK) == SVE_PT_REGS_SVE, "SVE registers\n"); if ((new_sve->flags & SVE_PT_REGS_MASK) != SVE_PT_REGS_SVE) goto error; dump_sve_regs(new_sve, 3, sizeof fpsimd->vregs[0]); p = (unsigned char *)new_sve + SVE_PT_SVE_ZREG_OFFSET(vq, 1); for (i = 0; i < sizeof fpsimd->vregs[0]; ++i) { unsigned char expected = i; if (__BYTE_ORDER == __BIG_ENDIAN) expected = sizeof fpsimd->vregs[0] - 1 - expected; ksft_test_result(p[i] == expected, "p[%d] == expected\n", i); if (p[i] != expected) goto error; } ret = EXIT_SUCCESS; error: kill(child, SIGKILL); disappeared: return ret; } int main(void) { int ret = EXIT_SUCCESS; pid_t child; ksft_print_header(); ksft_set_plan(EXPECTED_TESTS); if (!(getauxval(AT_HWCAP) & HWCAP_SVE)) ksft_exit_skip("SVE not available\n"); child = fork(); if (!child) return do_child(); if (do_parent(child)) ret = EXIT_FAILURE; ksft_print_cnts(); return ret; }