// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2021 ARM Limited. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../../kselftest.h" /* and don't like each other, so: */ #ifndef NT_ARM_ZA #define NT_ARM_ZA 0x40c #endif /* * The architecture defines the maximum VQ as 16 but for extensibility * the kernel specifies the SVE_VQ_MAX as 512 resulting in us running * a *lot* more tests than are useful if we use it. Until the * architecture is extended let's limit our coverage to what is * currently allowed, plus one extra to ensure we cover constraining * the VL as expected. */ #define TEST_VQ_MAX 17 #define EXPECTED_TESTS (((TEST_VQ_MAX - SVE_VQ_MIN) + 1) * 3) static void fill_buf(char *buf, size_t size) { int i; for (i = 0; i < size; i++) buf[i] = random(); } 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_za_header *get_za(pid_t pid, void **buf, size_t *size) { struct user_za_header *za; void *p; size_t sz = sizeof(*za); 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_ZA, &iov)) goto error; za = *buf; if (za->size <= sz) break; sz = za->size; } return za; error: return NULL; } static int set_za(pid_t pid, const struct user_za_header *za) { struct iovec iov; iov.iov_base = (void *)za; iov.iov_len = za->size; return ptrace(PTRACE_SETREGSET, pid, NT_ARM_ZA, &iov); } /* Validate attempting to set the specfied VL via ptrace */ static void ptrace_set_get_vl(pid_t child, unsigned int vl, bool *supported) { struct user_za_header za; struct user_za_header *new_za = NULL; size_t new_za_size = 0; int ret, prctl_vl; *supported = false; /* Check if the VL is supported in this process */ prctl_vl = prctl(PR_SME_SET_VL, vl); if (prctl_vl == -1) ksft_exit_fail_msg("prctl(PR_SME_SET_VL) failed: %s (%d)\n", strerror(errno), errno); /* If the VL is not supported then a supported VL will be returned */ *supported = (prctl_vl == vl); /* Set the VL by doing a set with no register payload */ memset(&za, 0, sizeof(za)); za.size = sizeof(za); za.vl = vl; ret = set_za(child, &za); if (ret != 0) { ksft_test_result_fail("Failed to set VL %u\n", vl); return; } /* * Read back the new register state and verify that we have the * same VL that we got from prctl() on ourselves. */ if (!get_za(child, (void **)&new_za, &new_za_size)) { ksft_test_result_fail("Failed to read VL %u\n", vl); return; } ksft_test_result(new_za->vl = prctl_vl, "Set VL %u\n", vl); free(new_za); } /* Validate attempting to set no ZA data and read it back */ static void ptrace_set_no_data(pid_t child, unsigned int vl) { void *read_buf = NULL; struct user_za_header write_za; struct user_za_header *read_za; size_t read_za_size = 0; int ret; /* Set up some data and write it out */ memset(&write_za, 0, sizeof(write_za)); write_za.size = ZA_PT_ZA_OFFSET; write_za.vl = vl; ret = set_za(child, &write_za); if (ret != 0) { ksft_test_result_fail("Failed to set VL %u no data\n", vl); return; } /* Read the data back */ if (!get_za(child, (void **)&read_buf, &read_za_size)) { ksft_test_result_fail("Failed to read VL %u no data\n", vl); return; } read_za = read_buf; /* We might read more data if there's extensions we don't know */ if (read_za->size < write_za.size) { ksft_test_result_fail("VL %u wrote %d bytes, only read %d\n", vl, write_za.size, read_za->size); goto out_read; } ksft_test_result(read_za->size == write_za.size, "Disabled ZA for VL %u\n", vl); out_read: free(read_buf); } /* Validate attempting to set data and read it back */ static void ptrace_set_get_data(pid_t child, unsigned int vl) { void *write_buf; void *read_buf = NULL; struct user_za_header *write_za; struct user_za_header *read_za; size_t read_za_size = 0; unsigned int vq = sve_vq_from_vl(vl); int ret; size_t data_size; data_size = ZA_PT_SIZE(vq); write_buf = malloc(data_size); if (!write_buf) { ksft_test_result_fail("Error allocating %d byte buffer for VL %u\n", data_size, vl); return; } write_za = write_buf; /* Set up some data and write it out */ memset(write_za, 0, data_size); write_za->size = data_size; write_za->vl = vl; fill_buf(write_buf + ZA_PT_ZA_OFFSET, ZA_PT_ZA_SIZE(vq)); ret = set_za(child, write_za); if (ret != 0) { ksft_test_result_fail("Failed to set VL %u data\n", vl); goto out; } /* Read the data back */ if (!get_za(child, (void **)&read_buf, &read_za_size)) { ksft_test_result_fail("Failed to read VL %u data\n", vl); goto out; } read_za = read_buf; /* We might read more data if there's extensions we don't know */ if (read_za->size < write_za->size) { ksft_test_result_fail("VL %u wrote %d bytes, only read %d\n", vl, write_za->size, read_za->size); goto out_read; } ksft_test_result(memcmp(write_buf + ZA_PT_ZA_OFFSET, read_buf + ZA_PT_ZA_OFFSET, ZA_PT_ZA_SIZE(vq)) == 0, "Data match for VL %u\n", vl); out_read: free(read_buf); out: free(write_buf); } static int do_parent(pid_t child) { int ret = EXIT_FAILURE; pid_t pid; int status; siginfo_t si; unsigned int vq, vl; bool vl_supported; /* 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; } } ksft_print_msg("Parent is %d, child is %d\n", getpid(), child); /* Step through every possible VQ */ for (vq = SVE_VQ_MIN; vq <= TEST_VQ_MAX; vq++) { vl = sve_vl_from_vq(vq); /* First, try to set this vector length */ ptrace_set_get_vl(child, vl, &vl_supported); /* If the VL is supported validate data set/get */ if (vl_supported) { ptrace_set_no_data(child, vl); ptrace_set_get_data(child, vl); } else { ksft_test_result_skip("Disabled ZA for VL %u\n", vl); ksft_test_result_skip("Get and set data for VL %u\n", vl); } } ret = EXIT_SUCCESS; error: kill(child, SIGKILL); disappeared: return ret; } int main(void) { int ret = EXIT_SUCCESS; pid_t child; srandom(getpid()); ksft_print_header(); if (!(getauxval(AT_HWCAP2) & HWCAP2_SME)) { ksft_set_plan(1); ksft_exit_skip("SME not available\n"); } ksft_set_plan(EXPECTED_TESTS); child = fork(); if (!child) return do_child(); if (do_parent(child)) ret = EXIT_FAILURE; ksft_print_cnts(); return ret; }