diff --git a/tests/sys/Makefile b/tests/sys/Makefile --- a/tests/sys/Makefile +++ b/tests/sys/Makefile @@ -5,6 +5,7 @@ TESTS_SUBDIRS+= acl TESTS_SUBDIRS+= aio +TESTS_SUBDIRS+= arch TESTS_SUBDIRS+= ${_audit} TESTS_SUBDIRS+= auditpipe TESTS_SUBDIRS+= capsicum diff --git a/tests/sys/arch/Makefile b/tests/sys/arch/Makefile new file mode 100644 --- /dev/null +++ b/tests/sys/arch/Makefile @@ -0,0 +1,6 @@ + +.if exists(${.CURDIR}/${MACHINE_ARCH}) +SUBDIR+= ${MACHINE_ARCH} +.endif + +.include diff --git a/tests/sys/arch/Makefile.inc b/tests/sys/arch/Makefile.inc new file mode 100644 --- /dev/null +++ b/tests/sys/arch/Makefile.inc @@ -0,0 +1,4 @@ + +TESTSDIR= ${TESTSBASE}/sys/arch + +.include "../Makefile.inc" diff --git a/tests/sys/arch/aarch64/Makefile b/tests/sys/arch/aarch64/Makefile new file mode 100644 --- /dev/null +++ b/tests/sys/arch/aarch64/Makefile @@ -0,0 +1,8 @@ +PACKAGE= tests + +ATF_TESTS_C+= sve + +BINDIR= ${TESTSDIR} +PROGS+= sve_ptrace_helper + +.include diff --git a/tests/sys/arch/aarch64/sve.c b/tests/sys/arch/aarch64/sve.c new file mode 100644 --- /dev/null +++ b/tests/sys/arch/aarch64/sve.c @@ -0,0 +1,426 @@ +/*- + * 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 iovec fpvec, svevec; + 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); + ATF_REQUIRE(fpvec.iov_base != NULL); + ATF_REQUIRE(ptrace(PT_GETREGSET, wpid, (caddr_t)&fpvec, NT_ARM_SVE) == + 0); + + /* 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), SIGSTOP); + + /* 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); + ATF_REQUIRE(svevec.iov_base != NULL); + ATF_REQUIRE(ptrace(PT_GETREGSET, wpid, (caddr_t)&svevec, NT_ARM_SVE) == + 0); + + /* 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); + + + 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()); +} diff --git a/tests/sys/arch/aarch64/sve_ptrace_helper.c b/tests/sys/arch/aarch64/sve_ptrace_helper.c new file mode 100644 --- /dev/null +++ b/tests/sys/arch/aarch64/sve_ptrace_helper.c @@ -0,0 +1,46 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 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 + +int +main(int argc, char *argv[]) +{ + (void)argc; + (void)argv; + + /* Enable SVE by using it */ + asm volatile( + ".arch_extension sve \n" + "fmov z0.h, #1 \n" + ".arch_extension nosve \n" + ::: "z0"); + + raise(SIGSTOP); + _exit(0); +}