Merge tag 'arm64-upstream' of git://git.kernel.org/pub/scm/linux/kernel/git/arm64/linux
Pull arm64 updates from Catalin Marinas: - Support for 32-bit tasks on asymmetric AArch32 systems (on top of the scheduler changes merged via the tip tree). - More entry.S clean-ups and conversion to C. - MTE updates: allow a preferred tag checking mode to be set per CPU (the overhead of synchronous mode is smaller for some CPUs than others); optimisations for kernel entry/exit path; optionally disable MTE on the kernel command line. - Kselftest improvements for SVE and signal handling, PtrAuth. - Fix unlikely race where a TLBI could use stale ASID on an ASID roll-over (found by inspection). - Miscellaneous fixes: disable trapping of PMSNEVFR_EL1 to higher exception levels; drop unnecessary sigdelsetmask() call in the signal32 handling; remove BUG_ON when failing to allocate SVE state (just signal the process); SYM_CODE annotations. - Other trivial clean-ups: use macros instead of magic numbers, remove redundant returns, typos. * tag 'arm64-upstream' of git://git.kernel.org/pub/scm/linux/kernel/git/arm64/linux: (56 commits) arm64: Do not trap PMSNEVFR_EL1 arm64: mm: fix comment typo of pud_offset_phys() arm64: signal32: Drop pointless call to sigdelsetmask() arm64/sve: Better handle failure to allocate SVE register storage arm64: Document the requirement for SCR_EL3.HCE arm64: head: avoid over-mapping in map_memory arm64/sve: Add a comment documenting the binutils needed for SVE asm arm64/sve: Add some comments for sve_save/load_state() kselftest/arm64: signal: Add a TODO list for signal handling tests kselftest/arm64: signal: Add test case for SVE register state in signals kselftest/arm64: signal: Verify that signals can't change the SVE vector length kselftest/arm64: signal: Check SVE signal frame shows expected vector length kselftest/arm64: signal: Support signal frames with SVE register data kselftest/arm64: signal: Add SVE to the set of features we can check for arm64: replace in_irq() with in_hardirq() kselftest/arm64: pac: Fix skipping of tests on systems without PAC Documentation: arm64: describe asymmetric 32-bit support arm64: Remove logic to kill 32-bit tasks on 64-bit-only cores arm64: Hook up cmdline parameter to allow mismatched 32-bit EL0 arm64: Advertise CPUs capable of running 32-bit applications in sysfs ...
This commit is contained in:
2
tools/testing/selftests/arm64/fp/.gitignore
vendored
2
tools/testing/selftests/arm64/fp/.gitignore
vendored
@@ -1,5 +1,7 @@
|
||||
fpsimd-test
|
||||
rdvl-sve
|
||||
sve-probe-vls
|
||||
sve-ptrace
|
||||
sve-test
|
||||
vec-syscfg
|
||||
vlset
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
CFLAGS += -I../../../../../usr/include/
|
||||
TEST_GEN_PROGS := sve-ptrace sve-probe-vls
|
||||
TEST_PROGS_EXTENDED := fpsimd-test fpsimd-stress sve-test sve-stress vlset
|
||||
TEST_GEN_PROGS := sve-ptrace sve-probe-vls vec-syscfg
|
||||
TEST_PROGS_EXTENDED := fpsimd-test fpsimd-stress \
|
||||
rdvl-sve \
|
||||
sve-test sve-stress \
|
||||
vlset
|
||||
|
||||
all: $(TEST_GEN_PROGS) $(TEST_PROGS_EXTENDED)
|
||||
|
||||
fpsimd-test: fpsimd-test.o
|
||||
$(CC) -nostdlib $^ -o $@
|
||||
rdvl-sve: rdvl-sve.o rdvl.o
|
||||
sve-ptrace: sve-ptrace.o sve-ptrace-asm.o
|
||||
sve-probe-vls: sve-probe-vls.o
|
||||
sve-probe-vls: sve-probe-vls.o rdvl.o
|
||||
sve-test: sve-test.o
|
||||
$(CC) -nostdlib $^ -o $@
|
||||
vec-syscfg: vec-syscfg.o rdvl.o
|
||||
vlset: vlset.o
|
||||
|
||||
include ../../lib.mk
|
||||
|
||||
4
tools/testing/selftests/arm64/fp/TODO
Normal file
4
tools/testing/selftests/arm64/fp/TODO
Normal file
@@ -0,0 +1,4 @@
|
||||
- Test unsupported values in the ABIs.
|
||||
- More coverage for ptrace (eg, vector length conversions).
|
||||
- Coverage for signals.
|
||||
- Test PR_SVE_VL_INHERITY after a double fork.
|
||||
14
tools/testing/selftests/arm64/fp/rdvl-sve.c
Normal file
14
tools/testing/selftests/arm64/fp/rdvl-sve.c
Normal file
@@ -0,0 +1,14 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "rdvl.h"
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int vl = rdvl_sve();
|
||||
|
||||
printf("%d\n", vl);
|
||||
|
||||
return 0;
|
||||
}
|
||||
10
tools/testing/selftests/arm64/fp/rdvl.S
Normal file
10
tools/testing/selftests/arm64/fp/rdvl.S
Normal file
@@ -0,0 +1,10 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
// Copyright (C) 2021 ARM Limited.
|
||||
|
||||
.arch_extension sve
|
||||
|
||||
.globl rdvl_sve
|
||||
rdvl_sve:
|
||||
hint 34 // BTI C
|
||||
rdvl x0, #1
|
||||
ret
|
||||
8
tools/testing/selftests/arm64/fp/rdvl.h
Normal file
8
tools/testing/selftests/arm64/fp/rdvl.h
Normal file
@@ -0,0 +1,8 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
|
||||
#ifndef RDVL_H
|
||||
#define RDVL_H
|
||||
|
||||
int rdvl_sve(void);
|
||||
|
||||
#endif
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <asm/sigcontext.h>
|
||||
|
||||
#include "../../kselftest.h"
|
||||
#include "rdvl.h"
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
@@ -38,6 +39,10 @@ int main(int argc, char **argv)
|
||||
|
||||
vl &= PR_SVE_VL_LEN_MASK;
|
||||
|
||||
if (rdvl_sve() != vl)
|
||||
ksft_exit_fail_msg("PR_SVE_SET_VL reports %d, RDVL %d\n",
|
||||
vl, rdvl_sve());
|
||||
|
||||
if (!sve_vl_valid(vl))
|
||||
ksft_exit_fail_msg("VL %d invalid\n", vl);
|
||||
vq = sve_vq_from_vl(vl);
|
||||
|
||||
593
tools/testing/selftests/arm64/fp/vec-syscfg.c
Normal file
593
tools/testing/selftests/arm64/fp/vec-syscfg.c
Normal file
@@ -0,0 +1,593 @@
|
||||
// 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 <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 ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
|
||||
|
||||
#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;
|
||||
};
|
||||
|
||||
|
||||
static struct vec_data vec_data[] = {
|
||||
{
|
||||
.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",
|
||||
},
|
||||
};
|
||||
|
||||
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\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;
|
||||
int ret;
|
||||
|
||||
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);
|
||||
if (ret < 0) {
|
||||
ksft_test_result_fail("Error writing %d to %s\n",
|
||||
val, name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (cur_vl != 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());
|
||||
}
|
||||
|
||||
/* 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);
|
||||
}
|
||||
|
||||
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,
|
||||
prctl_set_no_child,
|
||||
prctl_set_for_child,
|
||||
prctl_set_onexec,
|
||||
};
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int i, j;
|
||||
|
||||
ksft_print_header();
|
||||
ksft_set_plan(ARRAY_SIZE(tests) * ARRAY_SIZE(vec_data));
|
||||
|
||||
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;
|
||||
|
||||
for (j = 0; j < ARRAY_SIZE(tests); j++) {
|
||||
if (supported)
|
||||
tests[j](data);
|
||||
else
|
||||
ksft_test_result_skip("%s not supported\n",
|
||||
data->name);
|
||||
}
|
||||
}
|
||||
|
||||
ksft_exit_pass();
|
||||
}
|
||||
1
tools/testing/selftests/arm64/mte/.gitignore
vendored
1
tools/testing/selftests/arm64/mte/.gitignore
vendored
@@ -1,4 +1,5 @@
|
||||
check_buffer_fill
|
||||
check_gcr_el1_cswitch
|
||||
check_tags_inclusion
|
||||
check_child_memory
|
||||
check_mmap_options
|
||||
|
||||
@@ -298,7 +298,7 @@ int mte_default_setup(void)
|
||||
int ret;
|
||||
|
||||
if (!(hwcaps2 & HWCAP2_MTE)) {
|
||||
ksft_print_msg("FAIL: MTE features unavailable\n");
|
||||
ksft_print_msg("SKIP: MTE features unavailable\n");
|
||||
return KSFT_SKIP;
|
||||
}
|
||||
/* Get current mte mode */
|
||||
|
||||
@@ -25,13 +25,15 @@
|
||||
do { \
|
||||
unsigned long hwcaps = getauxval(AT_HWCAP); \
|
||||
/* data key instructions are not in NOP space. This prevents a SIGILL */ \
|
||||
ASSERT_NE(0, hwcaps & HWCAP_PACA) TH_LOG("PAUTH not enabled"); \
|
||||
if (!(hwcaps & HWCAP_PACA)) \
|
||||
SKIP(return, "PAUTH not enabled"); \
|
||||
} while (0)
|
||||
#define ASSERT_GENERIC_PAUTH_ENABLED() \
|
||||
do { \
|
||||
unsigned long hwcaps = getauxval(AT_HWCAP); \
|
||||
/* generic key instructions are not in NOP space. This prevents a SIGILL */ \
|
||||
ASSERT_NE(0, hwcaps & HWCAP_PACG) TH_LOG("Generic PAUTH not enabled"); \
|
||||
if (!(hwcaps & HWCAP_PACG)) \
|
||||
SKIP(return, "Generic PAUTH not enabled"); \
|
||||
} while (0)
|
||||
|
||||
void sign_specific(struct signatures *sign, size_t val)
|
||||
@@ -256,7 +258,7 @@ TEST(single_thread_different_keys)
|
||||
unsigned long hwcaps = getauxval(AT_HWCAP);
|
||||
|
||||
/* generic and data key instructions are not in NOP space. This prevents a SIGILL */
|
||||
ASSERT_NE(0, hwcaps & HWCAP_PACA) TH_LOG("PAUTH not enabled");
|
||||
ASSERT_PAUTH_ENABLED();
|
||||
if (!(hwcaps & HWCAP_PACG)) {
|
||||
TH_LOG("WARNING: Generic PAUTH not enabled. Skipping generic key checks");
|
||||
nkeys = NKEYS - 1;
|
||||
@@ -299,7 +301,7 @@ TEST(exec_changed_keys)
|
||||
unsigned long hwcaps = getauxval(AT_HWCAP);
|
||||
|
||||
/* generic and data key instructions are not in NOP space. This prevents a SIGILL */
|
||||
ASSERT_NE(0, hwcaps & HWCAP_PACA) TH_LOG("PAUTH not enabled");
|
||||
ASSERT_PAUTH_ENABLED();
|
||||
if (!(hwcaps & HWCAP_PACG)) {
|
||||
TH_LOG("WARNING: Generic PAUTH not enabled. Skipping generic key checks");
|
||||
nkeys = NKEYS - 1;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
mangle_*
|
||||
fake_sigreturn_*
|
||||
sve_*
|
||||
!*.[ch]
|
||||
|
||||
@@ -33,10 +33,12 @@
|
||||
*/
|
||||
enum {
|
||||
FSSBS_BIT,
|
||||
FSVE_BIT,
|
||||
FMAX_END
|
||||
};
|
||||
|
||||
#define FEAT_SSBS (1UL << FSSBS_BIT)
|
||||
#define FEAT_SVE (1UL << FSVE_BIT)
|
||||
|
||||
/*
|
||||
* A descriptor used to describe and configure a test case.
|
||||
|
||||
@@ -26,6 +26,7 @@ static int sig_copyctx = SIGTRAP;
|
||||
|
||||
static char const *const feats_names[FMAX_END] = {
|
||||
" SSBS ",
|
||||
" SVE ",
|
||||
};
|
||||
|
||||
#define MAX_FEATS_SZ 128
|
||||
@@ -263,6 +264,8 @@ int test_init(struct tdescr *td)
|
||||
*/
|
||||
if (getauxval(AT_HWCAP) & HWCAP_SSBS)
|
||||
td->feats_supported |= FEAT_SSBS;
|
||||
if (getauxval(AT_HWCAP) & HWCAP_SVE)
|
||||
td->feats_supported |= FEAT_SVE;
|
||||
if (feats_ok(td))
|
||||
fprintf(stderr,
|
||||
"Required Features: [%s] supported\n",
|
||||
|
||||
2
tools/testing/selftests/arm64/signal/testcases/TODO
Normal file
2
tools/testing/selftests/arm64/signal/testcases/TODO
Normal file
@@ -0,0 +1,2 @@
|
||||
- Validate that register contents are saved and restored as expected.
|
||||
- Support and validate extra_context.
|
||||
@@ -0,0 +1,92 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2021 ARM Limited
|
||||
*
|
||||
* Attempt to change the SVE vector length in a signal hander, this is not
|
||||
* supported and is expected to segfault.
|
||||
*/
|
||||
|
||||
#include <signal.h>
|
||||
#include <ucontext.h>
|
||||
#include <sys/prctl.h>
|
||||
|
||||
#include "test_signals_utils.h"
|
||||
#include "testcases.h"
|
||||
|
||||
struct fake_sigframe sf;
|
||||
static unsigned int vls[SVE_VQ_MAX];
|
||||
unsigned int nvls = 0;
|
||||
|
||||
static bool sve_get_vls(struct tdescr *td)
|
||||
{
|
||||
int vq, vl;
|
||||
|
||||
/*
|
||||
* Enumerate up to SVE_VQ_MAX vector lengths
|
||||
*/
|
||||
for (vq = SVE_VQ_MAX; vq > 0; --vq) {
|
||||
vl = prctl(PR_SVE_SET_VL, vq * 16);
|
||||
if (vl == -1)
|
||||
return false;
|
||||
|
||||
vl &= PR_SVE_VL_LEN_MASK;
|
||||
|
||||
/* Skip missing VLs */
|
||||
vq = sve_vq_from_vl(vl);
|
||||
|
||||
vls[nvls++] = vl;
|
||||
}
|
||||
|
||||
/* We need at least two VLs */
|
||||
if (nvls < 2) {
|
||||
fprintf(stderr, "Only %d VL supported\n", nvls);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int fake_sigreturn_sve_change_vl(struct tdescr *td,
|
||||
siginfo_t *si, ucontext_t *uc)
|
||||
{
|
||||
size_t resv_sz, offset;
|
||||
struct _aarch64_ctx *head = GET_SF_RESV_HEAD(sf);
|
||||
struct sve_context *sve;
|
||||
|
||||
/* Get a signal context with a SVE frame in it */
|
||||
if (!get_current_context(td, &sf.uc))
|
||||
return 1;
|
||||
|
||||
resv_sz = GET_SF_RESV_SIZE(sf);
|
||||
head = get_header(head, SVE_MAGIC, resv_sz, &offset);
|
||||
if (!head) {
|
||||
fprintf(stderr, "No SVE context\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (head->size != sizeof(struct sve_context)) {
|
||||
fprintf(stderr, "SVE register state active, skipping\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
sve = (struct sve_context *)head;
|
||||
|
||||
/* No changes are supported; init left us at minimum VL so go to max */
|
||||
fprintf(stderr, "Attempting to change VL from %d to %d\n",
|
||||
sve->vl, vls[0]);
|
||||
sve->vl = vls[0];
|
||||
|
||||
fake_sigreturn(&sf, sizeof(sf), 0);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct tdescr tde = {
|
||||
.name = "FAKE_SIGRETURN_SVE_CHANGE",
|
||||
.descr = "Attempt to change SVE VL",
|
||||
.feats_required = FEAT_SVE,
|
||||
.sig_ok = SIGSEGV,
|
||||
.timeout = 3,
|
||||
.init = sve_get_vls,
|
||||
.run = fake_sigreturn_sve_change_vl,
|
||||
};
|
||||
126
tools/testing/selftests/arm64/signal/testcases/sve_regs.c
Normal file
126
tools/testing/selftests/arm64/signal/testcases/sve_regs.c
Normal file
@@ -0,0 +1,126 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2021 ARM Limited
|
||||
*
|
||||
* Verify that the SVE register context in signal frames is set up as
|
||||
* expected.
|
||||
*/
|
||||
|
||||
#include <signal.h>
|
||||
#include <ucontext.h>
|
||||
#include <sys/prctl.h>
|
||||
|
||||
#include "test_signals_utils.h"
|
||||
#include "testcases.h"
|
||||
|
||||
struct fake_sigframe sf;
|
||||
static unsigned int vls[SVE_VQ_MAX];
|
||||
unsigned int nvls = 0;
|
||||
|
||||
static bool sve_get_vls(struct tdescr *td)
|
||||
{
|
||||
int vq, vl;
|
||||
|
||||
/*
|
||||
* Enumerate up to SVE_VQ_MAX vector lengths
|
||||
*/
|
||||
for (vq = SVE_VQ_MAX; vq > 0; --vq) {
|
||||
vl = prctl(PR_SVE_SET_VL, vq * 16);
|
||||
if (vl == -1)
|
||||
return false;
|
||||
|
||||
vl &= PR_SVE_VL_LEN_MASK;
|
||||
|
||||
/* Skip missing VLs */
|
||||
vq = sve_vq_from_vl(vl);
|
||||
|
||||
vls[nvls++] = vl;
|
||||
}
|
||||
|
||||
/* We need at least one VL */
|
||||
if (nvls < 1) {
|
||||
fprintf(stderr, "Only %d VL supported\n", nvls);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void setup_sve_regs(void)
|
||||
{
|
||||
/* RDVL x16, #1 so we should have SVE regs; real data is TODO */
|
||||
asm volatile(".inst 0x04bf5030" : : : "x16" );
|
||||
}
|
||||
|
||||
static int do_one_sve_vl(struct tdescr *td, siginfo_t *si, ucontext_t *uc,
|
||||
unsigned int vl)
|
||||
{
|
||||
size_t resv_sz, offset;
|
||||
struct _aarch64_ctx *head = GET_SF_RESV_HEAD(sf);
|
||||
struct sve_context *sve;
|
||||
|
||||
fprintf(stderr, "Testing VL %d\n", vl);
|
||||
|
||||
if (prctl(PR_SVE_SET_VL, vl) == -1) {
|
||||
fprintf(stderr, "Failed to set VL\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get a signal context which should have a SVE frame and registers
|
||||
* in it.
|
||||
*/
|
||||
setup_sve_regs();
|
||||
if (!get_current_context(td, &sf.uc))
|
||||
return 1;
|
||||
|
||||
resv_sz = GET_SF_RESV_SIZE(sf);
|
||||
head = get_header(head, SVE_MAGIC, resv_sz, &offset);
|
||||
if (!head) {
|
||||
fprintf(stderr, "No SVE context\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
sve = (struct sve_context *)head;
|
||||
if (sve->vl != vl) {
|
||||
fprintf(stderr, "Got VL %d, expected %d\n", sve->vl, vl);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* The actual size validation is done in get_current_context() */
|
||||
fprintf(stderr, "Got expected size %u and VL %d\n",
|
||||
head->size, sve->vl);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sve_regs(struct tdescr *td, siginfo_t *si, ucontext_t *uc)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < nvls; i++) {
|
||||
/*
|
||||
* TODO: the signal test helpers can't currently cope
|
||||
* with signal frames bigger than struct sigcontext,
|
||||
* skip VLs that will trigger that.
|
||||
*/
|
||||
if (vls[i] > 64)
|
||||
continue;
|
||||
|
||||
if (do_one_sve_vl(td, si, uc, vls[i]))
|
||||
return 1;
|
||||
}
|
||||
|
||||
td->pass = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct tdescr tde = {
|
||||
.name = "SVE registers",
|
||||
.descr = "Check that we get the right SVE registers reported",
|
||||
.feats_required = FEAT_SVE,
|
||||
.timeout = 3,
|
||||
.init = sve_get_vls,
|
||||
.run = sve_regs,
|
||||
};
|
||||
68
tools/testing/selftests/arm64/signal/testcases/sve_vl.c
Normal file
68
tools/testing/selftests/arm64/signal/testcases/sve_vl.c
Normal file
@@ -0,0 +1,68 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2021 ARM Limited
|
||||
*
|
||||
* Check that the SVE vector length reported in signal contexts is the
|
||||
* expected one.
|
||||
*/
|
||||
|
||||
#include <signal.h>
|
||||
#include <ucontext.h>
|
||||
#include <sys/prctl.h>
|
||||
|
||||
#include "test_signals_utils.h"
|
||||
#include "testcases.h"
|
||||
|
||||
struct fake_sigframe sf;
|
||||
unsigned int vl;
|
||||
|
||||
static bool get_sve_vl(struct tdescr *td)
|
||||
{
|
||||
int ret = prctl(PR_SVE_GET_VL);
|
||||
if (ret == -1)
|
||||
return false;
|
||||
|
||||
vl = ret;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int sve_vl(struct tdescr *td, siginfo_t *si, ucontext_t *uc)
|
||||
{
|
||||
size_t resv_sz, offset;
|
||||
struct _aarch64_ctx *head = GET_SF_RESV_HEAD(sf);
|
||||
struct sve_context *sve;
|
||||
|
||||
/* Get a signal context which should have a SVE frame in it */
|
||||
if (!get_current_context(td, &sf.uc))
|
||||
return 1;
|
||||
|
||||
resv_sz = GET_SF_RESV_SIZE(sf);
|
||||
head = get_header(head, SVE_MAGIC, resv_sz, &offset);
|
||||
if (!head) {
|
||||
fprintf(stderr, "No SVE context\n");
|
||||
return 1;
|
||||
}
|
||||
sve = (struct sve_context *)head;
|
||||
|
||||
if (sve->vl != vl) {
|
||||
fprintf(stderr, "sigframe VL %u, expected %u\n",
|
||||
sve->vl, vl);
|
||||
return 1;
|
||||
} else {
|
||||
fprintf(stderr, "got expected VL %u\n", vl);
|
||||
}
|
||||
|
||||
td->pass = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct tdescr tde = {
|
||||
.name = "SVE VL",
|
||||
.descr = "Check that we get the right SVE VL reported",
|
||||
.feats_required = FEAT_SVE,
|
||||
.timeout = 3,
|
||||
.init = get_sve_vl,
|
||||
.run = sve_vl,
|
||||
};
|
||||
@@ -50,12 +50,38 @@ bool validate_extra_context(struct extra_context *extra, char **err)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool validate_sve_context(struct sve_context *sve, char **err)
|
||||
{
|
||||
/* Size will be rounded up to a multiple of 16 bytes */
|
||||
size_t regs_size
|
||||
= ((SVE_SIG_CONTEXT_SIZE(sve_vq_from_vl(sve->vl)) + 15) / 16) * 16;
|
||||
|
||||
if (!sve || !err)
|
||||
return false;
|
||||
|
||||
/* Either a bare sve_context or a sve_context followed by regs data */
|
||||
if ((sve->head.size != sizeof(struct sve_context)) &&
|
||||
(sve->head.size != regs_size)) {
|
||||
*err = "bad size for SVE context";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!sve_vl_valid(sve->vl)) {
|
||||
*err = "SVE VL invalid";
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool validate_reserved(ucontext_t *uc, size_t resv_sz, char **err)
|
||||
{
|
||||
bool terminated = false;
|
||||
size_t offs = 0;
|
||||
int flags = 0;
|
||||
struct extra_context *extra = NULL;
|
||||
struct sve_context *sve = NULL;
|
||||
struct _aarch64_ctx *head =
|
||||
(struct _aarch64_ctx *)uc->uc_mcontext.__reserved;
|
||||
|
||||
@@ -90,9 +116,8 @@ bool validate_reserved(ucontext_t *uc, size_t resv_sz, char **err)
|
||||
case SVE_MAGIC:
|
||||
if (flags & SVE_CTX)
|
||||
*err = "Multiple SVE_MAGIC";
|
||||
else if (head->size !=
|
||||
sizeof(struct sve_context))
|
||||
*err = "Bad size for sve_context";
|
||||
/* Size is validated in validate_sve_context() */
|
||||
sve = (struct sve_context *)head;
|
||||
flags |= SVE_CTX;
|
||||
break;
|
||||
case EXTRA_MAGIC:
|
||||
@@ -137,6 +162,9 @@ bool validate_reserved(ucontext_t *uc, size_t resv_sz, char **err)
|
||||
if (flags & EXTRA_CTX)
|
||||
if (!validate_extra_context(extra, err))
|
||||
return false;
|
||||
if (flags & SVE_CTX)
|
||||
if (!validate_sve_context(sve, err))
|
||||
return false;
|
||||
|
||||
head = GET_RESV_NEXT_HEAD(head);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user