// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2021 ARM Limited. * Original author: Mark Brown <broonie@kernel.org> */ #include <assert.h> #include <errno.h> #include <fcntl.h> #include <stdbool.h> #include <stddef.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/auxv.h> #include <sys/prctl.h> #include <sys/types.h> #include <sys/wait.h> #include <asm/sigcontext.h> #include <asm/hwcap.h> #include "../../kselftest.h" #include "rdvl.h" #define ARCH_MIN_VL SVE_VL_MIN struct vec_data { const char *name; unsigned long hwcap_type; unsigned long hwcap; const char *rdvl_binary; int (*rdvl)(void); int prctl_get; int prctl_set; const char *default_vl_file; int default_vl; int min_vl; int max_vl; }; #define VEC_SVE 0 #define VEC_SME 1 static struct vec_data vec_data[] = { [VEC_SVE] = { .name = "SVE", .hwcap_type = AT_HWCAP, .hwcap = HWCAP_SVE, .rdvl = rdvl_sve, .rdvl_binary = "./rdvl-sve", .prctl_get = PR_SVE_GET_VL, .prctl_set = PR_SVE_SET_VL, .default_vl_file = "/proc/sys/abi/sve_default_vector_length", }, [VEC_SME] = { .name = "SME", .hwcap_type = AT_HWCAP2, .hwcap = HWCAP2_SME, .rdvl = rdvl_sme, .rdvl_binary = "./rdvl-sme", .prctl_get = PR_SME_GET_VL, .prctl_set = PR_SME_SET_VL, .default_vl_file = "/proc/sys/abi/sme_default_vector_length", }, }; static int stdio_read_integer(FILE *f, const char *what, int *val) { int n = 0; int ret; ret = fscanf(f, "%d%*1[\n]%n", val, &n); if (ret < 1 || n < 1) { ksft_print_msg("failed to parse integer from %s\n", what); return -1; } return 0; } /* Start a new process and return the vector length it sees */ static int get_child_rdvl(struct vec_data *data) { FILE *out; int pipefd[2]; pid_t pid, child; int read_vl, ret; ret = pipe(pipefd); if (ret == -1) { ksft_print_msg("pipe() failed: %d (%s)\n", errno, strerror(errno)); return -1; } fflush(stdout); child = fork(); if (child == -1) { ksft_print_msg("fork() failed: %d (%s)\n", errno, strerror(errno)); close(pipefd[0]); close(pipefd[1]); return -1; } /* Child: put vector length on the pipe */ if (child == 0) { /* * Replace stdout with the pipe, errors to stderr from * here as kselftest prints to stdout. */ ret = dup2(pipefd[1], 1); if (ret == -1) { fprintf(stderr, "dup2() %d\n", errno); exit(EXIT_FAILURE); } /* exec() a new binary which puts the VL on stdout */ ret = execl(data->rdvl_binary, data->rdvl_binary, NULL); fprintf(stderr, "execl(%s) failed: %d (%s)\n", data->rdvl_binary, errno, strerror(errno)); exit(EXIT_FAILURE); } close(pipefd[1]); /* Parent; wait for the exit status from the child & verify it */ do { pid = wait(&ret); if (pid == -1) { ksft_print_msg("wait() failed: %d (%s)\n", errno, strerror(errno)); close(pipefd[0]); return -1; } } while (pid != child); assert(pid == child); if (!WIFEXITED(ret)) { ksft_print_msg("child exited abnormally\n"); close(pipefd[0]); return -1; } if (WEXITSTATUS(ret) != 0) { ksft_print_msg("child returned error %d\n", WEXITSTATUS(ret)); close(pipefd[0]); return -1; } out = fdopen(pipefd[0], "r"); if (!out) { ksft_print_msg("failed to open child stdout\n"); close(pipefd[0]); return -1; } ret = stdio_read_integer(out, "child", &read_vl); fclose(out); if (ret != 0) return ret; return read_vl; } static int file_read_integer(const char *name, int *val) { FILE *f; int ret; f = fopen(name, "r"); if (!f) { ksft_test_result_fail("Unable to open %s: %d (%s)\n", name, errno, strerror(errno)); return -1; } ret = stdio_read_integer(f, name, val); fclose(f); return ret; } static int file_write_integer(const char *name, int val) { FILE *f; f = fopen(name, "w"); if (!f) { ksft_test_result_fail("Unable to open %s: %d (%s)\n", name, errno, strerror(errno)); return -1; } fprintf(f, "%d", val); fclose(f); return 0; } /* * Verify that we can read the default VL via proc, checking that it * is set in a freshly spawned child. */ static void proc_read_default(struct vec_data *data) { int default_vl, child_vl, ret; ret = file_read_integer(data->default_vl_file, &default_vl); if (ret != 0) return; /* Is this the actual default seen by new processes? */ child_vl = get_child_rdvl(data); if (child_vl != default_vl) { ksft_test_result_fail("%s is %d but child VL is %d\n", data->default_vl_file, default_vl, child_vl); return; } ksft_test_result_pass("%s default vector length %d\n", data->name, default_vl); data->default_vl = default_vl; } /* Verify that we can write a minimum value and have it take effect */ static void proc_write_min(struct vec_data *data) { int ret, new_default, child_vl; if (geteuid() != 0) { ksft_test_result_skip("Need to be root to write to /proc\n"); return; } ret = file_write_integer(data->default_vl_file, ARCH_MIN_VL); if (ret != 0) return; /* What was the new value? */ ret = file_read_integer(data->default_vl_file, &new_default); if (ret != 0) return; /* Did it take effect in a new process? */ child_vl = get_child_rdvl(data); if (child_vl != new_default) { ksft_test_result_fail("%s is %d but child VL is %d\n", data->default_vl_file, new_default, child_vl); return; } ksft_test_result_pass("%s minimum vector length %d\n", data->name, new_default); data->min_vl = new_default; file_write_integer(data->default_vl_file, data->default_vl); } /* Verify that we can write a maximum value and have it take effect */ static void proc_write_max(struct vec_data *data) { int ret, new_default, child_vl; if (geteuid() != 0) { ksft_test_result_skip("Need to be root to write to /proc\n"); return; } /* -1 is accepted by the /proc interface as the maximum VL */ ret = file_write_integer(data->default_vl_file, -1); if (ret != 0) return; /* What was the new value? */ ret = file_read_integer(data->default_vl_file, &new_default); if (ret != 0) return; /* Did it take effect in a new process? */ child_vl = get_child_rdvl(data); if (child_vl != new_default) { ksft_test_result_fail("%s is %d but child VL is %d\n", data->default_vl_file, new_default, child_vl); return; } ksft_test_result_pass("%s maximum vector length %d\n", data->name, new_default); data->max_vl = new_default; file_write_integer(data->default_vl_file, data->default_vl); } /* Can we read back a VL from prctl? */ static void prctl_get(struct vec_data *data) { int ret; ret = prctl(data->prctl_get); if (ret == -1) { ksft_test_result_fail("%s prctl() read failed: %d (%s)\n", data->name, errno, strerror(errno)); return; } /* Mask out any flags */ ret &= PR_SVE_VL_LEN_MASK; /* Is that what we can read back directly? */ if (ret == data->rdvl()) ksft_test_result_pass("%s current VL is %d\n", data->name, ret); else ksft_test_result_fail("%s prctl() VL %d but RDVL is %d\n", data->name, ret, data->rdvl()); } /* Does the prctl let us set the VL we already have? */ static void prctl_set_same(struct vec_data *data) { int cur_vl = data->rdvl(); int ret; ret = prctl(data->prctl_set, cur_vl); if (ret < 0) { ksft_test_result_fail("%s prctl set failed: %d (%s)\n", data->name, errno, strerror(errno)); return; } ksft_test_result(cur_vl == data->rdvl(), "%s set VL %d and have VL %d\n", data->name, cur_vl, data->rdvl()); } /* Can we set a new VL for this process? */ static void prctl_set(struct vec_data *data) { int ret; if (data->min_vl == data->max_vl) { ksft_test_result_skip("%s only one VL supported\n", data->name); return; } /* Try to set the minimum VL */ ret = prctl(data->prctl_set, data->min_vl); if (ret < 0) { ksft_test_result_fail("%s prctl set failed for %d: %d (%s)\n", data->name, data->min_vl, errno, strerror(errno)); return; } if ((ret & PR_SVE_VL_LEN_MASK) != data->min_vl) { ksft_test_result_fail("%s prctl set %d but return value is %d\n", data->name, data->min_vl, data->rdvl()); return; } if (data->rdvl() != data->min_vl) { ksft_test_result_fail("%s set %d but RDVL is %d\n", data->name, data->min_vl, data->rdvl()); return; } /* Try to set the maximum VL */ ret = prctl(data->prctl_set, data->max_vl); if (ret < 0) { ksft_test_result_fail("%s prctl set failed for %d: %d (%s)\n", data->name, data->max_vl, errno, strerror(errno)); return; } if ((ret & PR_SVE_VL_LEN_MASK) != data->max_vl) { ksft_test_result_fail("%s prctl() set %d but return value is %d\n", data->name, data->max_vl, data->rdvl()); return; } /* The _INHERIT flag should not be present when we read the VL */ ret = prctl(data->prctl_get); if (ret == -1) { ksft_test_result_fail("%s prctl() read failed: %d (%s)\n", data->name, errno, strerror(errno)); return; } if (ret & PR_SVE_VL_INHERIT) { ksft_test_result_fail("%s prctl() reports _INHERIT\n", data->name); return; } ksft_test_result_pass("%s prctl() set min/max\n", data->name); } /* If we didn't request it a new VL shouldn't affect the child */ static void prctl_set_no_child(struct vec_data *data) { int ret, child_vl; if (data->min_vl == data->max_vl) { ksft_test_result_skip("%s only one VL supported\n", data->name); return; } ret = prctl(data->prctl_set, data->min_vl); if (ret < 0) { ksft_test_result_fail("%s prctl set failed for %d: %d (%s)\n", data->name, data->min_vl, errno, strerror(errno)); return; } /* Ensure the default VL is different */ ret = file_write_integer(data->default_vl_file, data->max_vl); if (ret != 0) return; /* Check that the child has the default we just set */ child_vl = get_child_rdvl(data); if (child_vl != data->max_vl) { ksft_test_result_fail("%s is %d but child VL is %d\n", data->default_vl_file, data->max_vl, child_vl); return; } ksft_test_result_pass("%s vector length used default\n", data->name); file_write_integer(data->default_vl_file, data->default_vl); } /* If we didn't request it a new VL shouldn't affect the child */ static void prctl_set_for_child(struct vec_data *data) { int ret, child_vl; if (data->min_vl == data->max_vl) { ksft_test_result_skip("%s only one VL supported\n", data->name); return; } ret = prctl(data->prctl_set, data->min_vl | PR_SVE_VL_INHERIT); if (ret < 0) { ksft_test_result_fail("%s prctl set failed for %d: %d (%s)\n", data->name, data->min_vl, errno, strerror(errno)); return; } /* The _INHERIT flag should be present when we read the VL */ ret = prctl(data->prctl_get); if (ret == -1) { ksft_test_result_fail("%s prctl() read failed: %d (%s)\n", data->name, errno, strerror(errno)); return; } if (!(ret & PR_SVE_VL_INHERIT)) { ksft_test_result_fail("%s prctl() does not report _INHERIT\n", data->name); return; } /* Ensure the default VL is different */ ret = file_write_integer(data->default_vl_file, data->max_vl); if (ret != 0) return; /* Check that the child inherited our VL */ child_vl = get_child_rdvl(data); if (child_vl != data->min_vl) { ksft_test_result_fail("%s is %d but child VL is %d\n", data->default_vl_file, data->min_vl, child_vl); return; } ksft_test_result_pass("%s vector length was inherited\n", data->name); file_write_integer(data->default_vl_file, data->default_vl); } /* _ONEXEC takes effect only in the child process */ static void prctl_set_onexec(struct vec_data *data) { int ret, child_vl; if (data->min_vl == data->max_vl) { ksft_test_result_skip("%s only one VL supported\n", data->name); return; } /* Set a known value for the default and our current VL */ ret = file_write_integer(data->default_vl_file, data->max_vl); if (ret != 0) return; ret = prctl(data->prctl_set, data->max_vl); if (ret < 0) { ksft_test_result_fail("%s prctl set failed for %d: %d (%s)\n", data->name, data->min_vl, errno, strerror(errno)); return; } /* Set a different value for the child to have on exec */ ret = prctl(data->prctl_set, data->min_vl | PR_SVE_SET_VL_ONEXEC); if (ret < 0) { ksft_test_result_fail("%s prctl set failed for %d: %d (%s)\n", data->name, data->min_vl, errno, strerror(errno)); return; } /* Our current VL should stay the same */ if (data->rdvl() != data->max_vl) { ksft_test_result_fail("%s VL changed by _ONEXEC prctl()\n", data->name); return; } /* Check that the child inherited our VL */ child_vl = get_child_rdvl(data); if (child_vl != data->min_vl) { ksft_test_result_fail("Set %d _ONEXEC but child VL is %d\n", data->min_vl, child_vl); return; } ksft_test_result_pass("%s vector length set on exec\n", data->name); file_write_integer(data->default_vl_file, data->default_vl); } /* For each VQ verify that setting via prctl() does the right thing */ static void prctl_set_all_vqs(struct vec_data *data) { int ret, vq, vl, new_vl, i; int orig_vls[ARRAY_SIZE(vec_data)]; int errors = 0; if (!data->min_vl || !data->max_vl) { ksft_test_result_skip("%s Failed to enumerate VLs, not testing VL setting\n", data->name); return; } for (i = 0; i < ARRAY_SIZE(vec_data); i++) orig_vls[i] = vec_data[i].rdvl(); for (vq = SVE_VQ_MIN; vq <= SVE_VQ_MAX; vq++) { vl = sve_vl_from_vq(vq); /* Attempt to set the VL */ ret = prctl(data->prctl_set, vl); if (ret < 0) { errors++; ksft_print_msg("%s prctl set failed for %d: %d (%s)\n", data->name, vl, errno, strerror(errno)); continue; } new_vl = ret & PR_SVE_VL_LEN_MASK; /* Check that we actually have the reported new VL */ if (data->rdvl() != new_vl) { ksft_print_msg("Set %s VL %d but RDVL reports %d\n", data->name, new_vl, data->rdvl()); errors++; } /* Did any other VLs change? */ for (i = 0; i < ARRAY_SIZE(vec_data); i++) { if (&vec_data[i] == data) continue; if (!(getauxval(vec_data[i].hwcap_type) & vec_data[i].hwcap)) continue; if (vec_data[i].rdvl() != orig_vls[i]) { ksft_print_msg("%s VL changed from %d to %d\n", vec_data[i].name, orig_vls[i], vec_data[i].rdvl()); errors++; } } /* Was that the VL we asked for? */ if (new_vl == vl) continue; /* Should round up to the minimum VL if below it */ if (vl < data->min_vl) { if (new_vl != data->min_vl) { ksft_print_msg("%s VL %d returned %d not minimum %d\n", data->name, vl, new_vl, data->min_vl); errors++; } continue; } /* Should round down to maximum VL if above it */ if (vl > data->max_vl) { if (new_vl != data->max_vl) { ksft_print_msg("%s VL %d returned %d not maximum %d\n", data->name, vl, new_vl, data->max_vl); errors++; } continue; } /* Otherwise we should've rounded down */ if (!(new_vl < vl)) { ksft_print_msg("%s VL %d returned %d, did not round down\n", data->name, vl, new_vl); errors++; continue; } } ksft_test_result(errors == 0, "%s prctl() set all VLs, %d errors\n", data->name, errors); } typedef void (*test_type)(struct vec_data *); static const test_type tests[] = { /* * The default/min/max tests must be first and in this order * to provide data for other tests. */ proc_read_default, proc_write_min, proc_write_max, prctl_get, prctl_set_same, prctl_set, prctl_set_no_child, prctl_set_for_child, prctl_set_onexec, prctl_set_all_vqs, }; static inline void smstart(void) { asm volatile("msr S0_3_C4_C7_3, xzr"); } static inline void smstart_sm(void) { asm volatile("msr S0_3_C4_C3_3, xzr"); } static inline void smstop(void) { asm volatile("msr S0_3_C4_C6_3, xzr"); } /* * Verify we can change the SVE vector length while SME is active and * continue to use SME afterwards. */ static void change_sve_with_za(void) { struct vec_data *sve_data = &vec_data[VEC_SVE]; bool pass = true; int ret, i; if (sve_data->min_vl == sve_data->max_vl) { ksft_print_msg("Only one SVE VL supported, can't change\n"); ksft_test_result_skip("change_sve_while_sme\n"); return; } /* Ensure we will trigger a change when we set the maximum */ ret = prctl(sve_data->prctl_set, sve_data->min_vl); if (ret != sve_data->min_vl) { ksft_print_msg("Failed to set SVE VL %d: %d\n", sve_data->min_vl, ret); pass = false; } /* Enable SM and ZA */ smstart(); /* Trigger another VL change */ ret = prctl(sve_data->prctl_set, sve_data->max_vl); if (ret != sve_data->max_vl) { ksft_print_msg("Failed to set SVE VL %d: %d\n", sve_data->max_vl, ret); pass = false; } /* * Spin for a bit with SM enabled to try to trigger another * save/restore. We can't use syscalls without exiting * streaming mode. */ for (i = 0; i < 100000000; i++) smstart_sm(); /* * TODO: Verify that ZA was preserved over the VL change and * spin. */ /* Clean up after ourselves */ smstop(); ret = prctl(sve_data->prctl_set, sve_data->default_vl); if (ret != sve_data->default_vl) { ksft_print_msg("Failed to restore SVE VL %d: %d\n", sve_data->default_vl, ret); pass = false; } ksft_test_result(pass, "change_sve_with_za\n"); } typedef void (*test_all_type)(void); static const struct { const char *name; test_all_type test; } all_types_tests[] = { { "change_sve_with_za", change_sve_with_za }, }; int main(void) { bool all_supported = true; int i, j; ksft_print_header(); ksft_set_plan(ARRAY_SIZE(tests) * ARRAY_SIZE(vec_data) + ARRAY_SIZE(all_types_tests)); for (i = 0; i < ARRAY_SIZE(vec_data); i++) { struct vec_data *data = &vec_data[i]; unsigned long supported; supported = getauxval(data->hwcap_type) & data->hwcap; if (!supported) all_supported = false; for (j = 0; j < ARRAY_SIZE(tests); j++) { if (supported) tests[j](data); else ksft_test_result_skip("%s not supported\n", data->name); } } for (i = 0; i < ARRAY_SIZE(all_types_tests); i++) { if (all_supported) all_types_tests[i].test(); else ksft_test_result_skip("%s\n", all_types_tests[i].name); } ksft_exit_pass(); }