/*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2023,2024 Arm Ltd * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include static unsigned long vl; static void check_for_sve(void) { unsigned long hwcap; if (elf_aux_info(AT_HWCAP, &hwcap, sizeof(hwcap)) != 0) atf_tc_skip("No HWCAP"); if ((hwcap & HWCAP_SVE) == 0) atf_tc_skip("No SVE support in HW"); ATF_REQUIRE(sysarch(ARM64_GET_SVE_VL, &vl) == 0); } ATF_TC_WITHOUT_HEAD(sve_registers); ATF_TC_BODY(sve_registers, tc) { uint64_t reg, val; check_for_sve(); /* Check the ID registers are sane */ reg = READ_SPECIALREG(id_aa64pfr0_el1); ATF_REQUIRE((reg & ID_AA64PFR0_SVE_MASK) >= ID_AA64PFR0_SVE_IMPL); /* * Store 1.0 in z0, repeated every 16 bits. Read it from d0 as * the registers alias. */ asm volatile( ".arch_extension sve \n" "fmov z0.h, #1 \n" "str d0, [%0] \n" ".arch_extension nosve \n" :: "r"(&val) : "z0", "d0" ); /* Check for the 1.0 bit pattern */ ATF_REQUIRE_EQ(val, 0x3c003c003c003c00); } static void sve_signal_handler(int sig __unused, siginfo_t *info, void *context) { struct arm64_reg_context *regctx; struct sve_context *svectx; ucontext_t *ctx; uint64_t *sveregs; ctx = context; /* Check the trap is from a breakpoint instruction */ ATF_REQUIRE_EQ(info->si_trapno, EXCP_BRK); ctx->uc_mcontext.mc_gpregs.gp_elr += 4; /* Trash z0 to check it's not kept when exiting the handler */ asm volatile( ".arch_extension sve \n" "fmov z0.h, #2 \n" ".arch_extension nosve \n" ::: "z0"); /* Change the lower bits of z1 through the siginfo struct */ ctx->uc_mcontext.mc_fpregs.fp_q[1] = 0x5a5a5a5a5a5a5a5a; /* Find the SVE registers */ regctx = (struct arm64_reg_context *)ctx->uc_mcontext.mc_ptr; if (regctx != NULL) { int idx, next; do { if (regctx->ctx_id == ARM64_CTX_SVE) break; ATF_REQUIRE(regctx->ctx_id != ARM64_CTX_END); regctx = (struct arm64_reg_context *) ((uintptr_t)regctx + regctx->ctx_size); } while (1); /* Update the register context */ svectx = (struct sve_context *)regctx; ATF_REQUIRE_EQ(svectx->sve_vector_len, vl); sveregs = (uint64_t *)(void *)(svectx + 1); /* Find the array entries to change */ idx = 2 * svectx->sve_vector_len / sizeof(*sveregs); next = 3 * svectx->sve_vector_len / sizeof(*sveregs); while (idx != next) { sveregs[idx] = 0xdeaddeaddeaddead; idx++; } } } ATF_TC_WITHOUT_HEAD(sve_signal); ATF_TC_BODY(sve_signal, tc) { struct sigaction sa = { .sa_sigaction = sve_signal_handler, .sa_flags = SA_SIGINFO, }; uint64_t val0, val1, *val2; check_for_sve(); ATF_REQUIRE(sigaction(SIGTRAP, &sa, NULL) == 0); val2 = malloc(vl); ATF_REQUIRE(val2 != NULL); asm volatile( ".arch_extension sve \n" "fmov z0.h, #1 \n" "fmov z1.h, #1 \n" "fmov z2.h, #1 \n" /* Raise a SIGTRAP */ "brk #1 \n" "str d0, [%0] \n" "str d1, [%1] \n" "str z2, [%2] \n" ".arch_extension nosve \n" :: "r"(&val0), "r"(&val1), "r"(val2) : "z0", "z1", "z2", "d0", "d1" ); /* Check for the 1.0 bit pattern */ ATF_REQUIRE_EQ(val0, 0x3c003c003c003c00); /* Check for the changed bit pattern */ ATF_REQUIRE_EQ(val1, 0x5a5a5a5a5a5a5a5a); /* * Check the lower 128 bits are restored from fp_q and the * upper bits are restored from the sve data region */ for (size_t i = 0; i < vl / sizeof(*val2); i++) { if (i < 2) ATF_REQUIRE_EQ(val2[i], 0x3c003c003c003c00); else ATF_REQUIRE_EQ(val2[i], 0xdeaddeaddeaddead); } free(val2); } ATF_TC_WITHOUT_HEAD(sve_signal_altstack); ATF_TC_BODY(sve_signal_altstack, tc) { struct sigaction sa = { .sa_sigaction = sve_signal_handler, .sa_flags = SA_ONSTACK | SA_SIGINFO, }; stack_t ss = { .ss_size = SIGSTKSZ, }; uint64_t val0, val1, *val2; check_for_sve(); ss.ss_sp = malloc(ss.ss_size); ATF_REQUIRE(ss.ss_sp != NULL); ATF_REQUIRE(sigaltstack(&ss, NULL) == 0); ATF_REQUIRE(sigaction(SIGTRAP, &sa, NULL) == 0); val2 = malloc(vl); ATF_REQUIRE(val2 != NULL); asm volatile( ".arch_extension sve \n" "fmov z0.h, #1 \n" "fmov z1.h, #1 \n" "fmov z2.h, #1 \n" /* Raise a SIGTRAP */ "brk #1 \n" "str d0, [%0] \n" "str d1, [%1] \n" "str z2, [%2] \n" ".arch_extension nosve \n" :: "r"(&val0), "r"(&val1), "r"(val2) : "z0", "z1", "z2", "d0", "d1" ); /* Check for the 1.0 bit pattern */ ATF_REQUIRE_EQ(val0, 0x3c003c003c003c00); /* Check for the changed bit pattern */ ATF_REQUIRE_EQ(val1, 0x5a5a5a5a5a5a5a5a); /* * Check the lower 128 bits are restored from fp_q and the * upper bits are restored from the sve data region */ for (size_t i = 0; i < vl / sizeof(*val2); i++) { if (i < 2) ATF_REQUIRE_EQ(val2[i], 0x3c003c003c003c00); else ATF_REQUIRE_EQ(val2[i], 0xdeaddeaddeaddead); } free(val2); } ATF_TC_WITHOUT_HEAD(sve_ptrace); ATF_TC_BODY(sve_ptrace, tc) { struct reg reg; struct iovec fpvec, svevec; struct svereg_header *header; pid_t child, wpid; int status; check_for_sve(); child = fork(); ATF_REQUIRE(child >= 0); if (child == 0) { char exec_path[1024]; /* Calculate the location of the helper */ snprintf(exec_path, sizeof(exec_path), "%s/sve_ptrace_helper", atf_tc_get_config_var(tc, "srcdir")); ptrace(PT_TRACE_ME, 0, NULL, 0); /* Execute the helper so SVE will be disabled */ execl(exec_path, "sve_ptrace_helper", NULL); _exit(1); } /* The first event should be the SIGSTOP at the start of the child */ wpid = waitpid(child, &status, 0); ATF_REQUIRE(WIFSTOPPED(status)); fpvec.iov_base = NULL; fpvec.iov_len = 0; /* Read the length before SVE has been used */ ATF_REQUIRE(ptrace(PT_GETREGSET, wpid, (caddr_t)&fpvec, NT_ARM_SVE) == 0); ATF_REQUIRE(fpvec.iov_len == (sizeof(struct svereg_header) + sizeof(struct fpregs))); fpvec.iov_base = malloc(fpvec.iov_len); header = fpvec.iov_base; ATF_REQUIRE(fpvec.iov_base != NULL); ATF_REQUIRE(ptrace(PT_GETREGSET, wpid, (caddr_t)&fpvec, NT_ARM_SVE) == 0); ATF_REQUIRE((header->sve_flags & SVEREG_FLAG_REGS_MASK) == SVEREG_FLAG_FP); /* Check writing back the FP registers works */ ATF_REQUIRE(ptrace(PT_SETREGSET, wpid, (caddr_t)&fpvec, NT_ARM_SVE) == 0); ptrace(PT_CONTINUE, wpid, (caddr_t)1, 0); /* The second event should be the SIGINFO at the end */ wpid = waitpid(child, &status, 0); ATF_REQUIRE(WIFSTOPPED(status)); ATF_REQUIRE_EQ(WSTOPSIG(status), SIGTRAP); /* Check writing back FP registers when SVE has started will fail */ ATF_REQUIRE(ptrace(PT_SETREGSET, wpid, (caddr_t)&fpvec, NT_ARM_SVE) == -1); svevec.iov_base = NULL; svevec.iov_len = 0; /* Read the length after SVE has been used */ ATF_REQUIRE(ptrace(PT_GETREGSET, wpid, (caddr_t)&svevec, NT_ARM_SVE) == 0); /* TODO: Check the length is correct based on vector length */ ATF_REQUIRE(svevec.iov_len > (sizeof(struct svereg_header) + sizeof(struct fpregs))); svevec.iov_base = malloc(svevec.iov_len); header = svevec.iov_base; ATF_REQUIRE(svevec.iov_base != NULL); ATF_REQUIRE(ptrace(PT_GETREGSET, wpid, (caddr_t)&svevec, NT_ARM_SVE) == 0); ATF_REQUIRE((header->sve_flags & SVEREG_FLAG_REGS_MASK) == SVEREG_FLAG_SVE); /* Test writing back the SVE registers works */ ATF_REQUIRE(ptrace(PT_SETREGSET, wpid, (caddr_t)&svevec, NT_ARM_SVE) == 0); free(svevec.iov_base); free(fpvec.iov_base); /* Step over the brk instruction */ ATF_REQUIRE(ptrace(PT_GETREGS, wpid, (caddr_t)®, 0) != -1); reg.elr += 4; ATF_REQUIRE(ptrace(PT_SETREGS, wpid, (caddr_t)®, 0) != -1); ptrace(PT_CONTINUE, wpid, (caddr_t)1, 0); waitpid(child, &status, 0); ATF_REQUIRE(WIFEXITED(status)); ATF_REQUIRE_EQ(WEXITSTATUS(status), 0); } ATF_TC_WITHOUT_HEAD(sve_fork_env); ATF_TC_BODY(sve_fork_env, tc) { pid_t child, wpid; int status; check_for_sve(); child = fork(); ATF_REQUIRE(child >= 0); /* Check the child environment is sane */ if (child == 0) { unsigned long child_vl, hwcap; if (elf_aux_info(AT_HWCAP, &hwcap, sizeof(hwcap)) != 0) _exit(1); if ((hwcap & HWCAP_SVE) == 0) _exit(2); if (sysarch(ARM64_GET_SVE_VL, &child_vl) != 0) _exit(3); if (child_vl != vl) _exit(4); _exit(0); } wpid = waitpid(child, &status, 0); ATF_REQUIRE_EQ(child, wpid); ATF_REQUIRE(WIFEXITED(status)); ATF_REQUIRE(WEXITSTATUS(status) == 0); } ATF_TC_WITHOUT_HEAD(sve_fork_regs); ATF_TC_BODY(sve_fork_regs, tc) { pid_t child, wpid; uint64_t *val; int status; check_for_sve(); /* * Malloc before fork to reduce the change of trashing sve registers */ val = malloc(vl); ATF_REQUIRE(val != NULL); /* * Store 1.0 in z0, repeated every 16 bits. Read it from d0 as * the registers alias. */ asm volatile( ".arch_extension sve \n" "fmov z8.h, #1 \n" ".arch_extension nosve \n" ::: "z8" ); /* TODO: Move to asm to ensure z8 isn't trashed */ child = fork(); asm volatile( ".arch_extension sve \n" "str z8, [%0] \n" ".arch_extension nosve \n" :: "r"(val) : "z8" ); ATF_REQUIRE(child >= 0); /* Check the child environment is sane */ if (child == 0) { for (size_t i = 0; i < vl / sizeof(*val); i++) { if (val[i] != 0x3c003c003c003c00) _exit(i + 1); } free(val); _exit(0); } free(val); wpid = waitpid(child, &status, 0); ATF_REQUIRE_EQ(child, wpid); ATF_REQUIRE(WIFEXITED(status)); ATF_REQUIRE(WEXITSTATUS(status) == 0); } ATF_TP_ADD_TCS(tp) { ATF_TP_ADD_TC(tp, sve_registers); ATF_TP_ADD_TC(tp, sve_signal); ATF_TP_ADD_TC(tp, sve_signal_altstack); /* TODO: Check a too small signal stack */ ATF_TP_ADD_TC(tp, sve_ptrace); ATF_TP_ADD_TC(tp, sve_fork_env); ATF_TP_ADD_TC(tp, sve_fork_regs); return (atf_no_error()); }