| // 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 bool vec_type_supported(struct vec_data *data) |
| { |
| return getauxval(data->hwcap_type) & data->hwcap; |
| } |
| |
| 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++) { |
| if (!vec_type_supported(&vec_data[i])) |
| continue; |
| 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 (!vec_type_supported(&vec_data[i])) |
| 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 = vec_type_supported(data); |
| 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(); |
| } |