mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-12-14 06:24:53 +08:00
Merge branch 'for-next/kselftest' into for-next/core
* for-next/kselftest: (32 commits) : arm64 kselftest updates. kselftest/arm64: Explicitly build no BTI tests with BTI disabled kselftest/arm64: bti: force static linking selftests/arm64: Use switch statements in mte_common_util.c selftests/arm64: Remove casts to/from void in check_tags_inclusion selftests/arm64: Check failures to set tags in check_tags_inclusion selftests/arm64: Allow zero tags in mte_switch_mode() selftests/arm64: Log errors in verify_mte_pointer_validity() kselftest/arm64: Fix ABI header directory location selftests/arm64: Fix O= builds for the floating point tests selftests/arm64: Clean the fp helper libraries selftests/arm64: Define top_srcdir for the fp tests selftests/arm64: Use TEST_GEN_PROGS_EXTENDED in the FP Makefile kselftest/arm64: fix array_size.cocci warning selftests/arm64: Add a testcase for handling of ZA on clone() kselftest/arm64: Add SME support to syscall ABI test kselftest/arm64: Add coverage for the ZA ptrace interface kselftest/arm64: Add streaming SVE to SVE ptrace tests kselftest/arm64: signal: Add SME signal handling tests kselftest/arm64: Add stress test for SME ZA context switching kselftest/arm64: signal: Handle ZA signal context in core code ...
This commit is contained in:
commit
d6fc5db0f8
@ -17,16 +17,7 @@ top_srcdir = $(realpath ../../../../)
|
||||
# Additional include paths needed by kselftest.h and local headers
|
||||
CFLAGS += -I$(top_srcdir)/tools/testing/selftests/
|
||||
|
||||
# Guessing where the Kernel headers could have been installed
|
||||
# depending on ENV config
|
||||
ifeq ($(KBUILD_OUTPUT),)
|
||||
khdr_dir = $(top_srcdir)/usr/include
|
||||
else
|
||||
# the KSFT preferred location when KBUILD_OUTPUT is set
|
||||
khdr_dir = $(KBUILD_OUTPUT)/kselftest/usr/include
|
||||
endif
|
||||
|
||||
CFLAGS += -I$(khdr_dir)
|
||||
CFLAGS += $(KHDR_INCLUDES)
|
||||
|
||||
export CFLAGS
|
||||
export top_srcdir
|
||||
|
1
tools/testing/selftests/arm64/abi/.gitignore
vendored
1
tools/testing/selftests/arm64/abi/.gitignore
vendored
@ -1 +1,2 @@
|
||||
syscall-abi
|
||||
tpidr2
|
||||
|
@ -1,8 +1,15 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Copyright (C) 2021 ARM Limited
|
||||
|
||||
TEST_GEN_PROGS := syscall-abi
|
||||
TEST_GEN_PROGS := syscall-abi tpidr2
|
||||
|
||||
include ../../lib.mk
|
||||
|
||||
$(OUTPUT)/syscall-abi: syscall-abi.c syscall-abi-asm.S
|
||||
|
||||
# Build with nolibc since TPIDR2 is intended to be actively managed by
|
||||
# libc and we're trying to test the functionality that it depends on here.
|
||||
$(OUTPUT)/tpidr2: tpidr2.c
|
||||
$(CC) -fno-asynchronous-unwind-tables -fno-ident -s -Os -nostdlib \
|
||||
-static -include ../../../../include/nolibc/nolibc.h \
|
||||
-ffreestanding -Wall $^ -o $@ -lgcc
|
||||
|
@ -9,15 +9,42 @@
|
||||
// invoked is configured in x8 of the input GPR data.
|
||||
//
|
||||
// x0: SVE VL, 0 for FP only
|
||||
// x1: SME VL
|
||||
//
|
||||
// GPRs: gpr_in, gpr_out
|
||||
// FPRs: fpr_in, fpr_out
|
||||
// Zn: z_in, z_out
|
||||
// Pn: p_in, p_out
|
||||
// FFR: ffr_in, ffr_out
|
||||
// ZA: za_in, za_out
|
||||
// SVCR: svcr_in, svcr_out
|
||||
|
||||
#include "syscall-abi.h"
|
||||
|
||||
.arch_extension sve
|
||||
|
||||
/*
|
||||
* LDR (vector to ZA array):
|
||||
* LDR ZA[\nw, #\offset], [X\nxbase, #\offset, MUL VL]
|
||||
*/
|
||||
.macro _ldr_za nw, nxbase, offset=0
|
||||
.inst 0xe1000000 \
|
||||
| (((\nw) & 3) << 13) \
|
||||
| ((\nxbase) << 5) \
|
||||
| ((\offset) & 7)
|
||||
.endm
|
||||
|
||||
/*
|
||||
* STR (vector from ZA array):
|
||||
* STR ZA[\nw, #\offset], [X\nxbase, #\offset, MUL VL]
|
||||
*/
|
||||
.macro _str_za nw, nxbase, offset=0
|
||||
.inst 0xe1200000 \
|
||||
| (((\nw) & 3) << 13) \
|
||||
| ((\nxbase) << 5) \
|
||||
| ((\offset) & 7)
|
||||
.endm
|
||||
|
||||
.globl do_syscall
|
||||
do_syscall:
|
||||
// Store callee saved registers x19-x29 (80 bytes) plus x0 and x1
|
||||
@ -30,6 +57,24 @@ do_syscall:
|
||||
stp x25, x26, [sp, #80]
|
||||
stp x27, x28, [sp, #96]
|
||||
|
||||
// Set SVCR if we're doing SME
|
||||
cbz x1, 1f
|
||||
adrp x2, svcr_in
|
||||
ldr x2, [x2, :lo12:svcr_in]
|
||||
msr S3_3_C4_C2_2, x2
|
||||
1:
|
||||
|
||||
// Load ZA if it's enabled - uses x12 as scratch due to SME LDR
|
||||
tbz x2, #SVCR_ZA_SHIFT, 1f
|
||||
mov w12, #0
|
||||
ldr x2, =za_in
|
||||
2: _ldr_za 12, 2
|
||||
add x2, x2, x1
|
||||
add x12, x12, #1
|
||||
cmp x1, x12
|
||||
bne 2b
|
||||
1:
|
||||
|
||||
// Load GPRs x8-x28, and save our SP/FP for later comparison
|
||||
ldr x2, =gpr_in
|
||||
add x2, x2, #64
|
||||
@ -68,7 +113,7 @@ do_syscall:
|
||||
ldp q30, q31, [x2, #16 * 30]
|
||||
1:
|
||||
|
||||
// Load the SVE registers if we're doing SVE
|
||||
// Load the SVE registers if we're doing SVE/SME
|
||||
cbz x0, 1f
|
||||
|
||||
ldr x2, =z_in
|
||||
@ -105,9 +150,14 @@ do_syscall:
|
||||
ldr z30, [x2, #30, MUL VL]
|
||||
ldr z31, [x2, #31, MUL VL]
|
||||
|
||||
// Only set a non-zero FFR, test patterns must be zero since the
|
||||
// syscall should clear it - this lets us handle FA64.
|
||||
ldr x2, =ffr_in
|
||||
ldr p0, [x2, #0]
|
||||
ldr x2, [x2, #0]
|
||||
cbz x2, 2f
|
||||
wrffr p0.b
|
||||
2:
|
||||
|
||||
ldr x2, =p_in
|
||||
ldr p0, [x2, #0, MUL VL]
|
||||
@ -169,6 +219,24 @@ do_syscall:
|
||||
stp q28, q29, [x2, #16 * 28]
|
||||
stp q30, q31, [x2, #16 * 30]
|
||||
|
||||
// Save SVCR if we're doing SME
|
||||
cbz x1, 1f
|
||||
mrs x2, S3_3_C4_C2_2
|
||||
adrp x3, svcr_out
|
||||
str x2, [x3, :lo12:svcr_out]
|
||||
1:
|
||||
|
||||
// Save ZA if it's enabled - uses x12 as scratch due to SME STR
|
||||
tbz x2, #SVCR_ZA_SHIFT, 1f
|
||||
mov w12, #0
|
||||
ldr x2, =za_out
|
||||
2: _str_za 12, 2
|
||||
add x2, x2, x1
|
||||
add x12, x12, #1
|
||||
cmp x1, x12
|
||||
bne 2b
|
||||
1:
|
||||
|
||||
// Save the SVE state if we have some
|
||||
cbz x0, 1f
|
||||
|
||||
@ -224,6 +292,10 @@ do_syscall:
|
||||
str p14, [x2, #14, MUL VL]
|
||||
str p15, [x2, #15, MUL VL]
|
||||
|
||||
// Only save FFR if we wrote a value for SME
|
||||
ldr x2, =ffr_in
|
||||
ldr x2, [x2, #0]
|
||||
cbz x2, 1f
|
||||
ldr x2, =ffr_out
|
||||
rdffr p0.b
|
||||
str p0, [x2, #0]
|
||||
@ -237,4 +309,9 @@ do_syscall:
|
||||
ldp x27, x28, [sp, #96]
|
||||
ldp x29, x30, [sp], #112
|
||||
|
||||
// Clear SVCR if we were doing SME so future tests don't have ZA
|
||||
cbz x1, 1f
|
||||
msr S3_3_C4_C2_2, xzr
|
||||
1:
|
||||
|
||||
ret
|
||||
|
@ -18,9 +18,13 @@
|
||||
|
||||
#include "../../kselftest.h"
|
||||
|
||||
#include "syscall-abi.h"
|
||||
|
||||
#define NUM_VL ((SVE_VQ_MAX - SVE_VQ_MIN) + 1)
|
||||
|
||||
extern void do_syscall(int sve_vl);
|
||||
static int default_sme_vl;
|
||||
|
||||
extern void do_syscall(int sve_vl, int sme_vl);
|
||||
|
||||
static void fill_random(void *buf, size_t size)
|
||||
{
|
||||
@ -48,14 +52,15 @@ static struct syscall_cfg {
|
||||
uint64_t gpr_in[NUM_GPR];
|
||||
uint64_t gpr_out[NUM_GPR];
|
||||
|
||||
static void setup_gpr(struct syscall_cfg *cfg, int sve_vl)
|
||||
static void setup_gpr(struct syscall_cfg *cfg, int sve_vl, int sme_vl,
|
||||
uint64_t svcr)
|
||||
{
|
||||
fill_random(gpr_in, sizeof(gpr_in));
|
||||
gpr_in[8] = cfg->syscall_nr;
|
||||
memset(gpr_out, 0, sizeof(gpr_out));
|
||||
}
|
||||
|
||||
static int check_gpr(struct syscall_cfg *cfg, int sve_vl)
|
||||
static int check_gpr(struct syscall_cfg *cfg, int sve_vl, int sme_vl, uint64_t svcr)
|
||||
{
|
||||
int errors = 0;
|
||||
int i;
|
||||
@ -79,13 +84,15 @@ static int check_gpr(struct syscall_cfg *cfg, int sve_vl)
|
||||
uint64_t fpr_in[NUM_FPR * 2];
|
||||
uint64_t fpr_out[NUM_FPR * 2];
|
||||
|
||||
static void setup_fpr(struct syscall_cfg *cfg, int sve_vl)
|
||||
static void setup_fpr(struct syscall_cfg *cfg, int sve_vl, int sme_vl,
|
||||
uint64_t svcr)
|
||||
{
|
||||
fill_random(fpr_in, sizeof(fpr_in));
|
||||
memset(fpr_out, 0, sizeof(fpr_out));
|
||||
}
|
||||
|
||||
static int check_fpr(struct syscall_cfg *cfg, int sve_vl)
|
||||
static int check_fpr(struct syscall_cfg *cfg, int sve_vl, int sme_vl,
|
||||
uint64_t svcr)
|
||||
{
|
||||
int errors = 0;
|
||||
int i;
|
||||
@ -109,13 +116,15 @@ static uint8_t z_zero[__SVE_ZREG_SIZE(SVE_VQ_MAX)];
|
||||
uint8_t z_in[SVE_NUM_PREGS * __SVE_ZREG_SIZE(SVE_VQ_MAX)];
|
||||
uint8_t z_out[SVE_NUM_PREGS * __SVE_ZREG_SIZE(SVE_VQ_MAX)];
|
||||
|
||||
static void setup_z(struct syscall_cfg *cfg, int sve_vl)
|
||||
static void setup_z(struct syscall_cfg *cfg, int sve_vl, int sme_vl,
|
||||
uint64_t svcr)
|
||||
{
|
||||
fill_random(z_in, sizeof(z_in));
|
||||
fill_random(z_out, sizeof(z_out));
|
||||
}
|
||||
|
||||
static int check_z(struct syscall_cfg *cfg, int sve_vl)
|
||||
static int check_z(struct syscall_cfg *cfg, int sve_vl, int sme_vl,
|
||||
uint64_t svcr)
|
||||
{
|
||||
size_t reg_size = sve_vl;
|
||||
int errors = 0;
|
||||
@ -126,13 +135,17 @@ static int check_z(struct syscall_cfg *cfg, int sve_vl)
|
||||
|
||||
/*
|
||||
* After a syscall the low 128 bits of the Z registers should
|
||||
* be preserved and the rest be zeroed or preserved.
|
||||
* be preserved and the rest be zeroed or preserved, except if
|
||||
* we were in streaming mode in which case the low 128 bits may
|
||||
* also be cleared by the transition out of streaming mode.
|
||||
*/
|
||||
for (i = 0; i < SVE_NUM_ZREGS; i++) {
|
||||
void *in = &z_in[reg_size * i];
|
||||
void *out = &z_out[reg_size * i];
|
||||
|
||||
if (memcmp(in, out, SVE_VQ_BYTES) != 0) {
|
||||
if ((memcmp(in, out, SVE_VQ_BYTES) != 0) &&
|
||||
!((svcr & SVCR_SM_MASK) &&
|
||||
memcmp(z_zero, out, SVE_VQ_BYTES) == 0)) {
|
||||
ksft_print_msg("%s SVE VL %d Z%d low 128 bits changed\n",
|
||||
cfg->name, sve_vl, i);
|
||||
errors++;
|
||||
@ -145,13 +158,15 @@ static int check_z(struct syscall_cfg *cfg, int sve_vl)
|
||||
uint8_t p_in[SVE_NUM_PREGS * __SVE_PREG_SIZE(SVE_VQ_MAX)];
|
||||
uint8_t p_out[SVE_NUM_PREGS * __SVE_PREG_SIZE(SVE_VQ_MAX)];
|
||||
|
||||
static void setup_p(struct syscall_cfg *cfg, int sve_vl)
|
||||
static void setup_p(struct syscall_cfg *cfg, int sve_vl, int sme_vl,
|
||||
uint64_t svcr)
|
||||
{
|
||||
fill_random(p_in, sizeof(p_in));
|
||||
fill_random(p_out, sizeof(p_out));
|
||||
}
|
||||
|
||||
static int check_p(struct syscall_cfg *cfg, int sve_vl)
|
||||
static int check_p(struct syscall_cfg *cfg, int sve_vl, int sme_vl,
|
||||
uint64_t svcr)
|
||||
{
|
||||
size_t reg_size = sve_vq_from_vl(sve_vl) * 2; /* 1 bit per VL byte */
|
||||
|
||||
@ -175,8 +190,19 @@ static int check_p(struct syscall_cfg *cfg, int sve_vl)
|
||||
uint8_t ffr_in[__SVE_PREG_SIZE(SVE_VQ_MAX)];
|
||||
uint8_t ffr_out[__SVE_PREG_SIZE(SVE_VQ_MAX)];
|
||||
|
||||
static void setup_ffr(struct syscall_cfg *cfg, int sve_vl)
|
||||
static void setup_ffr(struct syscall_cfg *cfg, int sve_vl, int sme_vl,
|
||||
uint64_t svcr)
|
||||
{
|
||||
/*
|
||||
* If we are in streaming mode and do not have FA64 then FFR
|
||||
* is unavailable.
|
||||
*/
|
||||
if ((svcr & SVCR_SM_MASK) &&
|
||||
!(getauxval(AT_HWCAP2) & HWCAP2_SME_FA64)) {
|
||||
memset(&ffr_in, 0, sizeof(ffr_in));
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* It is only valid to set a contiguous set of bits starting
|
||||
* at 0. For now since we're expecting this to be cleared by
|
||||
@ -186,7 +212,8 @@ static void setup_ffr(struct syscall_cfg *cfg, int sve_vl)
|
||||
fill_random(ffr_out, sizeof(ffr_out));
|
||||
}
|
||||
|
||||
static int check_ffr(struct syscall_cfg *cfg, int sve_vl)
|
||||
static int check_ffr(struct syscall_cfg *cfg, int sve_vl, int sme_vl,
|
||||
uint64_t svcr)
|
||||
{
|
||||
size_t reg_size = sve_vq_from_vl(sve_vl) * 2; /* 1 bit per VL byte */
|
||||
int errors = 0;
|
||||
@ -195,6 +222,10 @@ static int check_ffr(struct syscall_cfg *cfg, int sve_vl)
|
||||
if (!sve_vl)
|
||||
return 0;
|
||||
|
||||
if ((svcr & SVCR_SM_MASK) &&
|
||||
!(getauxval(AT_HWCAP2) & HWCAP2_SME_FA64))
|
||||
return 0;
|
||||
|
||||
/* After a syscall the P registers should be preserved or zeroed */
|
||||
for (i = 0; i < reg_size; i++)
|
||||
if (ffr_out[i] && (ffr_in[i] != ffr_out[i]))
|
||||
@ -206,8 +237,65 @@ static int check_ffr(struct syscall_cfg *cfg, int sve_vl)
|
||||
return errors;
|
||||
}
|
||||
|
||||
typedef void (*setup_fn)(struct syscall_cfg *cfg, int sve_vl);
|
||||
typedef int (*check_fn)(struct syscall_cfg *cfg, int sve_vl);
|
||||
uint64_t svcr_in, svcr_out;
|
||||
|
||||
static void setup_svcr(struct syscall_cfg *cfg, int sve_vl, int sme_vl,
|
||||
uint64_t svcr)
|
||||
{
|
||||
svcr_in = svcr;
|
||||
}
|
||||
|
||||
static int check_svcr(struct syscall_cfg *cfg, int sve_vl, int sme_vl,
|
||||
uint64_t svcr)
|
||||
{
|
||||
int errors = 0;
|
||||
|
||||
if (svcr_out & SVCR_SM_MASK) {
|
||||
ksft_print_msg("%s Still in SM, SVCR %llx\n",
|
||||
cfg->name, svcr_out);
|
||||
errors++;
|
||||
}
|
||||
|
||||
if ((svcr_in & SVCR_ZA_MASK) != (svcr_out & SVCR_ZA_MASK)) {
|
||||
ksft_print_msg("%s PSTATE.ZA changed, SVCR %llx != %llx\n",
|
||||
cfg->name, svcr_in, svcr_out);
|
||||
errors++;
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
uint8_t za_in[SVE_NUM_PREGS * __SVE_ZREG_SIZE(SVE_VQ_MAX)];
|
||||
uint8_t za_out[SVE_NUM_PREGS * __SVE_ZREG_SIZE(SVE_VQ_MAX)];
|
||||
|
||||
static void setup_za(struct syscall_cfg *cfg, int sve_vl, int sme_vl,
|
||||
uint64_t svcr)
|
||||
{
|
||||
fill_random(za_in, sizeof(za_in));
|
||||
memset(za_out, 0, sizeof(za_out));
|
||||
}
|
||||
|
||||
static int check_za(struct syscall_cfg *cfg, int sve_vl, int sme_vl,
|
||||
uint64_t svcr)
|
||||
{
|
||||
size_t reg_size = sme_vl * sme_vl;
|
||||
int errors = 0;
|
||||
|
||||
if (!(svcr & SVCR_ZA_MASK))
|
||||
return 0;
|
||||
|
||||
if (memcmp(za_in, za_out, reg_size) != 0) {
|
||||
ksft_print_msg("SME VL %d ZA does not match\n", sme_vl);
|
||||
errors++;
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
typedef void (*setup_fn)(struct syscall_cfg *cfg, int sve_vl, int sme_vl,
|
||||
uint64_t svcr);
|
||||
typedef int (*check_fn)(struct syscall_cfg *cfg, int sve_vl, int sme_vl,
|
||||
uint64_t svcr);
|
||||
|
||||
/*
|
||||
* Each set of registers has a setup function which is called before
|
||||
@ -225,20 +313,23 @@ static struct {
|
||||
{ setup_z, check_z },
|
||||
{ setup_p, check_p },
|
||||
{ setup_ffr, check_ffr },
|
||||
{ setup_svcr, check_svcr },
|
||||
{ setup_za, check_za },
|
||||
};
|
||||
|
||||
static bool do_test(struct syscall_cfg *cfg, int sve_vl)
|
||||
static bool do_test(struct syscall_cfg *cfg, int sve_vl, int sme_vl,
|
||||
uint64_t svcr)
|
||||
{
|
||||
int errors = 0;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(regset); i++)
|
||||
regset[i].setup(cfg, sve_vl);
|
||||
regset[i].setup(cfg, sve_vl, sme_vl, svcr);
|
||||
|
||||
do_syscall(sve_vl);
|
||||
do_syscall(sve_vl, sme_vl);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(regset); i++)
|
||||
errors += regset[i].check(cfg, sve_vl);
|
||||
errors += regset[i].check(cfg, sve_vl, sme_vl, svcr);
|
||||
|
||||
return errors == 0;
|
||||
}
|
||||
@ -246,9 +337,10 @@ static bool do_test(struct syscall_cfg *cfg, int sve_vl)
|
||||
static void test_one_syscall(struct syscall_cfg *cfg)
|
||||
{
|
||||
int sve_vq, sve_vl;
|
||||
int sme_vq, sme_vl;
|
||||
|
||||
/* FPSIMD only case */
|
||||
ksft_test_result(do_test(cfg, 0),
|
||||
ksft_test_result(do_test(cfg, 0, default_sme_vl, 0),
|
||||
"%s FPSIMD\n", cfg->name);
|
||||
|
||||
if (!(getauxval(AT_HWCAP) & HWCAP_SVE))
|
||||
@ -265,8 +357,36 @@ static void test_one_syscall(struct syscall_cfg *cfg)
|
||||
if (sve_vq != sve_vq_from_vl(sve_vl))
|
||||
sve_vq = sve_vq_from_vl(sve_vl);
|
||||
|
||||
ksft_test_result(do_test(cfg, sve_vl),
|
||||
ksft_test_result(do_test(cfg, sve_vl, default_sme_vl, 0),
|
||||
"%s SVE VL %d\n", cfg->name, sve_vl);
|
||||
|
||||
if (!(getauxval(AT_HWCAP2) & HWCAP2_SME))
|
||||
continue;
|
||||
|
||||
for (sme_vq = SVE_VQ_MAX; sme_vq > 0; --sme_vq) {
|
||||
sme_vl = prctl(PR_SME_SET_VL, sme_vq * 16);
|
||||
if (sme_vl == -1)
|
||||
ksft_exit_fail_msg("PR_SME_SET_VL failed: %s (%d)\n",
|
||||
strerror(errno), errno);
|
||||
|
||||
sme_vl &= PR_SME_VL_LEN_MASK;
|
||||
|
||||
if (sme_vq != sve_vq_from_vl(sme_vl))
|
||||
sme_vq = sve_vq_from_vl(sme_vl);
|
||||
|
||||
ksft_test_result(do_test(cfg, sve_vl, sme_vl,
|
||||
SVCR_ZA_MASK | SVCR_SM_MASK),
|
||||
"%s SVE VL %d/SME VL %d SM+ZA\n",
|
||||
cfg->name, sve_vl, sme_vl);
|
||||
ksft_test_result(do_test(cfg, sve_vl, sme_vl,
|
||||
SVCR_SM_MASK),
|
||||
"%s SVE VL %d/SME VL %d SM\n",
|
||||
cfg->name, sve_vl, sme_vl);
|
||||
ksft_test_result(do_test(cfg, sve_vl, sme_vl,
|
||||
SVCR_ZA_MASK),
|
||||
"%s SVE VL %d/SME VL %d ZA\n",
|
||||
cfg->name, sve_vl, sme_vl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -299,14 +419,54 @@ int sve_count_vls(void)
|
||||
return vl_count;
|
||||
}
|
||||
|
||||
int sme_count_vls(void)
|
||||
{
|
||||
unsigned int vq;
|
||||
int vl_count = 0;
|
||||
int vl;
|
||||
|
||||
if (!(getauxval(AT_HWCAP2) & HWCAP2_SME))
|
||||
return 0;
|
||||
|
||||
/* Ensure we configure a SME VL, used to flag if SVCR is set */
|
||||
default_sme_vl = 16;
|
||||
|
||||
/*
|
||||
* Enumerate up to SVE_VQ_MAX vector lengths
|
||||
*/
|
||||
for (vq = SVE_VQ_MAX; vq > 0; --vq) {
|
||||
vl = prctl(PR_SME_SET_VL, vq * 16);
|
||||
if (vl == -1)
|
||||
ksft_exit_fail_msg("PR_SME_SET_VL failed: %s (%d)\n",
|
||||
strerror(errno), errno);
|
||||
|
||||
vl &= PR_SME_VL_LEN_MASK;
|
||||
|
||||
if (vq != sve_vq_from_vl(vl))
|
||||
vq = sve_vq_from_vl(vl);
|
||||
|
||||
vl_count++;
|
||||
}
|
||||
|
||||
return vl_count;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int i;
|
||||
int tests = 1; /* FPSIMD */
|
||||
|
||||
srandom(getpid());
|
||||
|
||||
ksft_print_header();
|
||||
ksft_set_plan(ARRAY_SIZE(syscalls) * (sve_count_vls() + 1));
|
||||
tests += sve_count_vls();
|
||||
tests += (sve_count_vls() * sme_count_vls()) * 3;
|
||||
ksft_set_plan(ARRAY_SIZE(syscalls) * tests);
|
||||
|
||||
if (getauxval(AT_HWCAP2) & HWCAP2_SME_FA64)
|
||||
ksft_print_msg("SME with FA64\n");
|
||||
else if (getauxval(AT_HWCAP2) & HWCAP2_SME)
|
||||
ksft_print_msg("SME without FA64\n");
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(syscalls); i++)
|
||||
test_one_syscall(&syscalls[i]);
|
||||
|
15
tools/testing/selftests/arm64/abi/syscall-abi.h
Normal file
15
tools/testing/selftests/arm64/abi/syscall-abi.h
Normal file
@ -0,0 +1,15 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (C) 2021 ARM Limited.
|
||||
*/
|
||||
|
||||
#ifndef SYSCALL_ABI_H
|
||||
#define SYSCALL_ABI_H
|
||||
|
||||
#define SVCR_ZA_MASK 2
|
||||
#define SVCR_SM_MASK 1
|
||||
|
||||
#define SVCR_ZA_SHIFT 1
|
||||
#define SVCR_SM_SHIFT 0
|
||||
|
||||
#endif
|
298
tools/testing/selftests/arm64/abi/tpidr2.c
Normal file
298
tools/testing/selftests/arm64/abi/tpidr2.c
Normal file
@ -0,0 +1,298 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
#include <linux/sched.h>
|
||||
#include <linux/wait.h>
|
||||
|
||||
#define SYS_TPIDR2 "S3_3_C13_C0_5"
|
||||
|
||||
#define EXPECTED_TESTS 5
|
||||
|
||||
static void putstr(const char *str)
|
||||
{
|
||||
write(1, str, strlen(str));
|
||||
}
|
||||
|
||||
static void putnum(unsigned int num)
|
||||
{
|
||||
char c;
|
||||
|
||||
if (num / 10)
|
||||
putnum(num / 10);
|
||||
|
||||
c = '0' + (num % 10);
|
||||
write(1, &c, 1);
|
||||
}
|
||||
|
||||
static int tests_run;
|
||||
static int tests_passed;
|
||||
static int tests_failed;
|
||||
static int tests_skipped;
|
||||
|
||||
static void set_tpidr2(uint64_t val)
|
||||
{
|
||||
asm volatile (
|
||||
"msr " SYS_TPIDR2 ", %0\n"
|
||||
:
|
||||
: "r"(val)
|
||||
: "cc");
|
||||
}
|
||||
|
||||
static uint64_t get_tpidr2(void)
|
||||
{
|
||||
uint64_t val;
|
||||
|
||||
asm volatile (
|
||||
"mrs %0, " SYS_TPIDR2 "\n"
|
||||
: "=r"(val)
|
||||
:
|
||||
: "cc");
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
static void print_summary(void)
|
||||
{
|
||||
if (tests_passed + tests_failed + tests_skipped != EXPECTED_TESTS)
|
||||
putstr("# UNEXPECTED TEST COUNT: ");
|
||||
|
||||
putstr("# Totals: pass:");
|
||||
putnum(tests_passed);
|
||||
putstr(" fail:");
|
||||
putnum(tests_failed);
|
||||
putstr(" xfail:0 xpass:0 skip:");
|
||||
putnum(tests_skipped);
|
||||
putstr(" error:0\n");
|
||||
}
|
||||
|
||||
/* Processes should start with TPIDR2 == 0 */
|
||||
static int default_value(void)
|
||||
{
|
||||
return get_tpidr2() == 0;
|
||||
}
|
||||
|
||||
/* If we set TPIDR2 we should read that value */
|
||||
static int write_read(void)
|
||||
{
|
||||
set_tpidr2(getpid());
|
||||
|
||||
return getpid() == get_tpidr2();
|
||||
}
|
||||
|
||||
/* If we set a value we should read the same value after scheduling out */
|
||||
static int write_sleep_read(void)
|
||||
{
|
||||
set_tpidr2(getpid());
|
||||
|
||||
msleep(100);
|
||||
|
||||
return getpid() == get_tpidr2();
|
||||
}
|
||||
|
||||
/*
|
||||
* If we fork the value in the parent should be unchanged and the
|
||||
* child should start with the same value and be able to set its own
|
||||
* value.
|
||||
*/
|
||||
static int write_fork_read(void)
|
||||
{
|
||||
pid_t newpid, waiting, oldpid;
|
||||
int status;
|
||||
|
||||
set_tpidr2(getpid());
|
||||
|
||||
oldpid = getpid();
|
||||
newpid = fork();
|
||||
if (newpid == 0) {
|
||||
/* In child */
|
||||
if (get_tpidr2() != oldpid) {
|
||||
putstr("# TPIDR2 changed in child: ");
|
||||
putnum(get_tpidr2());
|
||||
putstr("\n");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
set_tpidr2(getpid());
|
||||
if (get_tpidr2() == getpid()) {
|
||||
exit(1);
|
||||
} else {
|
||||
putstr("# Failed to set TPIDR2 in child\n");
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
if (newpid < 0) {
|
||||
putstr("# fork() failed: -");
|
||||
putnum(-newpid);
|
||||
putstr("\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
waiting = waitpid(newpid, &status, 0);
|
||||
|
||||
if (waiting < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
putstr("# waitpid() failed: ");
|
||||
putnum(errno);
|
||||
putstr("\n");
|
||||
return 0;
|
||||
}
|
||||
if (waiting != newpid) {
|
||||
putstr("# waitpid() returned wrong PID\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!WIFEXITED(status)) {
|
||||
putstr("# child did not exit\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (getpid() != get_tpidr2()) {
|
||||
putstr("# TPIDR2 corrupted in parent\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return WEXITSTATUS(status);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* sys_clone() has a lot of per architecture variation so just define
|
||||
* it here rather than adding it to nolibc, plus the raw API is a
|
||||
* little more convenient for this test.
|
||||
*/
|
||||
static int sys_clone(unsigned long clone_flags, unsigned long newsp,
|
||||
int *parent_tidptr, unsigned long tls,
|
||||
int *child_tidptr)
|
||||
{
|
||||
return my_syscall5(__NR_clone, clone_flags, newsp, parent_tidptr, tls,
|
||||
child_tidptr);
|
||||
}
|
||||
|
||||
/*
|
||||
* If we clone with CLONE_SETTLS then the value in the parent should
|
||||
* be unchanged and the child should start with zero and be able to
|
||||
* set its own value.
|
||||
*/
|
||||
static int write_clone_read(void)
|
||||
{
|
||||
int parent_tid, child_tid;
|
||||
pid_t parent, waiting;
|
||||
int ret, status;
|
||||
|
||||
parent = getpid();
|
||||
set_tpidr2(parent);
|
||||
|
||||
ret = sys_clone(CLONE_SETTLS, 0, &parent_tid, 0, &child_tid);
|
||||
if (ret == -1) {
|
||||
putstr("# clone() failed\n");
|
||||
putnum(errno);
|
||||
putstr("\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ret == 0) {
|
||||
/* In child */
|
||||
if (get_tpidr2() != 0) {
|
||||
putstr("# TPIDR2 non-zero in child: ");
|
||||
putnum(get_tpidr2());
|
||||
putstr("\n");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
if (gettid() == 0)
|
||||
putstr("# Child TID==0\n");
|
||||
set_tpidr2(gettid());
|
||||
if (get_tpidr2() == gettid()) {
|
||||
exit(1);
|
||||
} else {
|
||||
putstr("# Failed to set TPIDR2 in child\n");
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
waiting = wait4(ret, &status, __WCLONE, NULL);
|
||||
|
||||
if (waiting < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
putstr("# wait4() failed: ");
|
||||
putnum(errno);
|
||||
putstr("\n");
|
||||
return 0;
|
||||
}
|
||||
if (waiting != ret) {
|
||||
putstr("# wait4() returned wrong PID ");
|
||||
putnum(waiting);
|
||||
putstr("\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!WIFEXITED(status)) {
|
||||
putstr("# child did not exit\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (parent != get_tpidr2()) {
|
||||
putstr("# TPIDR2 corrupted in parent\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return WEXITSTATUS(status);
|
||||
}
|
||||
}
|
||||
|
||||
#define run_test(name) \
|
||||
if (name()) { \
|
||||
tests_passed++; \
|
||||
} else { \
|
||||
tests_failed++; \
|
||||
putstr("not "); \
|
||||
} \
|
||||
putstr("ok "); \
|
||||
putnum(++tests_run); \
|
||||
putstr(" " #name "\n");
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int ret, i;
|
||||
|
||||
putstr("TAP version 13\n");
|
||||
putstr("1..");
|
||||
putnum(EXPECTED_TESTS);
|
||||
putstr("\n");
|
||||
|
||||
putstr("# PID: ");
|
||||
putnum(getpid());
|
||||
putstr("\n");
|
||||
|
||||
/*
|
||||
* This test is run with nolibc which doesn't support hwcap and
|
||||
* it's probably disproportionate to implement so instead check
|
||||
* for the default vector length configuration in /proc.
|
||||
*/
|
||||
ret = open("/proc/sys/abi/sme_default_vector_length", O_RDONLY, 0);
|
||||
if (ret >= 0) {
|
||||
run_test(default_value);
|
||||
run_test(write_read);
|
||||
run_test(write_sleep_read);
|
||||
run_test(write_fork_read);
|
||||
run_test(write_clone_read);
|
||||
|
||||
} else {
|
||||
putstr("# SME support not present\n");
|
||||
|
||||
for (i = 0; i < EXPECTED_TESTS; i++) {
|
||||
putstr("ok ");
|
||||
putnum(i);
|
||||
putstr(" skipped, TPIDR2 not supported\n");
|
||||
}
|
||||
|
||||
tests_skipped += EXPECTED_TESTS;
|
||||
}
|
||||
|
||||
print_summary();
|
||||
|
||||
return 0;
|
||||
}
|
@ -10,7 +10,7 @@ PROGS := $(patsubst %,gen/%,$(TEST_GEN_PROGS))
|
||||
# cases for statically linked and dynamically lined binaries are
|
||||
# slightly different.
|
||||
|
||||
CFLAGS_NOBTI = -DBTI=0
|
||||
CFLAGS_NOBTI = -mbranch-protection=none -DBTI=0
|
||||
CFLAGS_BTI = -mbranch-protection=standard -DBTI=1
|
||||
|
||||
CFLAGS_COMMON = -ffreestanding -Wall -Wextra $(CFLAGS)
|
||||
@ -39,7 +39,7 @@ BTI_OBJS = \
|
||||
teststubs-bti.o \
|
||||
trampoline-bti.o
|
||||
gen/btitest: $(BTI_OBJS)
|
||||
$(CC) $(CFLAGS_BTI) $(CFLAGS_COMMON) -nostdlib -o $@ $^
|
||||
$(CC) $(CFLAGS_BTI) $(CFLAGS_COMMON) -nostdlib -static -o $@ $^
|
||||
|
||||
NOBTI_OBJS = \
|
||||
test-nobti.o \
|
||||
@ -50,7 +50,7 @@ NOBTI_OBJS = \
|
||||
teststubs-nobti.o \
|
||||
trampoline-nobti.o
|
||||
gen/nobtitest: $(NOBTI_OBJS)
|
||||
$(CC) $(CFLAGS_BTI) $(CFLAGS_COMMON) -nostdlib -o $@ $^
|
||||
$(CC) $(CFLAGS_BTI) $(CFLAGS_COMMON) -nostdlib -static -o $@ $^
|
||||
|
||||
# Including KSFT lib.mk here will also mangle the TEST_GEN_PROGS list
|
||||
# to account for any OUTPUT target-dirs optionally provided by
|
||||
|
5
tools/testing/selftests/arm64/fp/.gitignore
vendored
5
tools/testing/selftests/arm64/fp/.gitignore
vendored
@ -1,8 +1,13 @@
|
||||
fp-pidbench
|
||||
fpsimd-test
|
||||
rdvl-sme
|
||||
rdvl-sve
|
||||
sve-probe-vls
|
||||
sve-ptrace
|
||||
sve-test
|
||||
ssve-test
|
||||
vec-syscfg
|
||||
vlset
|
||||
za-fork
|
||||
za-ptrace
|
||||
za-test
|
||||
|
@ -1,24 +1,42 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
CFLAGS += -I../../../../../usr/include/
|
||||
TEST_GEN_PROGS := sve-ptrace sve-probe-vls vec-syscfg
|
||||
TEST_PROGS_EXTENDED := fp-pidbench fpsimd-test fpsimd-stress \
|
||||
rdvl-sve \
|
||||
sve-test sve-stress \
|
||||
# A proper top_srcdir is needed by KSFT(lib.mk)
|
||||
top_srcdir = $(realpath ../../../../../)
|
||||
|
||||
CFLAGS += -I$(top_srcdir)/usr/include/
|
||||
|
||||
TEST_GEN_PROGS := sve-ptrace sve-probe-vls vec-syscfg za-fork za-ptrace
|
||||
TEST_GEN_PROGS_EXTENDED := fp-pidbench fpsimd-test \
|
||||
rdvl-sme rdvl-sve \
|
||||
sve-test \
|
||||
ssve-test \
|
||||
za-test \
|
||||
vlset
|
||||
TEST_PROGS_EXTENDED := fpsimd-stress sve-stress ssve-stress za-stress
|
||||
|
||||
all: $(TEST_GEN_PROGS) $(TEST_PROGS_EXTENDED)
|
||||
EXTRA_CLEAN += $(OUTPUT)/asm-utils.o $(OUTPUT)/rdvl.o $(OUTPUT)/za-fork-asm.o
|
||||
|
||||
fp-pidbench: fp-pidbench.S asm-utils.o
|
||||
# Build with nolibc to avoid effects due to libc's clone() support
|
||||
$(OUTPUT)/fp-pidbench: fp-pidbench.S $(OUTPUT)/asm-utils.o
|
||||
$(CC) -nostdlib $^ -o $@
|
||||
fpsimd-test: fpsimd-test.o asm-utils.o
|
||||
$(OUTPUT)/fpsimd-test: fpsimd-test.S $(OUTPUT)/asm-utils.o
|
||||
$(CC) -nostdlib $^ -o $@
|
||||
rdvl-sve: rdvl-sve.o rdvl.o
|
||||
sve-ptrace: sve-ptrace.o
|
||||
sve-probe-vls: sve-probe-vls.o rdvl.o
|
||||
sve-test: sve-test.o asm-utils.o
|
||||
$(OUTPUT)/rdvl-sve: rdvl-sve.c $(OUTPUT)/rdvl.o
|
||||
$(OUTPUT)/rdvl-sme: rdvl-sme.c $(OUTPUT)/rdvl.o
|
||||
$(OUTPUT)/sve-ptrace: sve-ptrace.c
|
||||
$(OUTPUT)/sve-probe-vls: sve-probe-vls.c $(OUTPUT)/rdvl.o
|
||||
$(OUTPUT)/sve-test: sve-test.S $(OUTPUT)/asm-utils.o
|
||||
$(CC) -nostdlib $^ -o $@
|
||||
$(OUTPUT)/ssve-test: sve-test.S $(OUTPUT)/asm-utils.o
|
||||
$(CC) -DSSVE -nostdlib $^ -o $@
|
||||
$(OUTPUT)/vec-syscfg: vec-syscfg.c $(OUTPUT)/rdvl.o
|
||||
$(OUTPUT)/vlset: vlset.c
|
||||
$(OUTPUT)/za-fork: za-fork.c $(OUTPUT)/za-fork-asm.o
|
||||
$(CC) -fno-asynchronous-unwind-tables -fno-ident -s -Os -nostdlib \
|
||||
-include ../../../../include/nolibc/nolibc.h \
|
||||
-static -ffreestanding -Wall $^ -o $@
|
||||
$(OUTPUT)/za-ptrace: za-ptrace.c
|
||||
$(OUTPUT)/za-test: za-test.S $(OUTPUT)/asm-utils.o
|
||||
$(CC) -nostdlib $^ -o $@
|
||||
vec-syscfg: vec-syscfg.o rdvl.o
|
||||
vlset: vlset.o
|
||||
|
||||
include ../../lib.mk
|
||||
|
14
tools/testing/selftests/arm64/fp/rdvl-sme.c
Normal file
14
tools/testing/selftests/arm64/fp/rdvl-sme.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_sme();
|
||||
|
||||
printf("%d\n", vl);
|
||||
|
||||
return 0;
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
// Copyright (C) 2021 ARM Limited.
|
||||
|
||||
#include "sme-inst.h"
|
||||
|
||||
.arch_extension sve
|
||||
|
||||
.globl rdvl_sve
|
||||
@ -8,3 +10,11 @@ rdvl_sve:
|
||||
hint 34 // BTI C
|
||||
rdvl x0, #1
|
||||
ret
|
||||
|
||||
.globl rdvl_sme
|
||||
rdvl_sme:
|
||||
hint 34 // BTI C
|
||||
|
||||
rdsvl 0, 1
|
||||
|
||||
ret
|
||||
|
@ -3,6 +3,7 @@
|
||||
#ifndef RDVL_H
|
||||
#define RDVL_H
|
||||
|
||||
int rdvl_sme(void);
|
||||
int rdvl_sve(void);
|
||||
|
||||
#endif
|
||||
|
51
tools/testing/selftests/arm64/fp/sme-inst.h
Normal file
51
tools/testing/selftests/arm64/fp/sme-inst.h
Normal file
@ -0,0 +1,51 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
// Copyright (C) 2021-2 ARM Limited.
|
||||
// Original author: Mark Brown <broonie@kernel.org>
|
||||
|
||||
#ifndef SME_INST_H
|
||||
#define SME_INST_H
|
||||
|
||||
/*
|
||||
* RDSVL X\nx, #\imm
|
||||
*/
|
||||
.macro rdsvl nx, imm
|
||||
.inst 0x4bf5800 \
|
||||
| (\imm << 5) \
|
||||
| (\nx)
|
||||
.endm
|
||||
|
||||
.macro smstop
|
||||
msr S0_3_C4_C6_3, xzr
|
||||
.endm
|
||||
|
||||
.macro smstart_za
|
||||
msr S0_3_C4_C5_3, xzr
|
||||
.endm
|
||||
|
||||
.macro smstart_sm
|
||||
msr S0_3_C4_C3_3, xzr
|
||||
.endm
|
||||
|
||||
/*
|
||||
* LDR (vector to ZA array):
|
||||
* LDR ZA[\nw, #\offset], [X\nxbase, #\offset, MUL VL]
|
||||
*/
|
||||
.macro _ldr_za nw, nxbase, offset=0
|
||||
.inst 0xe1000000 \
|
||||
| (((\nw) & 3) << 13) \
|
||||
| ((\nxbase) << 5) \
|
||||
| ((\offset) & 7)
|
||||
.endm
|
||||
|
||||
/*
|
||||
* STR (vector from ZA array):
|
||||
* STR ZA[\nw, #\offset], [X\nxbase, #\offset, MUL VL]
|
||||
*/
|
||||
.macro _str_za nw, nxbase, offset=0
|
||||
.inst 0xe1200000 \
|
||||
| (((\nw) & 3) << 13) \
|
||||
| ((\nxbase) << 5) \
|
||||
| ((\offset) & 7)
|
||||
.endm
|
||||
|
||||
#endif
|
59
tools/testing/selftests/arm64/fp/ssve-stress
Normal file
59
tools/testing/selftests/arm64/fp/ssve-stress
Normal file
@ -0,0 +1,59 @@
|
||||
#!/bin/bash
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
# Copyright (C) 2015-2019 ARM Limited.
|
||||
# Original author: Dave Martin <Dave.Martin@arm.com>
|
||||
|
||||
set -ue
|
||||
|
||||
NR_CPUS=`nproc`
|
||||
|
||||
pids=
|
||||
logs=
|
||||
|
||||
cleanup () {
|
||||
trap - INT TERM CHLD
|
||||
set +e
|
||||
|
||||
if [ -n "$pids" ]; then
|
||||
kill $pids
|
||||
wait $pids
|
||||
pids=
|
||||
fi
|
||||
|
||||
if [ -n "$logs" ]; then
|
||||
cat $logs
|
||||
rm $logs
|
||||
logs=
|
||||
fi
|
||||
}
|
||||
|
||||
interrupt () {
|
||||
cleanup
|
||||
exit 0
|
||||
}
|
||||
|
||||
child_died () {
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
|
||||
trap interrupt INT TERM EXIT
|
||||
|
||||
for x in `seq 0 $((NR_CPUS * 4))`; do
|
||||
log=`mktemp`
|
||||
logs=$logs\ $log
|
||||
./ssve-test >$log &
|
||||
pids=$pids\ $!
|
||||
done
|
||||
|
||||
# Wait for all child processes to be created:
|
||||
sleep 10
|
||||
|
||||
while :; do
|
||||
kill -USR1 $pids
|
||||
done &
|
||||
pids=$pids\ $!
|
||||
|
||||
wait
|
||||
|
||||
exit 1
|
@ -26,6 +26,10 @@
|
||||
#define NT_ARM_SVE 0x405
|
||||
#endif
|
||||
|
||||
#ifndef NT_ARM_SSVE
|
||||
#define NT_ARM_SSVE 0x40b
|
||||
#endif
|
||||
|
||||
struct vec_type {
|
||||
const char *name;
|
||||
unsigned long hwcap_type;
|
||||
@ -42,11 +46,18 @@ static const struct vec_type vec_types[] = {
|
||||
.regset = NT_ARM_SVE,
|
||||
.prctl_set = PR_SVE_SET_VL,
|
||||
},
|
||||
{
|
||||
.name = "Streaming SVE",
|
||||
.hwcap_type = AT_HWCAP2,
|
||||
.hwcap = HWCAP2_SME,
|
||||
.regset = NT_ARM_SSVE,
|
||||
.prctl_set = PR_SME_SET_VL,
|
||||
},
|
||||
};
|
||||
|
||||
#define VL_TESTS (((SVE_VQ_MAX - SVE_VQ_MIN) + 1) * 3)
|
||||
#define VL_TESTS (((SVE_VQ_MAX - SVE_VQ_MIN) + 1) * 4)
|
||||
#define FLAG_TESTS 2
|
||||
#define FPSIMD_TESTS 3
|
||||
#define FPSIMD_TESTS 2
|
||||
|
||||
#define EXPECTED_TESTS ((VL_TESTS + FLAG_TESTS + FPSIMD_TESTS) * ARRAY_SIZE(vec_types))
|
||||
|
||||
@ -78,6 +89,15 @@ static int get_fpsimd(pid_t pid, struct user_fpsimd_state *fpsimd)
|
||||
return ptrace(PTRACE_GETREGSET, pid, NT_PRFPREG, &iov);
|
||||
}
|
||||
|
||||
static int set_fpsimd(pid_t pid, struct user_fpsimd_state *fpsimd)
|
||||
{
|
||||
struct iovec iov;
|
||||
|
||||
iov.iov_base = fpsimd;
|
||||
iov.iov_len = sizeof(*fpsimd);
|
||||
return ptrace(PTRACE_SETREGSET, pid, NT_PRFPREG, &iov);
|
||||
}
|
||||
|
||||
static struct user_sve_header *get_sve(pid_t pid, const struct vec_type *type,
|
||||
void **buf, size_t *size)
|
||||
{
|
||||
@ -240,28 +260,24 @@ static void check_u32(unsigned int vl, const char *reg,
|
||||
/* Access the FPSIMD registers via the SVE regset */
|
||||
static void ptrace_sve_fpsimd(pid_t child, const struct vec_type *type)
|
||||
{
|
||||
void *svebuf = NULL;
|
||||
size_t svebufsz = 0;
|
||||
void *svebuf;
|
||||
struct user_sve_header *sve;
|
||||
struct user_fpsimd_state *fpsimd, new_fpsimd;
|
||||
unsigned int i, j;
|
||||
unsigned char *p;
|
||||
int ret;
|
||||
|
||||
/* New process should start with FPSIMD registers only */
|
||||
sve = get_sve(child, type, &svebuf, &svebufsz);
|
||||
if (!sve) {
|
||||
ksft_test_result_fail("get_sve(%s): %s\n",
|
||||
type->name, strerror(errno));
|
||||
|
||||
svebuf = malloc(SVE_PT_SIZE(0, SVE_PT_REGS_FPSIMD));
|
||||
if (!svebuf) {
|
||||
ksft_test_result_fail("Failed to allocate FPSIMD buffer\n");
|
||||
return;
|
||||
} else {
|
||||
ksft_test_result_pass("get_sve(%s FPSIMD)\n", type->name);
|
||||
}
|
||||
|
||||
ksft_test_result((sve->flags & SVE_PT_REGS_MASK) == SVE_PT_REGS_FPSIMD,
|
||||
"Got FPSIMD registers via %s\n", type->name);
|
||||
if ((sve->flags & SVE_PT_REGS_MASK) != SVE_PT_REGS_FPSIMD)
|
||||
goto out;
|
||||
memset(svebuf, 0, SVE_PT_SIZE(0, SVE_PT_REGS_FPSIMD));
|
||||
sve = svebuf;
|
||||
sve->flags = SVE_PT_REGS_FPSIMD;
|
||||
sve->size = SVE_PT_SIZE(0, SVE_PT_REGS_FPSIMD);
|
||||
sve->vl = 16; /* We don't care what the VL is */
|
||||
|
||||
/* Try to set a known FPSIMD state via PT_REGS_SVE */
|
||||
fpsimd = (struct user_fpsimd_state *)((char *)sve +
|
||||
@ -273,12 +289,11 @@ static void ptrace_sve_fpsimd(pid_t child, const struct vec_type *type)
|
||||
p[j] = j;
|
||||
}
|
||||
|
||||
if (set_sve(child, type, sve)) {
|
||||
ksft_test_result_fail("set_sve(%s FPSIMD): %s\n",
|
||||
type->name, strerror(errno));
|
||||
|
||||
ret = set_sve(child, type, sve);
|
||||
ksft_test_result(ret == 0, "%s FPSIMD set via SVE: %d\n",
|
||||
type->name, ret);
|
||||
if (ret)
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Verify via the FPSIMD regset */
|
||||
if (get_fpsimd(child, &new_fpsimd)) {
|
||||
@ -395,7 +410,7 @@ out:
|
||||
free(write_buf);
|
||||
}
|
||||
|
||||
/* Validate attempting to set SVE data and read SVE data */
|
||||
/* Validate attempting to set SVE data and read it via the FPSIMD regset */
|
||||
static void ptrace_set_sve_get_fpsimd_data(pid_t child,
|
||||
const struct vec_type *type,
|
||||
unsigned int vl)
|
||||
@ -478,6 +493,115 @@ out:
|
||||
free(write_buf);
|
||||
}
|
||||
|
||||
/* Validate attempting to set FPSIMD data and read it via the SVE regset */
|
||||
static void ptrace_set_fpsimd_get_sve_data(pid_t child,
|
||||
const struct vec_type *type,
|
||||
unsigned int vl)
|
||||
{
|
||||
void *read_buf = NULL;
|
||||
unsigned char *p;
|
||||
struct user_sve_header *read_sve;
|
||||
unsigned int vq = sve_vq_from_vl(vl);
|
||||
struct user_fpsimd_state write_fpsimd;
|
||||
int ret, i, j;
|
||||
size_t read_sve_size = 0;
|
||||
size_t expected_size;
|
||||
int errors = 0;
|
||||
|
||||
if (__BYTE_ORDER == __BIG_ENDIAN) {
|
||||
ksft_test_result_skip("Big endian not supported\n");
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < 32; ++i) {
|
||||
p = (unsigned char *)&write_fpsimd.vregs[i];
|
||||
|
||||
for (j = 0; j < sizeof(write_fpsimd.vregs[i]); ++j)
|
||||
p[j] = j;
|
||||
}
|
||||
|
||||
ret = set_fpsimd(child, &write_fpsimd);
|
||||
if (ret != 0) {
|
||||
ksft_test_result_fail("Failed to set FPSIMD state: %d\n)",
|
||||
ret);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!get_sve(child, type, (void **)&read_buf, &read_sve_size)) {
|
||||
ksft_test_result_fail("Failed to read %s VL %u data\n",
|
||||
type->name, vl);
|
||||
return;
|
||||
}
|
||||
read_sve = read_buf;
|
||||
|
||||
if (read_sve->vl != vl) {
|
||||
ksft_test_result_fail("Child VL != expected VL %d\n",
|
||||
read_sve->vl, vl);
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* The kernel may return either SVE or FPSIMD format */
|
||||
switch (read_sve->flags & SVE_PT_REGS_MASK) {
|
||||
case SVE_PT_REGS_FPSIMD:
|
||||
expected_size = SVE_PT_FPSIMD_SIZE(vq, SVE_PT_REGS_FPSIMD);
|
||||
if (read_sve_size < expected_size) {
|
||||
ksft_test_result_fail("Read %d bytes, expected %d\n",
|
||||
read_sve_size, expected_size);
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = memcmp(&write_fpsimd, read_buf + SVE_PT_FPSIMD_OFFSET,
|
||||
sizeof(write_fpsimd));
|
||||
if (ret != 0) {
|
||||
ksft_print_msg("Read FPSIMD data mismatch\n");
|
||||
errors++;
|
||||
}
|
||||
break;
|
||||
|
||||
case SVE_PT_REGS_SVE:
|
||||
expected_size = SVE_PT_SVE_SIZE(vq, SVE_PT_REGS_SVE);
|
||||
if (read_sve_size < expected_size) {
|
||||
ksft_test_result_fail("Read %d bytes, expected %d\n",
|
||||
read_sve_size, expected_size);
|
||||
goto out;
|
||||
}
|
||||
|
||||
for (i = 0; i < __SVE_NUM_ZREGS; i++) {
|
||||
__uint128_t tmp = 0;
|
||||
|
||||
/*
|
||||
* Z regs are stored endianness invariant, this won't
|
||||
* work for big endian
|
||||
*/
|
||||
memcpy(&tmp, read_buf + SVE_PT_SVE_ZREG_OFFSET(vq, i),
|
||||
sizeof(tmp));
|
||||
|
||||
if (tmp != write_fpsimd.vregs[i]) {
|
||||
ksft_print_msg("Mismatch in FPSIMD for %s VL %u Z%d/V%d\n",
|
||||
type->name, vl, i, i);
|
||||
errors++;
|
||||
}
|
||||
}
|
||||
|
||||
check_u32(vl, "FPSR", &write_fpsimd.fpsr,
|
||||
read_buf + SVE_PT_SVE_FPSR_OFFSET(vq), &errors);
|
||||
check_u32(vl, "FPCR", &write_fpsimd.fpcr,
|
||||
read_buf + SVE_PT_SVE_FPCR_OFFSET(vq), &errors);
|
||||
break;
|
||||
default:
|
||||
ksft_print_msg("Unexpected regs type %d\n",
|
||||
read_sve->flags & SVE_PT_REGS_MASK);
|
||||
errors++;
|
||||
break;
|
||||
}
|
||||
|
||||
ksft_test_result(errors == 0, "Set FPSIMD, read via SVE for %s VL %u\n",
|
||||
type->name, vl);
|
||||
|
||||
out:
|
||||
free(read_buf);
|
||||
}
|
||||
|
||||
static int do_parent(pid_t child)
|
||||
{
|
||||
int ret = EXIT_FAILURE;
|
||||
@ -548,11 +672,9 @@ static int do_parent(pid_t child)
|
||||
if (getauxval(vec_types[i].hwcap_type) & vec_types[i].hwcap) {
|
||||
ptrace_sve_fpsimd(child, &vec_types[i]);
|
||||
} else {
|
||||
ksft_test_result_skip("%s FPSIMD get via SVE\n",
|
||||
vec_types[i].name);
|
||||
ksft_test_result_skip("%s FPSIMD set via SVE\n",
|
||||
vec_types[i].name);
|
||||
ksft_test_result_skip("%s set read via FPSIMD\n",
|
||||
ksft_test_result_skip("%s FPSIMD read\n",
|
||||
vec_types[i].name);
|
||||
}
|
||||
|
||||
@ -585,11 +707,14 @@ static int do_parent(pid_t child)
|
||||
if (vl_supported) {
|
||||
ptrace_set_sve_get_sve_data(child, &vec_types[i], vl);
|
||||
ptrace_set_sve_get_fpsimd_data(child, &vec_types[i], vl);
|
||||
ptrace_set_fpsimd_get_sve_data(child, &vec_types[i], vl);
|
||||
} else {
|
||||
ksft_test_result_skip("%s set SVE get SVE for VL %d\n",
|
||||
vec_types[i].name, vl);
|
||||
ksft_test_result_skip("%s set SVE get FPSIMD for VL %d\n",
|
||||
vec_types[i].name, vl);
|
||||
ksft_test_result_skip("%s set FPSIMD get SVE for VL %d\n",
|
||||
vec_types[i].name, vl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include <asm/unistd.h>
|
||||
#include "assembler.h"
|
||||
#include "asm-offsets.h"
|
||||
#include "sme-inst.h"
|
||||
|
||||
#define NZR 32
|
||||
#define NPR 16
|
||||
@ -156,6 +157,7 @@ endfunction
|
||||
// We fill the upper lanes of FFR with zeros.
|
||||
// Beware: corrupts P0.
|
||||
function setup_ffr
|
||||
#ifndef SSVE
|
||||
mov x4, x30
|
||||
|
||||
and w0, w0, #0x3
|
||||
@ -178,6 +180,9 @@ function setup_ffr
|
||||
wrffr p0.b
|
||||
|
||||
ret x4
|
||||
#else
|
||||
ret
|
||||
#endif
|
||||
endfunction
|
||||
|
||||
// Trivial memory compare: compare x2 bytes starting at address x0 with
|
||||
@ -260,6 +265,7 @@ endfunction
|
||||
// Beware -- corrupts P0.
|
||||
// Clobbers x0-x5.
|
||||
function check_ffr
|
||||
#ifndef SSVE
|
||||
mov x3, x30
|
||||
|
||||
ldr x4, =scratch
|
||||
@ -280,6 +286,9 @@ function check_ffr
|
||||
mov x2, x5
|
||||
mov x30, x3
|
||||
b memcmp
|
||||
#else
|
||||
ret
|
||||
#endif
|
||||
endfunction
|
||||
|
||||
// Any SVE register modified here can cause corruption in the main
|
||||
@ -295,10 +304,12 @@ function irritator_handler
|
||||
movi v0.8b, #1
|
||||
movi v9.16b, #2
|
||||
movi v31.8b, #3
|
||||
#ifndef SSVE
|
||||
// And P0
|
||||
rdffr p0.b
|
||||
// And FFR
|
||||
wrffr p15.b
|
||||
#endif
|
||||
|
||||
ret
|
||||
endfunction
|
||||
@ -359,6 +370,11 @@ endfunction
|
||||
.globl _start
|
||||
function _start
|
||||
_start:
|
||||
#ifdef SSVE
|
||||
puts "Streaming mode "
|
||||
smstart_sm
|
||||
#endif
|
||||
|
||||
// Sanity-check and report the vector length
|
||||
|
||||
rdvl x19, #8
|
||||
@ -407,6 +423,10 @@ _start:
|
||||
orr w2, w2, #SA_NODEFER
|
||||
bl setsignal
|
||||
|
||||
#ifdef SSVE
|
||||
smstart_sm // syscalls will have exited streaming mode
|
||||
#endif
|
||||
|
||||
mov x22, #0 // generation number, increments per iteration
|
||||
.Ltest_loop:
|
||||
rdvl x0, #8
|
||||
|
@ -51,6 +51,16 @@ static struct vec_data vec_data[] = {
|
||||
.prctl_set = PR_SVE_SET_VL,
|
||||
.default_vl_file = "/proc/sys/abi/sve_default_vector_length",
|
||||
},
|
||||
{
|
||||
.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)
|
||||
|
@ -22,12 +22,15 @@ static int inherit = 0;
|
||||
static int no_inherit = 0;
|
||||
static int force = 0;
|
||||
static unsigned long vl;
|
||||
static int set_ctl = PR_SVE_SET_VL;
|
||||
static int get_ctl = PR_SVE_GET_VL;
|
||||
|
||||
static const struct option options[] = {
|
||||
{ "force", no_argument, NULL, 'f' },
|
||||
{ "inherit", no_argument, NULL, 'i' },
|
||||
{ "max", no_argument, NULL, 'M' },
|
||||
{ "no-inherit", no_argument, &no_inherit, 1 },
|
||||
{ "sme", no_argument, NULL, 's' },
|
||||
{ "help", no_argument, NULL, '?' },
|
||||
{}
|
||||
};
|
||||
@ -50,6 +53,9 @@ static int parse_options(int argc, char **argv)
|
||||
case 'M': vl = SVE_VL_MAX; break;
|
||||
case 'f': force = 1; break;
|
||||
case 'i': inherit = 1; break;
|
||||
case 's': set_ctl = PR_SME_SET_VL;
|
||||
get_ctl = PR_SME_GET_VL;
|
||||
break;
|
||||
case 0: break;
|
||||
default: goto error;
|
||||
}
|
||||
@ -125,14 +131,14 @@ int main(int argc, char **argv)
|
||||
if (inherit)
|
||||
flags |= PR_SVE_VL_INHERIT;
|
||||
|
||||
t = prctl(PR_SVE_SET_VL, vl | flags);
|
||||
t = prctl(set_ctl, vl | flags);
|
||||
if (t < 0) {
|
||||
fprintf(stderr, "%s: PR_SVE_SET_VL: %s\n",
|
||||
program_name, strerror(errno));
|
||||
goto error;
|
||||
}
|
||||
|
||||
t = prctl(PR_SVE_GET_VL);
|
||||
t = prctl(get_ctl);
|
||||
if (t == -1) {
|
||||
fprintf(stderr, "%s: PR_SVE_GET_VL: %s\n",
|
||||
program_name, strerror(errno));
|
||||
|
61
tools/testing/selftests/arm64/fp/za-fork-asm.S
Normal file
61
tools/testing/selftests/arm64/fp/za-fork-asm.S
Normal file
@ -0,0 +1,61 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
// Copyright (C) 2021 ARM Limited.
|
||||
|
||||
#include "sme-inst.h"
|
||||
|
||||
.arch_extension sve
|
||||
|
||||
#define MAGIC 42
|
||||
|
||||
#define MAXVL 2048
|
||||
#define MAXVL_B (MAXVL / 8)
|
||||
|
||||
.pushsection .text
|
||||
.data
|
||||
.align 4
|
||||
scratch:
|
||||
.space MAXVL_B
|
||||
.popsection
|
||||
|
||||
.globl fork_test
|
||||
fork_test:
|
||||
smstart_za
|
||||
|
||||
// For simplicity just set one word in one vector, other tests
|
||||
// cover general data corruption issues.
|
||||
ldr x0, =scratch
|
||||
mov x1, #MAGIC
|
||||
str x1, [x0]
|
||||
mov w12, wzr
|
||||
_ldr_za 12, 0 // ZA.H[W12] loaded from [X0]
|
||||
|
||||
// Tail call into the C portion that does the fork & verify
|
||||
b fork_test_c
|
||||
|
||||
.globl verify_fork
|
||||
verify_fork:
|
||||
// SVCR should have ZA=1, SM=0
|
||||
mrs x0, S3_3_C4_C2_2
|
||||
and x1, x0, #3
|
||||
cmp x1, #2
|
||||
beq 1f
|
||||
mov x0, xzr
|
||||
b 100f
|
||||
1:
|
||||
|
||||
// ZA should still have the value we loaded
|
||||
ldr x0, =scratch
|
||||
mov w12, wzr
|
||||
_str_za 12, 0 // ZA.H[W12] stored to [X0]
|
||||
ldr x1, [x0]
|
||||
cmp x1, #MAGIC
|
||||
beq 2f
|
||||
mov x0, xzr
|
||||
b 100f
|
||||
|
||||
2:
|
||||
// All tests passed
|
||||
mov x0, #1
|
||||
100:
|
||||
ret
|
||||
|
156
tools/testing/selftests/arm64/fp/za-fork.c
Normal file
156
tools/testing/selftests/arm64/fp/za-fork.c
Normal file
@ -0,0 +1,156 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (C) 2022 ARM Limited.
|
||||
* Original author: Mark Brown <broonie@kernel.org>
|
||||
*/
|
||||
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
#include <linux/sched.h>
|
||||
#include <linux/wait.h>
|
||||
|
||||
#define EXPECTED_TESTS 1
|
||||
|
||||
static void putstr(const char *str)
|
||||
{
|
||||
write(1, str, strlen(str));
|
||||
}
|
||||
|
||||
static void putnum(unsigned int num)
|
||||
{
|
||||
char c;
|
||||
|
||||
if (num / 10)
|
||||
putnum(num / 10);
|
||||
|
||||
c = '0' + (num % 10);
|
||||
write(1, &c, 1);
|
||||
}
|
||||
|
||||
static int tests_run;
|
||||
static int tests_passed;
|
||||
static int tests_failed;
|
||||
static int tests_skipped;
|
||||
|
||||
static void print_summary(void)
|
||||
{
|
||||
if (tests_passed + tests_failed + tests_skipped != EXPECTED_TESTS)
|
||||
putstr("# UNEXPECTED TEST COUNT: ");
|
||||
|
||||
putstr("# Totals: pass:");
|
||||
putnum(tests_passed);
|
||||
putstr(" fail:");
|
||||
putnum(tests_failed);
|
||||
putstr(" xfail:0 xpass:0 skip:");
|
||||
putnum(tests_skipped);
|
||||
putstr(" error:0\n");
|
||||
}
|
||||
|
||||
int fork_test(void);
|
||||
int verify_fork(void);
|
||||
|
||||
/*
|
||||
* If we fork the value in the parent should be unchanged and the
|
||||
* child should start with the same value. This is called from the
|
||||
* fork_test() asm function.
|
||||
*/
|
||||
int fork_test_c(void)
|
||||
{
|
||||
pid_t newpid, waiting;
|
||||
int child_status, parent_result;
|
||||
|
||||
newpid = fork();
|
||||
if (newpid == 0) {
|
||||
/* In child */
|
||||
if (!verify_fork()) {
|
||||
putstr("# ZA state invalid in child\n");
|
||||
exit(0);
|
||||
} else {
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
if (newpid < 0) {
|
||||
putstr("# fork() failed: -");
|
||||
putnum(-newpid);
|
||||
putstr("\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
parent_result = verify_fork();
|
||||
if (!parent_result)
|
||||
putstr("# ZA state invalid in parent\n");
|
||||
|
||||
for (;;) {
|
||||
waiting = waitpid(newpid, &child_status, 0);
|
||||
|
||||
if (waiting < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
putstr("# waitpid() failed: ");
|
||||
putnum(errno);
|
||||
putstr("\n");
|
||||
return 0;
|
||||
}
|
||||
if (waiting != newpid) {
|
||||
putstr("# waitpid() returned wrong PID\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!WIFEXITED(child_status)) {
|
||||
putstr("# child did not exit\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return WEXITSTATUS(child_status) && parent_result;
|
||||
}
|
||||
}
|
||||
|
||||
#define run_test(name) \
|
||||
if (name()) { \
|
||||
tests_passed++; \
|
||||
} else { \
|
||||
tests_failed++; \
|
||||
putstr("not "); \
|
||||
} \
|
||||
putstr("ok "); \
|
||||
putnum(++tests_run); \
|
||||
putstr(" " #name "\n");
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int ret, i;
|
||||
|
||||
putstr("TAP version 13\n");
|
||||
putstr("1..");
|
||||
putnum(EXPECTED_TESTS);
|
||||
putstr("\n");
|
||||
|
||||
putstr("# PID: ");
|
||||
putnum(getpid());
|
||||
putstr("\n");
|
||||
|
||||
/*
|
||||
* This test is run with nolibc which doesn't support hwcap and
|
||||
* it's probably disproportionate to implement so instead check
|
||||
* for the default vector length configuration in /proc.
|
||||
*/
|
||||
ret = open("/proc/sys/abi/sme_default_vector_length", O_RDONLY, 0);
|
||||
if (ret >= 0) {
|
||||
run_test(fork_test);
|
||||
|
||||
} else {
|
||||
putstr("# SME support not present\n");
|
||||
|
||||
for (i = 0; i < EXPECTED_TESTS; i++) {
|
||||
putstr("ok ");
|
||||
putnum(i);
|
||||
putstr(" skipped\n");
|
||||
}
|
||||
|
||||
tests_skipped += EXPECTED_TESTS;
|
||||
}
|
||||
|
||||
print_summary();
|
||||
|
||||
return 0;
|
||||
}
|
356
tools/testing/selftests/arm64/fp/za-ptrace.c
Normal file
356
tools/testing/selftests/arm64/fp/za-ptrace.c
Normal file
@ -0,0 +1,356 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (C) 2021 ARM Limited.
|
||||
*/
|
||||
#include <errno.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/ptrace.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/uio.h>
|
||||
#include <sys/wait.h>
|
||||
#include <asm/sigcontext.h>
|
||||
#include <asm/ptrace.h>
|
||||
|
||||
#include "../../kselftest.h"
|
||||
|
||||
/* <linux/elf.h> and <sys/auxv.h> don't like each other, so: */
|
||||
#ifndef NT_ARM_ZA
|
||||
#define NT_ARM_ZA 0x40c
|
||||
#endif
|
||||
|
||||
#define EXPECTED_TESTS (((SVE_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 <= SVE_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;
|
||||
}
|
59
tools/testing/selftests/arm64/fp/za-stress
Normal file
59
tools/testing/selftests/arm64/fp/za-stress
Normal file
@ -0,0 +1,59 @@
|
||||
#!/bin/bash
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
# Copyright (C) 2015-2019 ARM Limited.
|
||||
# Original author: Dave Martin <Dave.Martin@arm.com>
|
||||
|
||||
set -ue
|
||||
|
||||
NR_CPUS=`nproc`
|
||||
|
||||
pids=
|
||||
logs=
|
||||
|
||||
cleanup () {
|
||||
trap - INT TERM CHLD
|
||||
set +e
|
||||
|
||||
if [ -n "$pids" ]; then
|
||||
kill $pids
|
||||
wait $pids
|
||||
pids=
|
||||
fi
|
||||
|
||||
if [ -n "$logs" ]; then
|
||||
cat $logs
|
||||
rm $logs
|
||||
logs=
|
||||
fi
|
||||
}
|
||||
|
||||
interrupt () {
|
||||
cleanup
|
||||
exit 0
|
||||
}
|
||||
|
||||
child_died () {
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
|
||||
trap interrupt INT TERM EXIT
|
||||
|
||||
for x in `seq 0 $((NR_CPUS * 4))`; do
|
||||
log=`mktemp`
|
||||
logs=$logs\ $log
|
||||
./za-test >$log &
|
||||
pids=$pids\ $!
|
||||
done
|
||||
|
||||
# Wait for all child processes to be created:
|
||||
sleep 10
|
||||
|
||||
while :; do
|
||||
kill -USR1 $pids
|
||||
done &
|
||||
pids=$pids\ $!
|
||||
|
||||
wait
|
||||
|
||||
exit 1
|
388
tools/testing/selftests/arm64/fp/za-test.S
Normal file
388
tools/testing/selftests/arm64/fp/za-test.S
Normal file
@ -0,0 +1,388 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
// Copyright (C) 2021 ARM Limited.
|
||||
// Original author: Mark Brown <broonie@kernel.org>
|
||||
//
|
||||
// Scalable Matrix Extension ZA context switch test
|
||||
// Repeatedly writes unique test patterns into each ZA tile
|
||||
// and reads them back to verify integrity.
|
||||
//
|
||||
// for x in `seq 1 NR_CPUS`; do sve-test & pids=$pids\ $! ; done
|
||||
// (leave it running for as long as you want...)
|
||||
// kill $pids
|
||||
|
||||
#include <asm/unistd.h>
|
||||
#include "assembler.h"
|
||||
#include "asm-offsets.h"
|
||||
#include "sme-inst.h"
|
||||
|
||||
.arch_extension sve
|
||||
|
||||
#define MAXVL 2048
|
||||
#define MAXVL_B (MAXVL / 8)
|
||||
|
||||
// Declare some storage space to shadow ZA register contents and a
|
||||
// scratch buffer for a vector.
|
||||
.pushsection .text
|
||||
.data
|
||||
.align 4
|
||||
zaref:
|
||||
.space MAXVL_B * MAXVL_B
|
||||
scratch:
|
||||
.space MAXVL_B
|
||||
.popsection
|
||||
|
||||
// Trivial memory copy: copy x2 bytes, starting at address x1, to address x0.
|
||||
// Clobbers x0-x3
|
||||
function memcpy
|
||||
cmp x2, #0
|
||||
b.eq 1f
|
||||
0: ldrb w3, [x1], #1
|
||||
strb w3, [x0], #1
|
||||
subs x2, x2, #1
|
||||
b.ne 0b
|
||||
1: ret
|
||||
endfunction
|
||||
|
||||
// Generate a test pattern for storage in ZA
|
||||
// x0: pid
|
||||
// x1: row in ZA
|
||||
// x2: generation
|
||||
|
||||
// These values are used to constuct a 32-bit pattern that is repeated in the
|
||||
// scratch buffer as many times as will fit:
|
||||
// bits 31:28 generation number (increments once per test_loop)
|
||||
// bits 27:16 pid
|
||||
// bits 15: 8 row number
|
||||
// bits 7: 0 32-bit lane index
|
||||
|
||||
function pattern
|
||||
mov w3, wzr
|
||||
bfi w3, w0, #16, #12 // PID
|
||||
bfi w3, w1, #8, #8 // Row
|
||||
bfi w3, w2, #28, #4 // Generation
|
||||
|
||||
ldr x0, =scratch
|
||||
mov w1, #MAXVL_B / 4
|
||||
|
||||
0: str w3, [x0], #4
|
||||
add w3, w3, #1 // Lane
|
||||
subs w1, w1, #1
|
||||
b.ne 0b
|
||||
|
||||
ret
|
||||
endfunction
|
||||
|
||||
// Get the address of shadow data for ZA horizontal vector xn
|
||||
.macro _adrza xd, xn, nrtmp
|
||||
ldr \xd, =zaref
|
||||
rdsvl \nrtmp, 1
|
||||
madd \xd, x\nrtmp, \xn, \xd
|
||||
.endm
|
||||
|
||||
// Set up test pattern in a ZA horizontal vector
|
||||
// x0: pid
|
||||
// x1: row number
|
||||
// x2: generation
|
||||
function setup_za
|
||||
mov x4, x30
|
||||
mov x12, x1 // Use x12 for vector select
|
||||
|
||||
bl pattern // Get pattern in scratch buffer
|
||||
_adrza x0, x12, 2 // Shadow buffer pointer to x0 and x5
|
||||
mov x5, x0
|
||||
ldr x1, =scratch
|
||||
bl memcpy // length set up in x2 by _adrza
|
||||
|
||||
_ldr_za 12, 5 // load vector w12 from pointer x5
|
||||
|
||||
ret x4
|
||||
endfunction
|
||||
|
||||
// Trivial memory compare: compare x2 bytes starting at address x0 with
|
||||
// bytes starting at address x1.
|
||||
// Returns only if all bytes match; otherwise, the program is aborted.
|
||||
// Clobbers x0-x5.
|
||||
function memcmp
|
||||
cbz x2, 2f
|
||||
|
||||
stp x0, x1, [sp, #-0x20]!
|
||||
str x2, [sp, #0x10]
|
||||
|
||||
mov x5, #0
|
||||
0: ldrb w3, [x0, x5]
|
||||
ldrb w4, [x1, x5]
|
||||
add x5, x5, #1
|
||||
cmp w3, w4
|
||||
b.ne 1f
|
||||
subs x2, x2, #1
|
||||
b.ne 0b
|
||||
|
||||
1: ldr x2, [sp, #0x10]
|
||||
ldp x0, x1, [sp], #0x20
|
||||
b.ne barf
|
||||
|
||||
2: ret
|
||||
endfunction
|
||||
|
||||
// Verify that a ZA vector matches its shadow in memory, else abort
|
||||
// x0: row number
|
||||
// Clobbers x0-x7 and x12.
|
||||
function check_za
|
||||
mov x3, x30
|
||||
|
||||
mov x12, x0
|
||||
_adrza x5, x0, 6 // pointer to expected value in x5
|
||||
mov x4, x0
|
||||
ldr x7, =scratch // x7 is scratch
|
||||
|
||||
mov x0, x7 // Poison scratch
|
||||
mov x1, x6
|
||||
bl memfill_ae
|
||||
|
||||
_str_za 12, 7 // save vector w12 to pointer x7
|
||||
|
||||
mov x0, x5
|
||||
mov x1, x7
|
||||
mov x2, x6
|
||||
mov x30, x3
|
||||
b memcmp
|
||||
endfunction
|
||||
|
||||
// Any SME register modified here can cause corruption in the main
|
||||
// thread -- but *only* the locations modified here.
|
||||
function irritator_handler
|
||||
// Increment the irritation signal count (x23):
|
||||
ldr x0, [x2, #ucontext_regs + 8 * 23]
|
||||
add x0, x0, #1
|
||||
str x0, [x2, #ucontext_regs + 8 * 23]
|
||||
|
||||
// Corrupt some random ZA data
|
||||
#if 0
|
||||
adr x0, .text + (irritator_handler - .text) / 16 * 16
|
||||
movi v0.8b, #1
|
||||
movi v9.16b, #2
|
||||
movi v31.8b, #3
|
||||
#endif
|
||||
|
||||
ret
|
||||
endfunction
|
||||
|
||||
function terminate_handler
|
||||
mov w21, w0
|
||||
mov x20, x2
|
||||
|
||||
puts "Terminated by signal "
|
||||
mov w0, w21
|
||||
bl putdec
|
||||
puts ", no error, iterations="
|
||||
ldr x0, [x20, #ucontext_regs + 8 * 22]
|
||||
bl putdec
|
||||
puts ", signals="
|
||||
ldr x0, [x20, #ucontext_regs + 8 * 23]
|
||||
bl putdecn
|
||||
|
||||
mov x0, #0
|
||||
mov x8, #__NR_exit
|
||||
svc #0
|
||||
endfunction
|
||||
|
||||
// w0: signal number
|
||||
// x1: sa_action
|
||||
// w2: sa_flags
|
||||
// Clobbers x0-x6,x8
|
||||
function setsignal
|
||||
str x30, [sp, #-((sa_sz + 15) / 16 * 16 + 16)]!
|
||||
|
||||
mov w4, w0
|
||||
mov x5, x1
|
||||
mov w6, w2
|
||||
|
||||
add x0, sp, #16
|
||||
mov x1, #sa_sz
|
||||
bl memclr
|
||||
|
||||
mov w0, w4
|
||||
add x1, sp, #16
|
||||
str w6, [x1, #sa_flags]
|
||||
str x5, [x1, #sa_handler]
|
||||
mov x2, #0
|
||||
mov x3, #sa_mask_sz
|
||||
mov x8, #__NR_rt_sigaction
|
||||
svc #0
|
||||
|
||||
cbz w0, 1f
|
||||
|
||||
puts "sigaction failure\n"
|
||||
b .Labort
|
||||
|
||||
1: ldr x30, [sp], #((sa_sz + 15) / 16 * 16 + 16)
|
||||
ret
|
||||
endfunction
|
||||
|
||||
// Main program entry point
|
||||
.globl _start
|
||||
function _start
|
||||
_start:
|
||||
puts "Streaming mode "
|
||||
smstart_za
|
||||
|
||||
// Sanity-check and report the vector length
|
||||
|
||||
rdsvl 19, 8
|
||||
cmp x19, #128
|
||||
b.lo 1f
|
||||
cmp x19, #2048
|
||||
b.hi 1f
|
||||
tst x19, #(8 - 1)
|
||||
b.eq 2f
|
||||
|
||||
1: puts "bad vector length: "
|
||||
mov x0, x19
|
||||
bl putdecn
|
||||
b .Labort
|
||||
|
||||
2: puts "vector length:\t"
|
||||
mov x0, x19
|
||||
bl putdec
|
||||
puts " bits\n"
|
||||
|
||||
// Obtain our PID, to ensure test pattern uniqueness between processes
|
||||
mov x8, #__NR_getpid
|
||||
svc #0
|
||||
mov x20, x0
|
||||
|
||||
puts "PID:\t"
|
||||
mov x0, x20
|
||||
bl putdecn
|
||||
|
||||
mov x23, #0 // Irritation signal count
|
||||
|
||||
mov w0, #SIGINT
|
||||
adr x1, terminate_handler
|
||||
mov w2, #SA_SIGINFO
|
||||
bl setsignal
|
||||
|
||||
mov w0, #SIGTERM
|
||||
adr x1, terminate_handler
|
||||
mov w2, #SA_SIGINFO
|
||||
bl setsignal
|
||||
|
||||
mov w0, #SIGUSR1
|
||||
adr x1, irritator_handler
|
||||
mov w2, #SA_SIGINFO
|
||||
orr w2, w2, #SA_NODEFER
|
||||
bl setsignal
|
||||
|
||||
mov x22, #0 // generation number, increments per iteration
|
||||
.Ltest_loop:
|
||||
rdsvl 0, 8
|
||||
cmp x0, x19
|
||||
b.ne vl_barf
|
||||
|
||||
rdsvl 21, 1 // Set up ZA & shadow with test pattern
|
||||
0: mov x0, x20
|
||||
sub x1, x21, #1
|
||||
mov x2, x22
|
||||
bl setup_za
|
||||
subs x21, x21, #1
|
||||
b.ne 0b
|
||||
|
||||
and x8, x22, #127 // Every 128 interations...
|
||||
cbz x8, 0f
|
||||
mov x8, #__NR_getpid // (otherwise minimal syscall)
|
||||
b 1f
|
||||
0:
|
||||
mov x8, #__NR_sched_yield // ...encourage preemption
|
||||
1:
|
||||
svc #0
|
||||
|
||||
mrs x0, S3_3_C4_C2_2 // SVCR should have ZA=1,SM=0
|
||||
and x1, x0, #3
|
||||
cmp x1, #2
|
||||
b.ne svcr_barf
|
||||
|
||||
rdsvl 21, 1 // Verify that the data made it through
|
||||
rdsvl 24, 1 // Verify that the data made it through
|
||||
0: sub x0, x24, x21
|
||||
bl check_za
|
||||
subs x21, x21, #1
|
||||
bne 0b
|
||||
|
||||
add x22, x22, #1 // Everything still working
|
||||
b .Ltest_loop
|
||||
|
||||
.Labort:
|
||||
mov x0, #0
|
||||
mov x1, #SIGABRT
|
||||
mov x8, #__NR_kill
|
||||
svc #0
|
||||
endfunction
|
||||
|
||||
function barf
|
||||
// fpsimd.c acitivty log dump hack
|
||||
// ldr w0, =0xdeadc0de
|
||||
// mov w8, #__NR_exit
|
||||
// svc #0
|
||||
// end hack
|
||||
smstop
|
||||
mov x10, x0 // expected data
|
||||
mov x11, x1 // actual data
|
||||
mov x12, x2 // data size
|
||||
|
||||
puts "Mismatch: PID="
|
||||
mov x0, x20
|
||||
bl putdec
|
||||
puts ", iteration="
|
||||
mov x0, x22
|
||||
bl putdec
|
||||
puts ", row="
|
||||
mov x0, x21
|
||||
bl putdecn
|
||||
puts "\tExpected ["
|
||||
mov x0, x10
|
||||
mov x1, x12
|
||||
bl dumphex
|
||||
puts "]\n\tGot ["
|
||||
mov x0, x11
|
||||
mov x1, x12
|
||||
bl dumphex
|
||||
puts "]\n"
|
||||
|
||||
mov x8, #__NR_getpid
|
||||
svc #0
|
||||
// fpsimd.c acitivty log dump hack
|
||||
// ldr w0, =0xdeadc0de
|
||||
// mov w8, #__NR_exit
|
||||
// svc #0
|
||||
// ^ end of hack
|
||||
mov x1, #SIGABRT
|
||||
mov x8, #__NR_kill
|
||||
svc #0
|
||||
// mov x8, #__NR_exit
|
||||
// mov x1, #1
|
||||
// svc #0
|
||||
endfunction
|
||||
|
||||
function vl_barf
|
||||
mov x10, x0
|
||||
|
||||
puts "Bad active VL: "
|
||||
mov x0, x10
|
||||
bl putdecn
|
||||
|
||||
mov x8, #__NR_exit
|
||||
mov x1, #1
|
||||
svc #0
|
||||
endfunction
|
||||
|
||||
function svcr_barf
|
||||
mov x10, x0
|
||||
|
||||
puts "Bad SVCR: "
|
||||
mov x0, x10
|
||||
bl putdecn
|
||||
|
||||
mov x8, #__NR_exit
|
||||
mov x1, #1
|
||||
svc #0
|
||||
endfunction
|
1
tools/testing/selftests/arm64/mte/.gitignore
vendored
1
tools/testing/selftests/arm64/mte/.gitignore
vendored
@ -3,5 +3,6 @@ check_gcr_el1_cswitch
|
||||
check_tags_inclusion
|
||||
check_child_memory
|
||||
check_mmap_options
|
||||
check_prctl
|
||||
check_ksm_options
|
||||
check_user_mem
|
||||
|
@ -85,9 +85,9 @@ static int check_child_memory_mapping(int mem_type, int mode, int mapping)
|
||||
{
|
||||
char *ptr;
|
||||
int run, result;
|
||||
int item = sizeof(sizes)/sizeof(int);
|
||||
int item = ARRAY_SIZE(sizes);
|
||||
|
||||
item = sizeof(sizes)/sizeof(int);
|
||||
item = ARRAY_SIZE(sizes);
|
||||
mte_switch_mode(mode, MTE_ALLOW_NON_ZERO_TAG);
|
||||
for (run = 0; run < item; run++) {
|
||||
ptr = (char *)mte_allocate_memory_tag_range(sizes[run], mem_type, mapping,
|
||||
@ -107,7 +107,7 @@ static int check_child_file_mapping(int mem_type, int mode, int mapping)
|
||||
{
|
||||
char *ptr, *map_ptr;
|
||||
int run, fd, map_size, result = KSFT_PASS;
|
||||
int total = sizeof(sizes)/sizeof(int);
|
||||
int total = ARRAY_SIZE(sizes);
|
||||
|
||||
mte_switch_mode(mode, MTE_ALLOW_NON_ZERO_TAG);
|
||||
for (run = 0; run < total; run++) {
|
||||
@ -144,7 +144,7 @@ static int check_child_file_mapping(int mem_type, int mode, int mapping)
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int err;
|
||||
int item = sizeof(sizes)/sizeof(int);
|
||||
int item = ARRAY_SIZE(sizes);
|
||||
|
||||
page_size = getpagesize();
|
||||
if (!page_size) {
|
||||
|
119
tools/testing/selftests/arm64/mte/check_prctl.c
Normal file
119
tools/testing/selftests/arm64/mte/check_prctl.c
Normal file
@ -0,0 +1,119 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
// Copyright (C) 2022 ARM Limited
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <sys/auxv.h>
|
||||
#include <sys/prctl.h>
|
||||
|
||||
#include <asm/hwcap.h>
|
||||
|
||||
#include "kselftest.h"
|
||||
|
||||
static int set_tagged_addr_ctrl(int val)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = prctl(PR_SET_TAGGED_ADDR_CTRL, val, 0, 0, 0);
|
||||
if (ret < 0)
|
||||
ksft_print_msg("PR_SET_TAGGED_ADDR_CTRL: failed %d %d (%s)\n",
|
||||
ret, errno, strerror(errno));
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int get_tagged_addr_ctrl(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = prctl(PR_GET_TAGGED_ADDR_CTRL, 0, 0, 0, 0);
|
||||
if (ret < 0)
|
||||
ksft_print_msg("PR_GET_TAGGED_ADDR_CTRL failed: %d %d (%s)\n",
|
||||
ret, errno, strerror(errno));
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read the current mode without having done any configuration, should
|
||||
* run first.
|
||||
*/
|
||||
void check_basic_read(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = get_tagged_addr_ctrl();
|
||||
if (ret < 0) {
|
||||
ksft_test_result_fail("check_basic_read\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ret & PR_MTE_TCF_SYNC)
|
||||
ksft_print_msg("SYNC enabled\n");
|
||||
if (ret & PR_MTE_TCF_ASYNC)
|
||||
ksft_print_msg("ASYNC enabled\n");
|
||||
|
||||
/* Any configuration is valid */
|
||||
ksft_test_result_pass("check_basic_read\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* Attempt to set a specified combination of modes.
|
||||
*/
|
||||
void set_mode_test(const char *name, int hwcap2, int mask)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if ((getauxval(AT_HWCAP2) & hwcap2) != hwcap2) {
|
||||
ksft_test_result_skip("%s\n", name);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = set_tagged_addr_ctrl(mask);
|
||||
if (ret < 0) {
|
||||
ksft_test_result_fail("%s\n", name);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = get_tagged_addr_ctrl();
|
||||
if (ret < 0) {
|
||||
ksft_test_result_fail("%s\n", name);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((ret & PR_MTE_TCF_MASK) == mask) {
|
||||
ksft_test_result_pass("%s\n", name);
|
||||
} else {
|
||||
ksft_print_msg("Got %x, expected %x\n",
|
||||
(ret & PR_MTE_TCF_MASK), mask);
|
||||
ksft_test_result_fail("%s\n", name);
|
||||
}
|
||||
}
|
||||
|
||||
struct mte_mode {
|
||||
int mask;
|
||||
int hwcap2;
|
||||
const char *name;
|
||||
} mte_modes[] = {
|
||||
{ PR_MTE_TCF_NONE, 0, "NONE" },
|
||||
{ PR_MTE_TCF_SYNC, HWCAP2_MTE, "SYNC" },
|
||||
{ PR_MTE_TCF_ASYNC, HWCAP2_MTE, "ASYNC" },
|
||||
{ PR_MTE_TCF_SYNC | PR_MTE_TCF_ASYNC, HWCAP2_MTE, "SYNC+ASYNC" },
|
||||
};
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
ksft_print_header();
|
||||
ksft_set_plan(5);
|
||||
|
||||
check_basic_read();
|
||||
for (i = 0; i < ARRAY_SIZE(mte_modes); i++)
|
||||
set_mode_test(mte_modes[i].name, mte_modes[i].hwcap2,
|
||||
mte_modes[i].mask);
|
||||
|
||||
ksft_print_cnts();
|
||||
|
||||
return 0;
|
||||
}
|
@ -23,10 +23,13 @@ static int verify_mte_pointer_validity(char *ptr, int mode)
|
||||
{
|
||||
mte_initialize_current_context(mode, (uintptr_t)ptr, BUFFER_SIZE);
|
||||
/* Check the validity of the tagged pointer */
|
||||
memset((void *)ptr, '1', BUFFER_SIZE);
|
||||
memset(ptr, '1', BUFFER_SIZE);
|
||||
mte_wait_after_trig();
|
||||
if (cur_mte_cxt.fault_valid)
|
||||
if (cur_mte_cxt.fault_valid) {
|
||||
ksft_print_msg("Unexpected fault recorded for %p-%p in mode %x\n",
|
||||
ptr, ptr + BUFFER_SIZE, mode);
|
||||
return KSFT_FAIL;
|
||||
}
|
||||
/* Proceed further for nonzero tags */
|
||||
if (!MT_FETCH_TAG((uintptr_t)ptr))
|
||||
return KSFT_PASS;
|
||||
@ -34,27 +37,32 @@ static int verify_mte_pointer_validity(char *ptr, int mode)
|
||||
/* Check the validity outside the range */
|
||||
ptr[BUFFER_SIZE] = '2';
|
||||
mte_wait_after_trig();
|
||||
if (!cur_mte_cxt.fault_valid)
|
||||
if (!cur_mte_cxt.fault_valid) {
|
||||
ksft_print_msg("No valid fault recorded for %p in mode %x\n",
|
||||
ptr, mode);
|
||||
return KSFT_FAIL;
|
||||
else
|
||||
} else {
|
||||
return KSFT_PASS;
|
||||
}
|
||||
}
|
||||
|
||||
static int check_single_included_tags(int mem_type, int mode)
|
||||
{
|
||||
char *ptr;
|
||||
int tag, run, result = KSFT_PASS;
|
||||
int tag, run, ret, result = KSFT_PASS;
|
||||
|
||||
ptr = (char *)mte_allocate_memory(BUFFER_SIZE + MT_GRANULE_SIZE, mem_type, 0, false);
|
||||
ptr = mte_allocate_memory(BUFFER_SIZE + MT_GRANULE_SIZE, mem_type, 0, false);
|
||||
if (check_allocated_memory(ptr, BUFFER_SIZE + MT_GRANULE_SIZE,
|
||||
mem_type, false) != KSFT_PASS)
|
||||
return KSFT_FAIL;
|
||||
|
||||
for (tag = 0; (tag < MT_TAG_COUNT) && (result == KSFT_PASS); tag++) {
|
||||
mte_switch_mode(mode, MT_INCLUDE_VALID_TAG(tag));
|
||||
ret = mte_switch_mode(mode, MT_INCLUDE_VALID_TAG(tag));
|
||||
if (ret != 0)
|
||||
result = KSFT_FAIL;
|
||||
/* Try to catch a excluded tag by a number of tries. */
|
||||
for (run = 0; (run < RUNS) && (result == KSFT_PASS); run++) {
|
||||
ptr = (char *)mte_insert_tags(ptr, BUFFER_SIZE);
|
||||
ptr = mte_insert_tags(ptr, BUFFER_SIZE);
|
||||
/* Check tag value */
|
||||
if (MT_FETCH_TAG((uintptr_t)ptr) == tag) {
|
||||
ksft_print_msg("FAIL: wrong tag = 0x%x with include mask=0x%x\n",
|
||||
@ -66,7 +74,7 @@ static int check_single_included_tags(int mem_type, int mode)
|
||||
result = verify_mte_pointer_validity(ptr, mode);
|
||||
}
|
||||
}
|
||||
mte_free_memory_tag_range((void *)ptr, BUFFER_SIZE, mem_type, 0, MT_GRANULE_SIZE);
|
||||
mte_free_memory_tag_range(ptr, BUFFER_SIZE, mem_type, 0, MT_GRANULE_SIZE);
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -76,7 +84,7 @@ static int check_multiple_included_tags(int mem_type, int mode)
|
||||
int tag, run, result = KSFT_PASS;
|
||||
unsigned long excl_mask = 0;
|
||||
|
||||
ptr = (char *)mte_allocate_memory(BUFFER_SIZE + MT_GRANULE_SIZE, mem_type, 0, false);
|
||||
ptr = mte_allocate_memory(BUFFER_SIZE + MT_GRANULE_SIZE, mem_type, 0, false);
|
||||
if (check_allocated_memory(ptr, BUFFER_SIZE + MT_GRANULE_SIZE,
|
||||
mem_type, false) != KSFT_PASS)
|
||||
return KSFT_FAIL;
|
||||
@ -86,7 +94,7 @@ static int check_multiple_included_tags(int mem_type, int mode)
|
||||
mte_switch_mode(mode, MT_INCLUDE_VALID_TAGS(excl_mask));
|
||||
/* Try to catch a excluded tag by a number of tries. */
|
||||
for (run = 0; (run < RUNS) && (result == KSFT_PASS); run++) {
|
||||
ptr = (char *)mte_insert_tags(ptr, BUFFER_SIZE);
|
||||
ptr = mte_insert_tags(ptr, BUFFER_SIZE);
|
||||
/* Check tag value */
|
||||
if (MT_FETCH_TAG((uintptr_t)ptr) < tag) {
|
||||
ksft_print_msg("FAIL: wrong tag = 0x%x with include mask=0x%x\n",
|
||||
@ -98,21 +106,23 @@ static int check_multiple_included_tags(int mem_type, int mode)
|
||||
result = verify_mte_pointer_validity(ptr, mode);
|
||||
}
|
||||
}
|
||||
mte_free_memory_tag_range((void *)ptr, BUFFER_SIZE, mem_type, 0, MT_GRANULE_SIZE);
|
||||
mte_free_memory_tag_range(ptr, BUFFER_SIZE, mem_type, 0, MT_GRANULE_SIZE);
|
||||
return result;
|
||||
}
|
||||
|
||||
static int check_all_included_tags(int mem_type, int mode)
|
||||
{
|
||||
char *ptr;
|
||||
int run, result = KSFT_PASS;
|
||||
int run, ret, result = KSFT_PASS;
|
||||
|
||||
ptr = (char *)mte_allocate_memory(BUFFER_SIZE + MT_GRANULE_SIZE, mem_type, 0, false);
|
||||
ptr = mte_allocate_memory(BUFFER_SIZE + MT_GRANULE_SIZE, mem_type, 0, false);
|
||||
if (check_allocated_memory(ptr, BUFFER_SIZE + MT_GRANULE_SIZE,
|
||||
mem_type, false) != KSFT_PASS)
|
||||
return KSFT_FAIL;
|
||||
|
||||
mte_switch_mode(mode, MT_INCLUDE_TAG_MASK);
|
||||
ret = mte_switch_mode(mode, MT_INCLUDE_TAG_MASK);
|
||||
if (ret != 0)
|
||||
return KSFT_FAIL;
|
||||
/* Try to catch a excluded tag by a number of tries. */
|
||||
for (run = 0; (run < RUNS) && (result == KSFT_PASS); run++) {
|
||||
ptr = (char *)mte_insert_tags(ptr, BUFFER_SIZE);
|
||||
@ -122,20 +132,22 @@ static int check_all_included_tags(int mem_type, int mode)
|
||||
*/
|
||||
result = verify_mte_pointer_validity(ptr, mode);
|
||||
}
|
||||
mte_free_memory_tag_range((void *)ptr, BUFFER_SIZE, mem_type, 0, MT_GRANULE_SIZE);
|
||||
mte_free_memory_tag_range(ptr, BUFFER_SIZE, mem_type, 0, MT_GRANULE_SIZE);
|
||||
return result;
|
||||
}
|
||||
|
||||
static int check_none_included_tags(int mem_type, int mode)
|
||||
{
|
||||
char *ptr;
|
||||
int run;
|
||||
int run, ret;
|
||||
|
||||
ptr = (char *)mte_allocate_memory(BUFFER_SIZE, mem_type, 0, false);
|
||||
ptr = mte_allocate_memory(BUFFER_SIZE, mem_type, 0, false);
|
||||
if (check_allocated_memory(ptr, BUFFER_SIZE, mem_type, false) != KSFT_PASS)
|
||||
return KSFT_FAIL;
|
||||
|
||||
mte_switch_mode(mode, MT_EXCLUDE_TAG_MASK);
|
||||
ret = mte_switch_mode(mode, MT_EXCLUDE_TAG_MASK);
|
||||
if (ret != 0)
|
||||
return KSFT_FAIL;
|
||||
/* Try to catch a excluded tag by a number of tries. */
|
||||
for (run = 0; run < RUNS; run++) {
|
||||
ptr = (char *)mte_insert_tags(ptr, BUFFER_SIZE);
|
||||
@ -147,12 +159,12 @@ static int check_none_included_tags(int mem_type, int mode)
|
||||
}
|
||||
mte_initialize_current_context(mode, (uintptr_t)ptr, BUFFER_SIZE);
|
||||
/* Check the write validity of the untagged pointer */
|
||||
memset((void *)ptr, '1', BUFFER_SIZE);
|
||||
memset(ptr, '1', BUFFER_SIZE);
|
||||
mte_wait_after_trig();
|
||||
if (cur_mte_cxt.fault_valid)
|
||||
break;
|
||||
}
|
||||
mte_free_memory((void *)ptr, BUFFER_SIZE, mem_type, false);
|
||||
mte_free_memory(ptr, BUFFER_SIZE, mem_type, false);
|
||||
if (cur_mte_cxt.fault_valid)
|
||||
return KSFT_FAIL;
|
||||
else
|
||||
|
@ -37,6 +37,10 @@ void mte_default_handler(int signum, siginfo_t *si, void *uc)
|
||||
if (si->si_code == SEGV_MTEAERR) {
|
||||
if (cur_mte_cxt.trig_si_code == si->si_code)
|
||||
cur_mte_cxt.fault_valid = true;
|
||||
else
|
||||
ksft_print_msg("Got unexpected SEGV_MTEAERR at pc=$lx, fault addr=%lx\n",
|
||||
((ucontext_t *)uc)->uc_mcontext.pc,
|
||||
addr);
|
||||
return;
|
||||
}
|
||||
/* Compare the context for precise error */
|
||||
@ -124,13 +128,16 @@ static void *__mte_allocate_memory_range(size_t size, int mem_type, int mapping,
|
||||
int prot_flag, map_flag;
|
||||
size_t entire_size = size + range_before + range_after;
|
||||
|
||||
if (mem_type != USE_MALLOC && mem_type != USE_MMAP &&
|
||||
mem_type != USE_MPROTECT) {
|
||||
switch (mem_type) {
|
||||
case USE_MALLOC:
|
||||
return malloc(entire_size) + range_before;
|
||||
case USE_MMAP:
|
||||
case USE_MPROTECT:
|
||||
break;
|
||||
default:
|
||||
ksft_print_msg("FAIL: Invalid allocate request\n");
|
||||
return NULL;
|
||||
}
|
||||
if (mem_type == USE_MALLOC)
|
||||
return malloc(entire_size) + range_before;
|
||||
|
||||
prot_flag = PROT_READ | PROT_WRITE;
|
||||
if (mem_type == USE_MMAP)
|
||||
@ -269,18 +276,33 @@ int mte_switch_mode(int mte_option, unsigned long incl_mask)
|
||||
{
|
||||
unsigned long en = 0;
|
||||
|
||||
if (!(mte_option == MTE_SYNC_ERR || mte_option == MTE_ASYNC_ERR ||
|
||||
mte_option == MTE_NONE_ERR || incl_mask <= MTE_ALLOW_NON_ZERO_TAG)) {
|
||||
ksft_print_msg("FAIL: Invalid mte config option\n");
|
||||
switch (mte_option) {
|
||||
case MTE_NONE_ERR:
|
||||
case MTE_SYNC_ERR:
|
||||
case MTE_ASYNC_ERR:
|
||||
break;
|
||||
default:
|
||||
ksft_print_msg("FAIL: Invalid MTE option %x\n", mte_option);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (incl_mask & ~MT_INCLUDE_TAG_MASK) {
|
||||
ksft_print_msg("FAIL: Invalid incl_mask %lx\n", incl_mask);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
en = PR_TAGGED_ADDR_ENABLE;
|
||||
if (mte_option == MTE_SYNC_ERR)
|
||||
switch (mte_option) {
|
||||
case MTE_SYNC_ERR:
|
||||
en |= PR_MTE_TCF_SYNC;
|
||||
else if (mte_option == MTE_ASYNC_ERR)
|
||||
break;
|
||||
case MTE_ASYNC_ERR:
|
||||
en |= PR_MTE_TCF_ASYNC;
|
||||
else if (mte_option == MTE_NONE_ERR)
|
||||
break;
|
||||
case MTE_NONE_ERR:
|
||||
en |= PR_MTE_TCF_NONE;
|
||||
break;
|
||||
}
|
||||
|
||||
en |= (incl_mask << PR_MTE_TAG_SHIFT);
|
||||
/* Enable address tagging ABI, mte error reporting mode and tag inclusion mask. */
|
||||
|
@ -75,10 +75,21 @@ unsigned int mte_get_pstate_tco(void);
|
||||
/* Test framework static inline functions/macros */
|
||||
static inline void evaluate_test(int err, const char *msg)
|
||||
{
|
||||
if (err == KSFT_PASS)
|
||||
switch (err) {
|
||||
case KSFT_PASS:
|
||||
ksft_test_result_pass(msg);
|
||||
else if (err == KSFT_FAIL)
|
||||
break;
|
||||
case KSFT_FAIL:
|
||||
ksft_test_result_fail(msg);
|
||||
break;
|
||||
case KSFT_SKIP:
|
||||
ksft_test_result_skip(msg);
|
||||
break;
|
||||
default:
|
||||
ksft_test_result_error("Unknown return code %d from %s",
|
||||
err, msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static inline int check_allocated_memory(void *ptr, size_t size,
|
||||
|
@ -1,5 +1,8 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
mangle_*
|
||||
fake_sigreturn_*
|
||||
sme_*
|
||||
ssve_*
|
||||
sve_*
|
||||
za_*
|
||||
!*.[ch]
|
||||
|
@ -34,11 +34,15 @@
|
||||
enum {
|
||||
FSSBS_BIT,
|
||||
FSVE_BIT,
|
||||
FSME_BIT,
|
||||
FSME_FA64_BIT,
|
||||
FMAX_END
|
||||
};
|
||||
|
||||
#define FEAT_SSBS (1UL << FSSBS_BIT)
|
||||
#define FEAT_SVE (1UL << FSVE_BIT)
|
||||
#define FEAT_SME (1UL << FSME_BIT)
|
||||
#define FEAT_SME_FA64 (1UL << FSME_FA64_BIT)
|
||||
|
||||
/*
|
||||
* A descriptor used to describe and configure a test case.
|
||||
|
@ -27,6 +27,8 @@ static int sig_copyctx = SIGTRAP;
|
||||
static char const *const feats_names[FMAX_END] = {
|
||||
" SSBS ",
|
||||
" SVE ",
|
||||
" SME ",
|
||||
" FA64 ",
|
||||
};
|
||||
|
||||
#define MAX_FEATS_SZ 128
|
||||
@ -268,6 +270,10 @@ int test_init(struct tdescr *td)
|
||||
td->feats_supported |= FEAT_SSBS;
|
||||
if (getauxval(AT_HWCAP) & HWCAP_SVE)
|
||||
td->feats_supported |= FEAT_SVE;
|
||||
if (getauxval(AT_HWCAP2) & HWCAP2_SME)
|
||||
td->feats_supported |= FEAT_SME;
|
||||
if (getauxval(AT_HWCAP2) & HWCAP2_SME_FA64)
|
||||
td->feats_supported |= FEAT_SME_FA64;
|
||||
if (feats_ok(td)) {
|
||||
if (td->feats_required & td->feats_supported)
|
||||
fprintf(stderr,
|
||||
|
@ -0,0 +1,92 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2021 ARM Limited
|
||||
*
|
||||
* Attempt to change the streaming SVE vector length in a signal
|
||||
* handler, 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 sme_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_SME_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_ssve_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 SME ZA 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, "Register data present, aborting\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_SSVE_CHANGE",
|
||||
.descr = "Attempt to change Streaming SVE VL",
|
||||
.feats_required = FEAT_SME,
|
||||
.sig_ok = SIGSEGV,
|
||||
.timeout = 3,
|
||||
.init = sme_get_vls,
|
||||
.run = fake_sigreturn_ssve_change_vl,
|
||||
};
|
@ -0,0 +1,38 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2021 ARM Limited
|
||||
*
|
||||
* Verify that using a streaming mode instruction without enabling it
|
||||
* generates a SIGILL.
|
||||
*/
|
||||
|
||||
#include <signal.h>
|
||||
#include <ucontext.h>
|
||||
#include <sys/prctl.h>
|
||||
|
||||
#include "test_signals_utils.h"
|
||||
#include "testcases.h"
|
||||
|
||||
int sme_trap_no_sm_trigger(struct tdescr *td)
|
||||
{
|
||||
/* SMSTART ZA ; ADDHA ZA0.S, P0/M, P0/M, Z0.S */
|
||||
asm volatile(".inst 0xd503457f ; .inst 0xc0900000");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sme_trap_no_sm_run(struct tdescr *td, siginfo_t *si, ucontext_t *uc)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct tdescr tde = {
|
||||
.name = "SME trap without SM",
|
||||
.descr = "Check that we get a SIGILL if we use streaming mode without enabling it",
|
||||
.timeout = 3,
|
||||
.feats_required = FEAT_SME, /* We need a SMSTART ZA */
|
||||
.sanity_disabled = true,
|
||||
.trigger = sme_trap_no_sm_trigger,
|
||||
.run = sme_trap_no_sm_run,
|
||||
.sig_ok = SIGILL,
|
||||
};
|
@ -0,0 +1,45 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2021 ARM Limited
|
||||
*
|
||||
* Verify that using an instruction not supported in streaming mode
|
||||
* traps when in streaming mode.
|
||||
*/
|
||||
|
||||
#include <signal.h>
|
||||
#include <ucontext.h>
|
||||
#include <sys/prctl.h>
|
||||
|
||||
#include "test_signals_utils.h"
|
||||
#include "testcases.h"
|
||||
|
||||
int sme_trap_non_streaming_trigger(struct tdescr *td)
|
||||
{
|
||||
/*
|
||||
* The framework will handle SIGILL so we need to exit SM to
|
||||
* stop any other code triggering a further SIGILL down the
|
||||
* line from using a streaming-illegal instruction.
|
||||
*/
|
||||
asm volatile(".inst 0xd503437f; /* SMSTART ZA */ \
|
||||
cnt v0.16b, v0.16b; \
|
||||
.inst 0xd503447f /* SMSTOP ZA */");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sme_trap_non_streaming_run(struct tdescr *td, siginfo_t *si, ucontext_t *uc)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct tdescr tde = {
|
||||
.name = "SME SM trap unsupported instruction",
|
||||
.descr = "Check that we get a SIGILL if we use an unsupported instruction in streaming mode",
|
||||
.feats_required = FEAT_SME,
|
||||
.feats_incompatible = FEAT_SME_FA64,
|
||||
.timeout = 3,
|
||||
.sanity_disabled = true,
|
||||
.trigger = sme_trap_non_streaming_trigger,
|
||||
.run = sme_trap_non_streaming_run,
|
||||
.sig_ok = SIGILL,
|
||||
};
|
36
tools/testing/selftests/arm64/signal/testcases/sme_trap_za.c
Normal file
36
tools/testing/selftests/arm64/signal/testcases/sme_trap_za.c
Normal file
@ -0,0 +1,36 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2021 ARM Limited
|
||||
*
|
||||
* Verify that accessing ZA without enabling it generates a SIGILL.
|
||||
*/
|
||||
|
||||
#include <signal.h>
|
||||
#include <ucontext.h>
|
||||
#include <sys/prctl.h>
|
||||
|
||||
#include "test_signals_utils.h"
|
||||
#include "testcases.h"
|
||||
|
||||
int sme_trap_za_trigger(struct tdescr *td)
|
||||
{
|
||||
/* ZERO ZA */
|
||||
asm volatile(".inst 0xc00800ff");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sme_trap_za_run(struct tdescr *td, siginfo_t *si, ucontext_t *uc)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct tdescr tde = {
|
||||
.name = "SME ZA trap",
|
||||
.descr = "Check that we get a SIGILL if we access ZA without enabling",
|
||||
.timeout = 3,
|
||||
.sanity_disabled = true,
|
||||
.trigger = sme_trap_za_trigger,
|
||||
.run = sme_trap_za_run,
|
||||
.sig_ok = SIGILL,
|
||||
};
|
68
tools/testing/selftests/arm64/signal/testcases/sme_vl.c
Normal file
68
tools/testing/selftests/arm64/signal/testcases/sme_vl.c
Normal file
@ -0,0 +1,68 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2021 ARM Limited
|
||||
*
|
||||
* Check that the SME 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_sme_vl(struct tdescr *td)
|
||||
{
|
||||
int ret = prctl(PR_SME_GET_VL);
|
||||
if (ret == -1)
|
||||
return false;
|
||||
|
||||
vl = ret;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int sme_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 za_context *za;
|
||||
|
||||
/* Get a signal context which should have a ZA frame in it */
|
||||
if (!get_current_context(td, &sf.uc))
|
||||
return 1;
|
||||
|
||||
resv_sz = GET_SF_RESV_SIZE(sf);
|
||||
head = get_header(head, ZA_MAGIC, resv_sz, &offset);
|
||||
if (!head) {
|
||||
fprintf(stderr, "No ZA context\n");
|
||||
return 1;
|
||||
}
|
||||
za = (struct za_context *)head;
|
||||
|
||||
if (za->vl != vl) {
|
||||
fprintf(stderr, "ZA sigframe VL %u, expected %u\n",
|
||||
za->vl, vl);
|
||||
return 1;
|
||||
} else {
|
||||
fprintf(stderr, "got expected VL %u\n", vl);
|
||||
}
|
||||
|
||||
td->pass = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct tdescr tde = {
|
||||
.name = "SME VL",
|
||||
.descr = "Check that we get the right SME VL reported",
|
||||
.feats_required = FEAT_SME,
|
||||
.timeout = 3,
|
||||
.init = get_sme_vl,
|
||||
.run = sme_vl,
|
||||
};
|
135
tools/testing/selftests/arm64/signal/testcases/ssve_regs.c
Normal file
135
tools/testing/selftests/arm64/signal/testcases/ssve_regs.c
Normal file
@ -0,0 +1,135 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2021 ARM Limited
|
||||
*
|
||||
* Verify that the streaming 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 sme_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_SME_SET_VL, vq * 16);
|
||||
if (vl == -1)
|
||||
return false;
|
||||
|
||||
vl &= PR_SME_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_ssve_regs(void)
|
||||
{
|
||||
/* smstart sm; real data is TODO */
|
||||
asm volatile(".inst 0xd503437f" : : : );
|
||||
}
|
||||
|
||||
static int do_one_sme_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 *ssve;
|
||||
int ret;
|
||||
|
||||
fprintf(stderr, "Testing VL %d\n", vl);
|
||||
|
||||
ret = prctl(PR_SME_SET_VL, vl);
|
||||
if (ret != vl) {
|
||||
fprintf(stderr, "Failed to set VL, got %d\n", ret);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get a signal context which should have a SVE frame and registers
|
||||
* in it.
|
||||
*/
|
||||
setup_ssve_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;
|
||||
}
|
||||
|
||||
ssve = (struct sve_context *)head;
|
||||
if (ssve->vl != vl) {
|
||||
fprintf(stderr, "Got VL %d, expected %d\n", ssve->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, ssve->vl);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sme_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) {
|
||||
printf("Skipping VL %u due to stack size\n", vls[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (do_one_sme_vl(td, si, uc, vls[i]))
|
||||
return 1;
|
||||
}
|
||||
|
||||
td->pass = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct tdescr tde = {
|
||||
.name = "Streaming SVE registers",
|
||||
.descr = "Check that we get the right Streaming SVE registers reported",
|
||||
/*
|
||||
* We shouldn't require FA64 but things like memset() used in the
|
||||
* helpers might use unsupported instructions so for now disable
|
||||
* the test unless we've got the full instruction set.
|
||||
*/
|
||||
.feats_required = FEAT_SME | FEAT_SME_FA64,
|
||||
.timeout = 3,
|
||||
.init = sme_get_vls,
|
||||
.run = sme_regs,
|
||||
};
|
@ -75,6 +75,31 @@ bool validate_sve_context(struct sve_context *sve, char **err)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool validate_za_context(struct za_context *za, char **err)
|
||||
{
|
||||
/* Size will be rounded up to a multiple of 16 bytes */
|
||||
size_t regs_size
|
||||
= ((ZA_SIG_CONTEXT_SIZE(sve_vq_from_vl(za->vl)) + 15) / 16) * 16;
|
||||
|
||||
if (!za || !err)
|
||||
return false;
|
||||
|
||||
/* Either a bare za_context or a za_context followed by regs data */
|
||||
if ((za->head.size != sizeof(struct za_context)) &&
|
||||
(za->head.size != regs_size)) {
|
||||
*err = "bad size for ZA context";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!sve_vl_valid(za->vl)) {
|
||||
*err = "SME VL in ZA context invalid";
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool validate_reserved(ucontext_t *uc, size_t resv_sz, char **err)
|
||||
{
|
||||
bool terminated = false;
|
||||
@ -82,6 +107,7 @@ bool validate_reserved(ucontext_t *uc, size_t resv_sz, char **err)
|
||||
int flags = 0;
|
||||
struct extra_context *extra = NULL;
|
||||
struct sve_context *sve = NULL;
|
||||
struct za_context *za = NULL;
|
||||
struct _aarch64_ctx *head =
|
||||
(struct _aarch64_ctx *)uc->uc_mcontext.__reserved;
|
||||
|
||||
@ -120,6 +146,13 @@ bool validate_reserved(ucontext_t *uc, size_t resv_sz, char **err)
|
||||
sve = (struct sve_context *)head;
|
||||
flags |= SVE_CTX;
|
||||
break;
|
||||
case ZA_MAGIC:
|
||||
if (flags & ZA_CTX)
|
||||
*err = "Multiple ZA_MAGIC";
|
||||
/* Size is validated in validate_za_context() */
|
||||
za = (struct za_context *)head;
|
||||
flags |= ZA_CTX;
|
||||
break;
|
||||
case EXTRA_MAGIC:
|
||||
if (flags & EXTRA_CTX)
|
||||
*err = "Multiple EXTRA_MAGIC";
|
||||
@ -165,6 +198,9 @@ bool validate_reserved(ucontext_t *uc, size_t resv_sz, char **err)
|
||||
if (flags & SVE_CTX)
|
||||
if (!validate_sve_context(sve, err))
|
||||
return false;
|
||||
if (flags & ZA_CTX)
|
||||
if (!validate_za_context(za, err))
|
||||
return false;
|
||||
|
||||
head = GET_RESV_NEXT_HEAD(head);
|
||||
}
|
||||
|
@ -16,7 +16,8 @@
|
||||
|
||||
#define FPSIMD_CTX (1 << 0)
|
||||
#define SVE_CTX (1 << 1)
|
||||
#define EXTRA_CTX (1 << 2)
|
||||
#define ZA_CTX (1 << 2)
|
||||
#define EXTRA_CTX (1 << 3)
|
||||
|
||||
#define KSFT_BAD_MAGIC 0xdeadbeef
|
||||
|
||||
|
128
tools/testing/selftests/arm64/signal/testcases/za_regs.c
Normal file
128
tools/testing/selftests/arm64/signal/testcases/za_regs.c
Normal file
@ -0,0 +1,128 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2021 ARM Limited
|
||||
*
|
||||
* Verify that the ZA 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 sme_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_SME_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_za_regs(void)
|
||||
{
|
||||
/* smstart za; real data is TODO */
|
||||
asm volatile(".inst 0xd503457f" : : : );
|
||||
}
|
||||
|
||||
static int do_one_sme_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 za_context *za;
|
||||
|
||||
fprintf(stderr, "Testing VL %d\n", vl);
|
||||
|
||||
if (prctl(PR_SME_SET_VL, vl) != vl) {
|
||||
fprintf(stderr, "Failed to set VL\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get a signal context which should have a SVE frame and registers
|
||||
* in it.
|
||||
*/
|
||||
setup_za_regs();
|
||||
if (!get_current_context(td, &sf.uc))
|
||||
return 1;
|
||||
|
||||
resv_sz = GET_SF_RESV_SIZE(sf);
|
||||
head = get_header(head, ZA_MAGIC, resv_sz, &offset);
|
||||
if (!head) {
|
||||
fprintf(stderr, "No ZA context\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
za = (struct za_context *)head;
|
||||
if (za->vl != vl) {
|
||||
fprintf(stderr, "Got VL %d, expected %d\n", za->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, za->vl);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sme_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] > 32) {
|
||||
printf("Skipping VL %u due to stack size\n", vls[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (do_one_sme_vl(td, si, uc, vls[i]))
|
||||
return 1;
|
||||
}
|
||||
|
||||
td->pass = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct tdescr tde = {
|
||||
.name = "ZA register",
|
||||
.descr = "Check that we get the right ZA registers reported",
|
||||
.feats_required = FEAT_SME,
|
||||
.timeout = 3,
|
||||
.init = sme_get_vls,
|
||||
.run = sme_regs,
|
||||
};
|
Loading…
Reference in New Issue
Block a user