Path: blob/master/tools/testing/selftests/arm64/fp/vec-syscfg.c
26292 views
// SPDX-License-Identifier: GPL-2.0-only1/*2* Copyright (C) 2021 ARM Limited.3* Original author: Mark Brown <[email protected]>4*/5#include <assert.h>6#include <errno.h>7#include <fcntl.h>8#include <stdbool.h>9#include <stddef.h>10#include <stdio.h>11#include <stdlib.h>12#include <string.h>13#include <unistd.h>14#include <sys/auxv.h>15#include <sys/prctl.h>16#include <sys/types.h>17#include <sys/wait.h>18#include <asm/sigcontext.h>19#include <asm/hwcap.h>2021#include "../../kselftest.h"22#include "rdvl.h"2324#define ARCH_MIN_VL SVE_VL_MIN2526struct vec_data {27const char *name;28unsigned long hwcap_type;29unsigned long hwcap;30const char *rdvl_binary;31int (*rdvl)(void);3233int prctl_get;34int prctl_set;35const char *default_vl_file;3637int default_vl;38int min_vl;39int max_vl;40};4142#define VEC_SVE 043#define VEC_SME 14445static struct vec_data vec_data[] = {46[VEC_SVE] = {47.name = "SVE",48.hwcap_type = AT_HWCAP,49.hwcap = HWCAP_SVE,50.rdvl = rdvl_sve,51.rdvl_binary = "./rdvl-sve",52.prctl_get = PR_SVE_GET_VL,53.prctl_set = PR_SVE_SET_VL,54.default_vl_file = "/proc/sys/abi/sve_default_vector_length",55},56[VEC_SME] = {57.name = "SME",58.hwcap_type = AT_HWCAP2,59.hwcap = HWCAP2_SME,60.rdvl = rdvl_sme,61.rdvl_binary = "./rdvl-sme",62.prctl_get = PR_SME_GET_VL,63.prctl_set = PR_SME_SET_VL,64.default_vl_file = "/proc/sys/abi/sme_default_vector_length",65},66};6768static bool vec_type_supported(struct vec_data *data)69{70return getauxval(data->hwcap_type) & data->hwcap;71}7273static int stdio_read_integer(FILE *f, const char *what, int *val)74{75int n = 0;76int ret;7778ret = fscanf(f, "%d%*1[\n]%n", val, &n);79if (ret < 1 || n < 1) {80ksft_print_msg("failed to parse integer from %s\n", what);81return -1;82}8384return 0;85}8687/* Start a new process and return the vector length it sees */88static int get_child_rdvl(struct vec_data *data)89{90FILE *out;91int pipefd[2];92pid_t pid, child;93int read_vl, ret;9495ret = pipe(pipefd);96if (ret == -1) {97ksft_print_msg("pipe() failed: %d (%s)\n",98errno, strerror(errno));99return -1;100}101102fflush(stdout);103104child = fork();105if (child == -1) {106ksft_print_msg("fork() failed: %d (%s)\n",107errno, strerror(errno));108close(pipefd[0]);109close(pipefd[1]);110return -1;111}112113/* Child: put vector length on the pipe */114if (child == 0) {115/*116* Replace stdout with the pipe, errors to stderr from117* here as kselftest prints to stdout.118*/119ret = dup2(pipefd[1], 1);120if (ret == -1) {121fprintf(stderr, "dup2() %d\n", errno);122exit(EXIT_FAILURE);123}124125/* exec() a new binary which puts the VL on stdout */126ret = execl(data->rdvl_binary, data->rdvl_binary, NULL);127fprintf(stderr, "execl(%s) failed: %d (%s)\n",128data->rdvl_binary, errno, strerror(errno));129130exit(EXIT_FAILURE);131}132133close(pipefd[1]);134135/* Parent; wait for the exit status from the child & verify it */136do {137pid = wait(&ret);138if (pid == -1) {139ksft_print_msg("wait() failed: %d (%s)\n",140errno, strerror(errno));141close(pipefd[0]);142return -1;143}144} while (pid != child);145146assert(pid == child);147148if (!WIFEXITED(ret)) {149ksft_print_msg("child exited abnormally\n");150close(pipefd[0]);151return -1;152}153154if (WEXITSTATUS(ret) != 0) {155ksft_print_msg("child returned error %d\n",156WEXITSTATUS(ret));157close(pipefd[0]);158return -1;159}160161out = fdopen(pipefd[0], "r");162if (!out) {163ksft_print_msg("failed to open child stdout\n");164close(pipefd[0]);165return -1;166}167168ret = stdio_read_integer(out, "child", &read_vl);169fclose(out);170if (ret != 0)171return ret;172173return read_vl;174}175176static int file_read_integer(const char *name, int *val)177{178FILE *f;179int ret;180181f = fopen(name, "r");182if (!f) {183ksft_test_result_fail("Unable to open %s: %d (%s)\n",184name, errno,185strerror(errno));186return -1;187}188189ret = stdio_read_integer(f, name, val);190fclose(f);191192return ret;193}194195static int file_write_integer(const char *name, int val)196{197FILE *f;198199f = fopen(name, "w");200if (!f) {201ksft_test_result_fail("Unable to open %s: %d (%s)\n",202name, errno,203strerror(errno));204return -1;205}206207fprintf(f, "%d", val);208fclose(f);209210return 0;211}212213/*214* Verify that we can read the default VL via proc, checking that it215* is set in a freshly spawned child.216*/217static void proc_read_default(struct vec_data *data)218{219int default_vl, child_vl, ret;220221ret = file_read_integer(data->default_vl_file, &default_vl);222if (ret != 0)223return;224225/* Is this the actual default seen by new processes? */226child_vl = get_child_rdvl(data);227if (child_vl != default_vl) {228ksft_test_result_fail("%s is %d but child VL is %d\n",229data->default_vl_file,230default_vl, child_vl);231return;232}233234ksft_test_result_pass("%s default vector length %d\n", data->name,235default_vl);236data->default_vl = default_vl;237}238239/* Verify that we can write a minimum value and have it take effect */240static void proc_write_min(struct vec_data *data)241{242int ret, new_default, child_vl;243244if (geteuid() != 0) {245ksft_test_result_skip("Need to be root to write to /proc\n");246return;247}248249ret = file_write_integer(data->default_vl_file, ARCH_MIN_VL);250if (ret != 0)251return;252253/* What was the new value? */254ret = file_read_integer(data->default_vl_file, &new_default);255if (ret != 0)256return;257258/* Did it take effect in a new process? */259child_vl = get_child_rdvl(data);260if (child_vl != new_default) {261ksft_test_result_fail("%s is %d but child VL is %d\n",262data->default_vl_file,263new_default, child_vl);264return;265}266267ksft_test_result_pass("%s minimum vector length %d\n", data->name,268new_default);269data->min_vl = new_default;270271file_write_integer(data->default_vl_file, data->default_vl);272}273274/* Verify that we can write a maximum value and have it take effect */275static void proc_write_max(struct vec_data *data)276{277int ret, new_default, child_vl;278279if (geteuid() != 0) {280ksft_test_result_skip("Need to be root to write to /proc\n");281return;282}283284/* -1 is accepted by the /proc interface as the maximum VL */285ret = file_write_integer(data->default_vl_file, -1);286if (ret != 0)287return;288289/* What was the new value? */290ret = file_read_integer(data->default_vl_file, &new_default);291if (ret != 0)292return;293294/* Did it take effect in a new process? */295child_vl = get_child_rdvl(data);296if (child_vl != new_default) {297ksft_test_result_fail("%s is %d but child VL is %d\n",298data->default_vl_file,299new_default, child_vl);300return;301}302303ksft_test_result_pass("%s maximum vector length %d\n", data->name,304new_default);305data->max_vl = new_default;306307file_write_integer(data->default_vl_file, data->default_vl);308}309310/* Can we read back a VL from prctl? */311static void prctl_get(struct vec_data *data)312{313int ret;314315ret = prctl(data->prctl_get);316if (ret == -1) {317ksft_test_result_fail("%s prctl() read failed: %d (%s)\n",318data->name, errno, strerror(errno));319return;320}321322/* Mask out any flags */323ret &= PR_SVE_VL_LEN_MASK;324325/* Is that what we can read back directly? */326if (ret == data->rdvl())327ksft_test_result_pass("%s current VL is %d\n",328data->name, ret);329else330ksft_test_result_fail("%s prctl() VL %d but RDVL is %d\n",331data->name, ret, data->rdvl());332}333334/* Does the prctl let us set the VL we already have? */335static void prctl_set_same(struct vec_data *data)336{337int cur_vl = data->rdvl();338int ret;339340ret = prctl(data->prctl_set, cur_vl);341if (ret < 0) {342ksft_test_result_fail("%s prctl set failed: %d (%s)\n",343data->name, errno, strerror(errno));344return;345}346347ksft_test_result(cur_vl == data->rdvl(),348"%s set VL %d and have VL %d\n",349data->name, cur_vl, data->rdvl());350}351352/* Can we set a new VL for this process? */353static void prctl_set(struct vec_data *data)354{355int ret;356357if (data->min_vl == data->max_vl) {358ksft_test_result_skip("%s only one VL supported\n",359data->name);360return;361}362363/* Try to set the minimum VL */364ret = prctl(data->prctl_set, data->min_vl);365if (ret < 0) {366ksft_test_result_fail("%s prctl set failed for %d: %d (%s)\n",367data->name, data->min_vl,368errno, strerror(errno));369return;370}371372if ((ret & PR_SVE_VL_LEN_MASK) != data->min_vl) {373ksft_test_result_fail("%s prctl set %d but return value is %d\n",374data->name, data->min_vl, data->rdvl());375return;376}377378if (data->rdvl() != data->min_vl) {379ksft_test_result_fail("%s set %d but RDVL is %d\n",380data->name, data->min_vl, data->rdvl());381return;382}383384/* Try to set the maximum VL */385ret = prctl(data->prctl_set, data->max_vl);386if (ret < 0) {387ksft_test_result_fail("%s prctl set failed for %d: %d (%s)\n",388data->name, data->max_vl,389errno, strerror(errno));390return;391}392393if ((ret & PR_SVE_VL_LEN_MASK) != data->max_vl) {394ksft_test_result_fail("%s prctl() set %d but return value is %d\n",395data->name, data->max_vl, data->rdvl());396return;397}398399/* The _INHERIT flag should not be present when we read the VL */400ret = prctl(data->prctl_get);401if (ret == -1) {402ksft_test_result_fail("%s prctl() read failed: %d (%s)\n",403data->name, errno, strerror(errno));404return;405}406407if (ret & PR_SVE_VL_INHERIT) {408ksft_test_result_fail("%s prctl() reports _INHERIT\n",409data->name);410return;411}412413ksft_test_result_pass("%s prctl() set min/max\n", data->name);414}415416/* If we didn't request it a new VL shouldn't affect the child */417static void prctl_set_no_child(struct vec_data *data)418{419int ret, child_vl;420421if (data->min_vl == data->max_vl) {422ksft_test_result_skip("%s only one VL supported\n",423data->name);424return;425}426427ret = prctl(data->prctl_set, data->min_vl);428if (ret < 0) {429ksft_test_result_fail("%s prctl set failed for %d: %d (%s)\n",430data->name, data->min_vl,431errno, strerror(errno));432return;433}434435/* Ensure the default VL is different */436ret = file_write_integer(data->default_vl_file, data->max_vl);437if (ret != 0)438return;439440/* Check that the child has the default we just set */441child_vl = get_child_rdvl(data);442if (child_vl != data->max_vl) {443ksft_test_result_fail("%s is %d but child VL is %d\n",444data->default_vl_file,445data->max_vl, child_vl);446return;447}448449ksft_test_result_pass("%s vector length used default\n", data->name);450451file_write_integer(data->default_vl_file, data->default_vl);452}453454/* If we didn't request it a new VL shouldn't affect the child */455static void prctl_set_for_child(struct vec_data *data)456{457int ret, child_vl;458459if (data->min_vl == data->max_vl) {460ksft_test_result_skip("%s only one VL supported\n",461data->name);462return;463}464465ret = prctl(data->prctl_set, data->min_vl | PR_SVE_VL_INHERIT);466if (ret < 0) {467ksft_test_result_fail("%s prctl set failed for %d: %d (%s)\n",468data->name, data->min_vl,469errno, strerror(errno));470return;471}472473/* The _INHERIT flag should be present when we read the VL */474ret = prctl(data->prctl_get);475if (ret == -1) {476ksft_test_result_fail("%s prctl() read failed: %d (%s)\n",477data->name, errno, strerror(errno));478return;479}480if (!(ret & PR_SVE_VL_INHERIT)) {481ksft_test_result_fail("%s prctl() does not report _INHERIT\n",482data->name);483return;484}485486/* Ensure the default VL is different */487ret = file_write_integer(data->default_vl_file, data->max_vl);488if (ret != 0)489return;490491/* Check that the child inherited our VL */492child_vl = get_child_rdvl(data);493if (child_vl != data->min_vl) {494ksft_test_result_fail("%s is %d but child VL is %d\n",495data->default_vl_file,496data->min_vl, child_vl);497return;498}499500ksft_test_result_pass("%s vector length was inherited\n", data->name);501502file_write_integer(data->default_vl_file, data->default_vl);503}504505/* _ONEXEC takes effect only in the child process */506static void prctl_set_onexec(struct vec_data *data)507{508int ret, child_vl;509510if (data->min_vl == data->max_vl) {511ksft_test_result_skip("%s only one VL supported\n",512data->name);513return;514}515516/* Set a known value for the default and our current VL */517ret = file_write_integer(data->default_vl_file, data->max_vl);518if (ret != 0)519return;520521ret = prctl(data->prctl_set, data->max_vl);522if (ret < 0) {523ksft_test_result_fail("%s prctl set failed for %d: %d (%s)\n",524data->name, data->min_vl,525errno, strerror(errno));526return;527}528529/* Set a different value for the child to have on exec */530ret = prctl(data->prctl_set, data->min_vl | PR_SVE_SET_VL_ONEXEC);531if (ret < 0) {532ksft_test_result_fail("%s prctl set failed for %d: %d (%s)\n",533data->name, data->min_vl,534errno, strerror(errno));535return;536}537538/* Our current VL should stay the same */539if (data->rdvl() != data->max_vl) {540ksft_test_result_fail("%s VL changed by _ONEXEC prctl()\n",541data->name);542return;543}544545/* Check that the child inherited our VL */546child_vl = get_child_rdvl(data);547if (child_vl != data->min_vl) {548ksft_test_result_fail("Set %d _ONEXEC but child VL is %d\n",549data->min_vl, child_vl);550return;551}552553ksft_test_result_pass("%s vector length set on exec\n", data->name);554555file_write_integer(data->default_vl_file, data->default_vl);556}557558/* For each VQ verify that setting via prctl() does the right thing */559static void prctl_set_all_vqs(struct vec_data *data)560{561int ret, vq, vl, new_vl, i;562int orig_vls[ARRAY_SIZE(vec_data)];563int errors = 0;564565if (!data->min_vl || !data->max_vl) {566ksft_test_result_skip("%s Failed to enumerate VLs, not testing VL setting\n",567data->name);568return;569}570571for (i = 0; i < ARRAY_SIZE(vec_data); i++) {572if (!vec_type_supported(&vec_data[i]))573continue;574orig_vls[i] = vec_data[i].rdvl();575}576577for (vq = SVE_VQ_MIN; vq <= SVE_VQ_MAX; vq++) {578vl = sve_vl_from_vq(vq);579580/* Attempt to set the VL */581ret = prctl(data->prctl_set, vl);582if (ret < 0) {583errors++;584ksft_print_msg("%s prctl set failed for %d: %d (%s)\n",585data->name, vl,586errno, strerror(errno));587continue;588}589590new_vl = ret & PR_SVE_VL_LEN_MASK;591592/* Check that we actually have the reported new VL */593if (data->rdvl() != new_vl) {594ksft_print_msg("Set %s VL %d but RDVL reports %d\n",595data->name, new_vl, data->rdvl());596errors++;597}598599/* Did any other VLs change? */600for (i = 0; i < ARRAY_SIZE(vec_data); i++) {601if (&vec_data[i] == data)602continue;603604if (!vec_type_supported(&vec_data[i]))605continue;606607if (vec_data[i].rdvl() != orig_vls[i]) {608ksft_print_msg("%s VL changed from %d to %d\n",609vec_data[i].name, orig_vls[i],610vec_data[i].rdvl());611errors++;612}613}614615/* Was that the VL we asked for? */616if (new_vl == vl)617continue;618619/* Should round up to the minimum VL if below it */620if (vl < data->min_vl) {621if (new_vl != data->min_vl) {622ksft_print_msg("%s VL %d returned %d not minimum %d\n",623data->name, vl, new_vl,624data->min_vl);625errors++;626}627628continue;629}630631/* Should round down to maximum VL if above it */632if (vl > data->max_vl) {633if (new_vl != data->max_vl) {634ksft_print_msg("%s VL %d returned %d not maximum %d\n",635data->name, vl, new_vl,636data->max_vl);637errors++;638}639640continue;641}642643/* Otherwise we should've rounded down */644if (!(new_vl < vl)) {645ksft_print_msg("%s VL %d returned %d, did not round down\n",646data->name, vl, new_vl);647errors++;648649continue;650}651}652653ksft_test_result(errors == 0, "%s prctl() set all VLs, %d errors\n",654data->name, errors);655}656657typedef void (*test_type)(struct vec_data *);658659static const test_type tests[] = {660/*661* The default/min/max tests must be first and in this order662* to provide data for other tests.663*/664proc_read_default,665proc_write_min,666proc_write_max,667668prctl_get,669prctl_set_same,670prctl_set,671prctl_set_no_child,672prctl_set_for_child,673prctl_set_onexec,674prctl_set_all_vqs,675};676677static inline void smstart(void)678{679asm volatile("msr S0_3_C4_C7_3, xzr");680}681682static inline void smstart_sm(void)683{684asm volatile("msr S0_3_C4_C3_3, xzr");685}686687static inline void smstop(void)688{689asm volatile("msr S0_3_C4_C6_3, xzr");690}691692693/*694* Verify we can change the SVE vector length while SME is active and695* continue to use SME afterwards.696*/697static void change_sve_with_za(void)698{699struct vec_data *sve_data = &vec_data[VEC_SVE];700bool pass = true;701int ret, i;702703if (sve_data->min_vl == sve_data->max_vl) {704ksft_print_msg("Only one SVE VL supported, can't change\n");705ksft_test_result_skip("change_sve_while_sme\n");706return;707}708709/* Ensure we will trigger a change when we set the maximum */710ret = prctl(sve_data->prctl_set, sve_data->min_vl);711if (ret != sve_data->min_vl) {712ksft_print_msg("Failed to set SVE VL %d: %d\n",713sve_data->min_vl, ret);714pass = false;715}716717/* Enable SM and ZA */718smstart();719720/* Trigger another VL change */721ret = prctl(sve_data->prctl_set, sve_data->max_vl);722if (ret != sve_data->max_vl) {723ksft_print_msg("Failed to set SVE VL %d: %d\n",724sve_data->max_vl, ret);725pass = false;726}727728/*729* Spin for a bit with SM enabled to try to trigger another730* save/restore. We can't use syscalls without exiting731* streaming mode.732*/733for (i = 0; i < 100000000; i++)734smstart_sm();735736/*737* TODO: Verify that ZA was preserved over the VL change and738* spin.739*/740741/* Clean up after ourselves */742smstop();743ret = prctl(sve_data->prctl_set, sve_data->default_vl);744if (ret != sve_data->default_vl) {745ksft_print_msg("Failed to restore SVE VL %d: %d\n",746sve_data->default_vl, ret);747pass = false;748}749750ksft_test_result(pass, "change_sve_with_za\n");751}752753typedef void (*test_all_type)(void);754755static const struct {756const char *name;757test_all_type test;758} all_types_tests[] = {759{ "change_sve_with_za", change_sve_with_za },760};761762int main(void)763{764bool all_supported = true;765int i, j;766767ksft_print_header();768ksft_set_plan(ARRAY_SIZE(tests) * ARRAY_SIZE(vec_data) +769ARRAY_SIZE(all_types_tests));770771for (i = 0; i < ARRAY_SIZE(vec_data); i++) {772struct vec_data *data = &vec_data[i];773unsigned long supported;774775supported = vec_type_supported(data);776if (!supported)777all_supported = false;778779for (j = 0; j < ARRAY_SIZE(tests); j++) {780if (supported)781tests[j](data);782else783ksft_test_result_skip("%s not supported\n",784data->name);785}786}787788for (i = 0; i < ARRAY_SIZE(all_types_tests); i++) {789if (all_supported)790all_types_tests[i].test();791else792ksft_test_result_skip("%s\n", all_types_tests[i].name);793}794795ksft_exit_pass();796}797798799