Commit d6fc5db0 authored by Catalin Marinas's avatar Catalin Marinas

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
  ...
parents e003d533 9f93c2e0
......@@ -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
......
# 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]);
......
/* 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
// 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
......
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
# 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 $@
$(OUTPUT)/fpsimd-test: fpsimd-test.S $(OUTPUT)/asm-utils.o
$(CC) -nostdlib $^ -o $@
fpsimd-test: fpsimd-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 $@
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)/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
// 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;
}
// 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
// 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
#!/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 @@ static void ptrace_set_sve_get_sve_data(pid_t child,
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 @@ static void ptrace_set_sve_get_fpsimd_data(pid_t child,
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));
......
// 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
// 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;
}
// 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;
}
#!/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
// 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
......@@ -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) {
......
// 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,
......
# 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,
......
// 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,
};
// 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,
};
// 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,
};
// 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,
};
// 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,
};
// 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
......
// 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,
};
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment