1637 lines
38 KiB
C
1637 lines
38 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (C) 2023 ARM Limited.
|
|
* Original author: Mark Brown <broonie@kernel.org>
|
|
*/
|
|
|
|
#define _GNU_SOURCE
|
|
|
|
#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 <linux/kernel.h>
|
|
|
|
#include <asm/sigcontext.h>
|
|
#include <asm/sve_context.h>
|
|
#include <asm/ptrace.h>
|
|
|
|
#include "../../kselftest.h"
|
|
|
|
#include "fp-ptrace.h"
|
|
|
|
#include <linux/bits.h>
|
|
|
|
#define FPMR_LSCALE2_MASK GENMASK(37, 32)
|
|
#define FPMR_NSCALE_MASK GENMASK(31, 24)
|
|
#define FPMR_LSCALE_MASK GENMASK(22, 16)
|
|
#define FPMR_OSC_MASK GENMASK(15, 15)
|
|
#define FPMR_OSM_MASK GENMASK(14, 14)
|
|
|
|
/* <linux/elf.h> and <sys/auxv.h> don't like each other, so: */
|
|
#ifndef NT_ARM_SVE
|
|
#define NT_ARM_SVE 0x405
|
|
#endif
|
|
|
|
#ifndef NT_ARM_SSVE
|
|
#define NT_ARM_SSVE 0x40b
|
|
#endif
|
|
|
|
#ifndef NT_ARM_ZA
|
|
#define NT_ARM_ZA 0x40c
|
|
#endif
|
|
|
|
#ifndef NT_ARM_ZT
|
|
#define NT_ARM_ZT 0x40d
|
|
#endif
|
|
|
|
#ifndef NT_ARM_FPMR
|
|
#define NT_ARM_FPMR 0x40e
|
|
#endif
|
|
|
|
#define ARCH_VQ_MAX 256
|
|
|
|
/* VL 128..2048 in powers of 2 */
|
|
#define MAX_NUM_VLS 5
|
|
|
|
/*
|
|
* FPMR bits we can set without doing feature checks to see if values
|
|
* are valid.
|
|
*/
|
|
#define FPMR_SAFE_BITS (FPMR_LSCALE2_MASK | FPMR_NSCALE_MASK | \
|
|
FPMR_LSCALE_MASK | FPMR_OSC_MASK | FPMR_OSM_MASK)
|
|
|
|
#define NUM_FPR 32
|
|
__uint128_t v_in[NUM_FPR];
|
|
__uint128_t v_expected[NUM_FPR];
|
|
__uint128_t v_out[NUM_FPR];
|
|
|
|
char z_in[__SVE_ZREGS_SIZE(ARCH_VQ_MAX)];
|
|
char z_expected[__SVE_ZREGS_SIZE(ARCH_VQ_MAX)];
|
|
char z_out[__SVE_ZREGS_SIZE(ARCH_VQ_MAX)];
|
|
|
|
char p_in[__SVE_PREGS_SIZE(ARCH_VQ_MAX)];
|
|
char p_expected[__SVE_PREGS_SIZE(ARCH_VQ_MAX)];
|
|
char p_out[__SVE_PREGS_SIZE(ARCH_VQ_MAX)];
|
|
|
|
char ffr_in[__SVE_PREG_SIZE(ARCH_VQ_MAX)];
|
|
char ffr_expected[__SVE_PREG_SIZE(ARCH_VQ_MAX)];
|
|
char ffr_out[__SVE_PREG_SIZE(ARCH_VQ_MAX)];
|
|
|
|
char za_in[ZA_SIG_REGS_SIZE(ARCH_VQ_MAX)];
|
|
char za_expected[ZA_SIG_REGS_SIZE(ARCH_VQ_MAX)];
|
|
char za_out[ZA_SIG_REGS_SIZE(ARCH_VQ_MAX)];
|
|
|
|
char zt_in[ZT_SIG_REG_BYTES];
|
|
char zt_expected[ZT_SIG_REG_BYTES];
|
|
char zt_out[ZT_SIG_REG_BYTES];
|
|
|
|
uint64_t fpmr_in, fpmr_expected, fpmr_out;
|
|
|
|
uint64_t sve_vl_out;
|
|
uint64_t sme_vl_out;
|
|
uint64_t svcr_in, svcr_expected, svcr_out;
|
|
|
|
void load_and_save(int flags);
|
|
|
|
static bool got_alarm;
|
|
|
|
static void handle_alarm(int sig, siginfo_t *info, void *context)
|
|
{
|
|
got_alarm = true;
|
|
}
|
|
|
|
#ifdef CONFIG_CPU_BIG_ENDIAN
|
|
static __uint128_t arm64_cpu_to_le128(__uint128_t x)
|
|
{
|
|
u64 a = swab64(x);
|
|
u64 b = swab64(x >> 64);
|
|
|
|
return ((__uint128_t)a << 64) | b;
|
|
}
|
|
#else
|
|
static __uint128_t arm64_cpu_to_le128(__uint128_t x)
|
|
{
|
|
return x;
|
|
}
|
|
#endif
|
|
|
|
#define arm64_le128_to_cpu(x) arm64_cpu_to_le128(x)
|
|
|
|
static bool sve_supported(void)
|
|
{
|
|
return getauxval(AT_HWCAP) & HWCAP_SVE;
|
|
}
|
|
|
|
static bool sme_supported(void)
|
|
{
|
|
return getauxval(AT_HWCAP2) & HWCAP2_SME;
|
|
}
|
|
|
|
static bool sme2_supported(void)
|
|
{
|
|
return getauxval(AT_HWCAP2) & HWCAP2_SME2;
|
|
}
|
|
|
|
static bool fa64_supported(void)
|
|
{
|
|
return getauxval(AT_HWCAP2) & HWCAP2_SME_FA64;
|
|
}
|
|
|
|
static bool fpmr_supported(void)
|
|
{
|
|
return getauxval(AT_HWCAP2) & HWCAP2_FPMR;
|
|
}
|
|
|
|
static bool compare_buffer(const char *name, void *out,
|
|
void *expected, size_t size)
|
|
{
|
|
void *tmp;
|
|
|
|
if (memcmp(out, expected, size) == 0)
|
|
return true;
|
|
|
|
ksft_print_msg("Mismatch in %s\n", name);
|
|
|
|
/* Did we just get zeros back? */
|
|
tmp = malloc(size);
|
|
if (!tmp) {
|
|
ksft_print_msg("OOM allocating %lu bytes for %s\n",
|
|
size, name);
|
|
ksft_exit_fail();
|
|
}
|
|
memset(tmp, 0, size);
|
|
|
|
if (memcmp(out, tmp, size) == 0)
|
|
ksft_print_msg("%s is zero\n", name);
|
|
|
|
free(tmp);
|
|
|
|
return false;
|
|
}
|
|
|
|
struct test_config {
|
|
int sve_vl_in;
|
|
int sve_vl_expected;
|
|
int sme_vl_in;
|
|
int sme_vl_expected;
|
|
int svcr_in;
|
|
int svcr_expected;
|
|
};
|
|
|
|
struct test_definition {
|
|
const char *name;
|
|
bool sve_vl_change;
|
|
bool (*supported)(struct test_config *config);
|
|
void (*set_expected_values)(struct test_config *config);
|
|
void (*modify_values)(pid_t child, struct test_config *test_config);
|
|
};
|
|
|
|
static int vl_in(struct test_config *config)
|
|
{
|
|
int vl;
|
|
|
|
if (config->svcr_in & SVCR_SM)
|
|
vl = config->sme_vl_in;
|
|
else
|
|
vl = config->sve_vl_in;
|
|
|
|
return vl;
|
|
}
|
|
|
|
static int vl_expected(struct test_config *config)
|
|
{
|
|
int vl;
|
|
|
|
if (config->svcr_expected & SVCR_SM)
|
|
vl = config->sme_vl_expected;
|
|
else
|
|
vl = config->sve_vl_expected;
|
|
|
|
return vl;
|
|
}
|
|
|
|
static void run_child(struct test_config *config)
|
|
{
|
|
int ret, flags;
|
|
|
|
/* Let the parent attach to us */
|
|
ret = ptrace(PTRACE_TRACEME, 0, 0, 0);
|
|
if (ret < 0)
|
|
ksft_exit_fail_msg("PTRACE_TRACEME failed: %s (%d)\n",
|
|
strerror(errno), errno);
|
|
|
|
/* VL setup */
|
|
if (sve_supported()) {
|
|
ret = prctl(PR_SVE_SET_VL, config->sve_vl_in);
|
|
if (ret != config->sve_vl_in) {
|
|
ksft_print_msg("Failed to set SVE VL %d: %d\n",
|
|
config->sve_vl_in, ret);
|
|
}
|
|
}
|
|
|
|
if (sme_supported()) {
|
|
ret = prctl(PR_SME_SET_VL, config->sme_vl_in);
|
|
if (ret != config->sme_vl_in) {
|
|
ksft_print_msg("Failed to set SME VL %d: %d\n",
|
|
config->sme_vl_in, ret);
|
|
}
|
|
}
|
|
|
|
/* Load values and wait for the parent */
|
|
flags = 0;
|
|
if (sve_supported())
|
|
flags |= HAVE_SVE;
|
|
if (sme_supported())
|
|
flags |= HAVE_SME;
|
|
if (sme2_supported())
|
|
flags |= HAVE_SME2;
|
|
if (fa64_supported())
|
|
flags |= HAVE_FA64;
|
|
if (fpmr_supported())
|
|
flags |= HAVE_FPMR;
|
|
|
|
load_and_save(flags);
|
|
|
|
exit(0);
|
|
}
|
|
|
|
static void read_one_child_regs(pid_t child, char *name,
|
|
struct iovec *iov_parent,
|
|
struct iovec *iov_child)
|
|
{
|
|
int len = iov_parent->iov_len;
|
|
int ret;
|
|
|
|
ret = process_vm_readv(child, iov_parent, 1, iov_child, 1, 0);
|
|
if (ret == -1)
|
|
ksft_print_msg("%s read failed: %s (%d)\n",
|
|
name, strerror(errno), errno);
|
|
else if (ret != len)
|
|
ksft_print_msg("Short read of %s: %d\n", name, ret);
|
|
}
|
|
|
|
static void read_child_regs(pid_t child)
|
|
{
|
|
struct iovec iov_parent, iov_child;
|
|
|
|
/*
|
|
* Since the child fork()ed from us the buffer addresses are
|
|
* the same in parent and child.
|
|
*/
|
|
iov_parent.iov_base = &v_out;
|
|
iov_parent.iov_len = sizeof(v_out);
|
|
iov_child.iov_base = &v_out;
|
|
iov_child.iov_len = sizeof(v_out);
|
|
read_one_child_regs(child, "FPSIMD", &iov_parent, &iov_child);
|
|
|
|
if (sve_supported() || sme_supported()) {
|
|
iov_parent.iov_base = &sve_vl_out;
|
|
iov_parent.iov_len = sizeof(sve_vl_out);
|
|
iov_child.iov_base = &sve_vl_out;
|
|
iov_child.iov_len = sizeof(sve_vl_out);
|
|
read_one_child_regs(child, "SVE VL", &iov_parent, &iov_child);
|
|
|
|
iov_parent.iov_base = &z_out;
|
|
iov_parent.iov_len = sizeof(z_out);
|
|
iov_child.iov_base = &z_out;
|
|
iov_child.iov_len = sizeof(z_out);
|
|
read_one_child_regs(child, "Z", &iov_parent, &iov_child);
|
|
|
|
iov_parent.iov_base = &p_out;
|
|
iov_parent.iov_len = sizeof(p_out);
|
|
iov_child.iov_base = &p_out;
|
|
iov_child.iov_len = sizeof(p_out);
|
|
read_one_child_regs(child, "P", &iov_parent, &iov_child);
|
|
|
|
iov_parent.iov_base = &ffr_out;
|
|
iov_parent.iov_len = sizeof(ffr_out);
|
|
iov_child.iov_base = &ffr_out;
|
|
iov_child.iov_len = sizeof(ffr_out);
|
|
read_one_child_regs(child, "FFR", &iov_parent, &iov_child);
|
|
}
|
|
|
|
if (sme_supported()) {
|
|
iov_parent.iov_base = &sme_vl_out;
|
|
iov_parent.iov_len = sizeof(sme_vl_out);
|
|
iov_child.iov_base = &sme_vl_out;
|
|
iov_child.iov_len = sizeof(sme_vl_out);
|
|
read_one_child_regs(child, "SME VL", &iov_parent, &iov_child);
|
|
|
|
iov_parent.iov_base = &svcr_out;
|
|
iov_parent.iov_len = sizeof(svcr_out);
|
|
iov_child.iov_base = &svcr_out;
|
|
iov_child.iov_len = sizeof(svcr_out);
|
|
read_one_child_regs(child, "SVCR", &iov_parent, &iov_child);
|
|
|
|
iov_parent.iov_base = &za_out;
|
|
iov_parent.iov_len = sizeof(za_out);
|
|
iov_child.iov_base = &za_out;
|
|
iov_child.iov_len = sizeof(za_out);
|
|
read_one_child_regs(child, "ZA", &iov_parent, &iov_child);
|
|
}
|
|
|
|
if (sme2_supported()) {
|
|
iov_parent.iov_base = &zt_out;
|
|
iov_parent.iov_len = sizeof(zt_out);
|
|
iov_child.iov_base = &zt_out;
|
|
iov_child.iov_len = sizeof(zt_out);
|
|
read_one_child_regs(child, "ZT", &iov_parent, &iov_child);
|
|
}
|
|
|
|
if (fpmr_supported()) {
|
|
iov_parent.iov_base = &fpmr_out;
|
|
iov_parent.iov_len = sizeof(fpmr_out);
|
|
iov_child.iov_base = &fpmr_out;
|
|
iov_child.iov_len = sizeof(fpmr_out);
|
|
read_one_child_regs(child, "FPMR", &iov_parent, &iov_child);
|
|
}
|
|
}
|
|
|
|
static bool continue_breakpoint(pid_t child,
|
|
enum __ptrace_request restart_type)
|
|
{
|
|
struct user_pt_regs pt_regs;
|
|
struct iovec iov;
|
|
int ret;
|
|
|
|
/* Get PC */
|
|
iov.iov_base = &pt_regs;
|
|
iov.iov_len = sizeof(pt_regs);
|
|
ret = ptrace(PTRACE_GETREGSET, child, NT_PRSTATUS, &iov);
|
|
if (ret < 0) {
|
|
ksft_print_msg("Failed to get PC: %s (%d)\n",
|
|
strerror(errno), errno);
|
|
return false;
|
|
}
|
|
|
|
/* Skip over the BRK */
|
|
pt_regs.pc += 4;
|
|
ret = ptrace(PTRACE_SETREGSET, child, NT_PRSTATUS, &iov);
|
|
if (ret < 0) {
|
|
ksft_print_msg("Failed to skip BRK: %s (%d)\n",
|
|
strerror(errno), errno);
|
|
return false;
|
|
}
|
|
|
|
/* Restart */
|
|
ret = ptrace(restart_type, child, 0, 0);
|
|
if (ret < 0) {
|
|
ksft_print_msg("Failed to restart child: %s (%d)\n",
|
|
strerror(errno), errno);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool check_ptrace_values_sve(pid_t child, struct test_config *config)
|
|
{
|
|
struct user_sve_header *sve;
|
|
struct user_fpsimd_state *fpsimd;
|
|
struct iovec iov;
|
|
int ret, vq;
|
|
bool pass = true;
|
|
|
|
if (!sve_supported())
|
|
return true;
|
|
|
|
vq = __sve_vq_from_vl(config->sve_vl_in);
|
|
|
|
iov.iov_len = SVE_PT_SVE_OFFSET + SVE_PT_SVE_SIZE(vq, SVE_PT_REGS_SVE);
|
|
iov.iov_base = malloc(iov.iov_len);
|
|
if (!iov.iov_base) {
|
|
ksft_print_msg("OOM allocating %lu byte SVE buffer\n",
|
|
iov.iov_len);
|
|
return false;
|
|
}
|
|
|
|
ret = ptrace(PTRACE_GETREGSET, child, NT_ARM_SVE, &iov);
|
|
if (ret != 0) {
|
|
ksft_print_msg("Failed to read initial SVE: %s (%d)\n",
|
|
strerror(errno), errno);
|
|
pass = false;
|
|
goto out;
|
|
}
|
|
|
|
sve = iov.iov_base;
|
|
|
|
if (sve->vl != config->sve_vl_in) {
|
|
ksft_print_msg("Mismatch in initial SVE VL: %d != %d\n",
|
|
sve->vl, config->sve_vl_in);
|
|
pass = false;
|
|
}
|
|
|
|
/* If we are in streaming mode we should just read FPSIMD */
|
|
if ((config->svcr_in & SVCR_SM) && (sve->flags & SVE_PT_REGS_SVE)) {
|
|
ksft_print_msg("NT_ARM_SVE reports SVE with PSTATE.SM\n");
|
|
pass = false;
|
|
}
|
|
|
|
if (sve->size != SVE_PT_SIZE(vq, sve->flags)) {
|
|
ksft_print_msg("Mismatch in SVE header size: %d != %lu\n",
|
|
sve->size, SVE_PT_SIZE(vq, sve->flags));
|
|
pass = false;
|
|
}
|
|
|
|
/* The registers might be in completely different formats! */
|
|
if (sve->flags & SVE_PT_REGS_SVE) {
|
|
if (!compare_buffer("initial SVE Z",
|
|
iov.iov_base + SVE_PT_SVE_ZREG_OFFSET(vq, 0),
|
|
z_in, SVE_PT_SVE_ZREGS_SIZE(vq)))
|
|
pass = false;
|
|
|
|
if (!compare_buffer("initial SVE P",
|
|
iov.iov_base + SVE_PT_SVE_PREG_OFFSET(vq, 0),
|
|
p_in, SVE_PT_SVE_PREGS_SIZE(vq)))
|
|
pass = false;
|
|
|
|
if (!compare_buffer("initial SVE FFR",
|
|
iov.iov_base + SVE_PT_SVE_FFR_OFFSET(vq),
|
|
ffr_in, SVE_PT_SVE_PREG_SIZE(vq)))
|
|
pass = false;
|
|
} else {
|
|
fpsimd = iov.iov_base + SVE_PT_FPSIMD_OFFSET;
|
|
if (!compare_buffer("initial V via SVE", &fpsimd->vregs[0],
|
|
v_in, sizeof(v_in)))
|
|
pass = false;
|
|
}
|
|
|
|
out:
|
|
free(iov.iov_base);
|
|
return pass;
|
|
}
|
|
|
|
static bool check_ptrace_values_ssve(pid_t child, struct test_config *config)
|
|
{
|
|
struct user_sve_header *sve;
|
|
struct user_fpsimd_state *fpsimd;
|
|
struct iovec iov;
|
|
int ret, vq;
|
|
bool pass = true;
|
|
|
|
if (!sme_supported())
|
|
return true;
|
|
|
|
vq = __sve_vq_from_vl(config->sme_vl_in);
|
|
|
|
iov.iov_len = SVE_PT_SVE_OFFSET + SVE_PT_SVE_SIZE(vq, SVE_PT_REGS_SVE);
|
|
iov.iov_base = malloc(iov.iov_len);
|
|
if (!iov.iov_base) {
|
|
ksft_print_msg("OOM allocating %lu byte SSVE buffer\n",
|
|
iov.iov_len);
|
|
return false;
|
|
}
|
|
|
|
ret = ptrace(PTRACE_GETREGSET, child, NT_ARM_SSVE, &iov);
|
|
if (ret != 0) {
|
|
ksft_print_msg("Failed to read initial SSVE: %s (%d)\n",
|
|
strerror(errno), errno);
|
|
pass = false;
|
|
goto out;
|
|
}
|
|
|
|
sve = iov.iov_base;
|
|
|
|
if (sve->vl != config->sme_vl_in) {
|
|
ksft_print_msg("Mismatch in initial SSVE VL: %d != %d\n",
|
|
sve->vl, config->sme_vl_in);
|
|
pass = false;
|
|
}
|
|
|
|
if ((config->svcr_in & SVCR_SM) && !(sve->flags & SVE_PT_REGS_SVE)) {
|
|
ksft_print_msg("NT_ARM_SSVE reports FPSIMD with PSTATE.SM\n");
|
|
pass = false;
|
|
}
|
|
|
|
if (sve->size != SVE_PT_SIZE(vq, sve->flags)) {
|
|
ksft_print_msg("Mismatch in SSVE header size: %d != %lu\n",
|
|
sve->size, SVE_PT_SIZE(vq, sve->flags));
|
|
pass = false;
|
|
}
|
|
|
|
/* The registers might be in completely different formats! */
|
|
if (sve->flags & SVE_PT_REGS_SVE) {
|
|
if (!compare_buffer("initial SSVE Z",
|
|
iov.iov_base + SVE_PT_SVE_ZREG_OFFSET(vq, 0),
|
|
z_in, SVE_PT_SVE_ZREGS_SIZE(vq)))
|
|
pass = false;
|
|
|
|
if (!compare_buffer("initial SSVE P",
|
|
iov.iov_base + SVE_PT_SVE_PREG_OFFSET(vq, 0),
|
|
p_in, SVE_PT_SVE_PREGS_SIZE(vq)))
|
|
pass = false;
|
|
|
|
if (!compare_buffer("initial SSVE FFR",
|
|
iov.iov_base + SVE_PT_SVE_FFR_OFFSET(vq),
|
|
ffr_in, SVE_PT_SVE_PREG_SIZE(vq)))
|
|
pass = false;
|
|
} else {
|
|
fpsimd = iov.iov_base + SVE_PT_FPSIMD_OFFSET;
|
|
if (!compare_buffer("initial V via SSVE",
|
|
&fpsimd->vregs[0], v_in, sizeof(v_in)))
|
|
pass = false;
|
|
}
|
|
|
|
out:
|
|
free(iov.iov_base);
|
|
return pass;
|
|
}
|
|
|
|
static bool check_ptrace_values_za(pid_t child, struct test_config *config)
|
|
{
|
|
struct user_za_header *za;
|
|
struct iovec iov;
|
|
int ret, vq;
|
|
bool pass = true;
|
|
|
|
if (!sme_supported())
|
|
return true;
|
|
|
|
vq = __sve_vq_from_vl(config->sme_vl_in);
|
|
|
|
iov.iov_len = ZA_SIG_CONTEXT_SIZE(vq);
|
|
iov.iov_base = malloc(iov.iov_len);
|
|
if (!iov.iov_base) {
|
|
ksft_print_msg("OOM allocating %lu byte ZA buffer\n",
|
|
iov.iov_len);
|
|
return false;
|
|
}
|
|
|
|
ret = ptrace(PTRACE_GETREGSET, child, NT_ARM_ZA, &iov);
|
|
if (ret != 0) {
|
|
ksft_print_msg("Failed to read initial ZA: %s (%d)\n",
|
|
strerror(errno), errno);
|
|
pass = false;
|
|
goto out;
|
|
}
|
|
|
|
za = iov.iov_base;
|
|
|
|
if (za->vl != config->sme_vl_in) {
|
|
ksft_print_msg("Mismatch in initial SME VL: %d != %d\n",
|
|
za->vl, config->sme_vl_in);
|
|
pass = false;
|
|
}
|
|
|
|
/* If PSTATE.ZA is not set we should just read the header */
|
|
if (config->svcr_in & SVCR_ZA) {
|
|
if (za->size != ZA_PT_SIZE(vq)) {
|
|
ksft_print_msg("Unexpected ZA ptrace read size: %d != %lu\n",
|
|
za->size, ZA_PT_SIZE(vq));
|
|
pass = false;
|
|
}
|
|
|
|
if (!compare_buffer("initial ZA",
|
|
iov.iov_base + ZA_PT_ZA_OFFSET,
|
|
za_in, ZA_PT_ZA_SIZE(vq)))
|
|
pass = false;
|
|
} else {
|
|
if (za->size != sizeof(*za)) {
|
|
ksft_print_msg("Unexpected ZA ptrace read size: %d != %lu\n",
|
|
za->size, sizeof(*za));
|
|
pass = false;
|
|
}
|
|
}
|
|
|
|
out:
|
|
free(iov.iov_base);
|
|
return pass;
|
|
}
|
|
|
|
static bool check_ptrace_values_zt(pid_t child, struct test_config *config)
|
|
{
|
|
uint8_t buf[512];
|
|
struct iovec iov;
|
|
int ret;
|
|
|
|
if (!sme2_supported())
|
|
return true;
|
|
|
|
iov.iov_base = &buf;
|
|
iov.iov_len = ZT_SIG_REG_BYTES;
|
|
ret = ptrace(PTRACE_GETREGSET, child, NT_ARM_ZT, &iov);
|
|
if (ret != 0) {
|
|
ksft_print_msg("Failed to read initial ZT: %s (%d)\n",
|
|
strerror(errno), errno);
|
|
return false;
|
|
}
|
|
|
|
return compare_buffer("initial ZT", buf, zt_in, ZT_SIG_REG_BYTES);
|
|
}
|
|
|
|
static bool check_ptrace_values_fpmr(pid_t child, struct test_config *config)
|
|
{
|
|
uint64_t val;
|
|
struct iovec iov;
|
|
int ret;
|
|
|
|
if (!fpmr_supported())
|
|
return true;
|
|
|
|
iov.iov_base = &val;
|
|
iov.iov_len = sizeof(val);
|
|
ret = ptrace(PTRACE_GETREGSET, child, NT_ARM_FPMR, &iov);
|
|
if (ret != 0) {
|
|
ksft_print_msg("Failed to read initial FPMR: %s (%d)\n",
|
|
strerror(errno), errno);
|
|
return false;
|
|
}
|
|
|
|
return compare_buffer("initial FPMR", &val, &fpmr_in, sizeof(val));
|
|
}
|
|
|
|
static bool check_ptrace_values(pid_t child, struct test_config *config)
|
|
{
|
|
bool pass = true;
|
|
struct user_fpsimd_state fpsimd;
|
|
struct iovec iov;
|
|
int ret;
|
|
|
|
iov.iov_base = &fpsimd;
|
|
iov.iov_len = sizeof(fpsimd);
|
|
ret = ptrace(PTRACE_GETREGSET, child, NT_PRFPREG, &iov);
|
|
if (ret == 0) {
|
|
if (!compare_buffer("initial V", &fpsimd.vregs, v_in,
|
|
sizeof(v_in))) {
|
|
pass = false;
|
|
}
|
|
} else {
|
|
ksft_print_msg("Failed to read initial V: %s (%d)\n",
|
|
strerror(errno), errno);
|
|
pass = false;
|
|
}
|
|
|
|
if (!check_ptrace_values_sve(child, config))
|
|
pass = false;
|
|
|
|
if (!check_ptrace_values_ssve(child, config))
|
|
pass = false;
|
|
|
|
if (!check_ptrace_values_za(child, config))
|
|
pass = false;
|
|
|
|
if (!check_ptrace_values_zt(child, config))
|
|
pass = false;
|
|
|
|
if (!check_ptrace_values_fpmr(child, config))
|
|
pass = false;
|
|
|
|
return pass;
|
|
}
|
|
|
|
static bool run_parent(pid_t child, struct test_definition *test,
|
|
struct test_config *config)
|
|
{
|
|
int wait_status, ret;
|
|
pid_t pid;
|
|
bool pass;
|
|
|
|
/* Initial attach */
|
|
while (1) {
|
|
pid = waitpid(child, &wait_status, 0);
|
|
if (pid < 0) {
|
|
if (errno == EINTR)
|
|
continue;
|
|
ksft_exit_fail_msg("waitpid() failed: %s (%d)\n",
|
|
strerror(errno), errno);
|
|
}
|
|
|
|
if (pid == child)
|
|
break;
|
|
}
|
|
|
|
if (WIFEXITED(wait_status)) {
|
|
ksft_print_msg("Child exited loading values with status %d\n",
|
|
WEXITSTATUS(wait_status));
|
|
pass = false;
|
|
goto out;
|
|
}
|
|
|
|
if (WIFSIGNALED(wait_status)) {
|
|
ksft_print_msg("Child died from signal %d loading values\n",
|
|
WTERMSIG(wait_status));
|
|
pass = false;
|
|
goto out;
|
|
}
|
|
|
|
/* Read initial values via ptrace */
|
|
pass = check_ptrace_values(child, config);
|
|
|
|
/* Do whatever writes we want to do */
|
|
if (test->modify_values)
|
|
test->modify_values(child, config);
|
|
|
|
if (!continue_breakpoint(child, PTRACE_CONT))
|
|
goto cleanup;
|
|
|
|
while (1) {
|
|
pid = waitpid(child, &wait_status, 0);
|
|
if (pid < 0) {
|
|
if (errno == EINTR)
|
|
continue;
|
|
ksft_exit_fail_msg("waitpid() failed: %s (%d)\n",
|
|
strerror(errno), errno);
|
|
}
|
|
|
|
if (pid == child)
|
|
break;
|
|
}
|
|
|
|
if (WIFEXITED(wait_status)) {
|
|
ksft_print_msg("Child exited saving values with status %d\n",
|
|
WEXITSTATUS(wait_status));
|
|
pass = false;
|
|
goto out;
|
|
}
|
|
|
|
if (WIFSIGNALED(wait_status)) {
|
|
ksft_print_msg("Child died from signal %d saving values\n",
|
|
WTERMSIG(wait_status));
|
|
pass = false;
|
|
goto out;
|
|
}
|
|
|
|
/* See what happened as a result */
|
|
read_child_regs(child);
|
|
|
|
if (!continue_breakpoint(child, PTRACE_DETACH))
|
|
goto cleanup;
|
|
|
|
/* The child should exit cleanly */
|
|
got_alarm = false;
|
|
alarm(1);
|
|
while (1) {
|
|
if (got_alarm) {
|
|
ksft_print_msg("Wait for child timed out\n");
|
|
goto cleanup;
|
|
}
|
|
|
|
pid = waitpid(child, &wait_status, 0);
|
|
if (pid < 0) {
|
|
if (errno == EINTR)
|
|
continue;
|
|
ksft_exit_fail_msg("waitpid() failed: %s (%d)\n",
|
|
strerror(errno), errno);
|
|
}
|
|
|
|
if (pid == child)
|
|
break;
|
|
}
|
|
alarm(0);
|
|
|
|
if (got_alarm) {
|
|
ksft_print_msg("Timed out waiting for child\n");
|
|
pass = false;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (pid == child && WIFSIGNALED(wait_status)) {
|
|
ksft_print_msg("Child died from signal %d cleaning up\n",
|
|
WTERMSIG(wait_status));
|
|
pass = false;
|
|
goto out;
|
|
}
|
|
|
|
if (pid == child && WIFEXITED(wait_status)) {
|
|
if (WEXITSTATUS(wait_status) != 0) {
|
|
ksft_print_msg("Child exited with error %d\n",
|
|
WEXITSTATUS(wait_status));
|
|
pass = false;
|
|
}
|
|
} else {
|
|
ksft_print_msg("Child did not exit cleanly\n");
|
|
pass = false;
|
|
goto cleanup;
|
|
}
|
|
|
|
goto out;
|
|
|
|
cleanup:
|
|
ret = kill(child, SIGKILL);
|
|
if (ret != 0) {
|
|
ksft_print_msg("kill() failed: %s (%d)\n",
|
|
strerror(errno), errno);
|
|
return false;
|
|
}
|
|
|
|
while (1) {
|
|
pid = waitpid(child, &wait_status, 0);
|
|
if (pid < 0) {
|
|
if (errno == EINTR)
|
|
continue;
|
|
ksft_exit_fail_msg("waitpid() failed: %s (%d)\n",
|
|
strerror(errno), errno);
|
|
}
|
|
|
|
if (pid == child)
|
|
break;
|
|
}
|
|
|
|
out:
|
|
return pass;
|
|
}
|
|
|
|
static void fill_random(void *buf, size_t size)
|
|
{
|
|
int i;
|
|
uint32_t *lbuf = buf;
|
|
|
|
/* random() returns a 32 bit number regardless of the size of long */
|
|
for (i = 0; i < size / sizeof(uint32_t); i++)
|
|
lbuf[i] = random();
|
|
}
|
|
|
|
static void fill_random_ffr(void *buf, size_t vq)
|
|
{
|
|
uint8_t *lbuf = buf;
|
|
int bits, i;
|
|
|
|
/*
|
|
* Only values with a continuous set of 0..n bits set are
|
|
* valid for FFR, set all bits then clear a random number of
|
|
* high bits.
|
|
*/
|
|
memset(buf, 0, __SVE_FFR_SIZE(vq));
|
|
|
|
bits = random() % (__SVE_FFR_SIZE(vq) * 8);
|
|
for (i = 0; i < bits / 8; i++)
|
|
lbuf[i] = 0xff;
|
|
if (bits / 8 != __SVE_FFR_SIZE(vq))
|
|
lbuf[i] = (1 << (bits % 8)) - 1;
|
|
}
|
|
|
|
static void fpsimd_to_sve(__uint128_t *v, char *z, int vl)
|
|
{
|
|
int vq = __sve_vq_from_vl(vl);
|
|
int i;
|
|
__uint128_t *p;
|
|
|
|
if (!vl)
|
|
return;
|
|
|
|
for (i = 0; i < __SVE_NUM_ZREGS; i++) {
|
|
p = (__uint128_t *)&z[__SVE_ZREG_OFFSET(vq, i)];
|
|
*p = arm64_cpu_to_le128(v[i]);
|
|
}
|
|
}
|
|
|
|
static void set_initial_values(struct test_config *config)
|
|
{
|
|
int vq = __sve_vq_from_vl(vl_in(config));
|
|
int sme_vq = __sve_vq_from_vl(config->sme_vl_in);
|
|
bool sm_change;
|
|
|
|
svcr_in = config->svcr_in;
|
|
svcr_expected = config->svcr_expected;
|
|
svcr_out = 0;
|
|
|
|
if (sme_supported() &&
|
|
(svcr_in & SVCR_SM) != (svcr_expected & SVCR_SM))
|
|
sm_change = true;
|
|
else
|
|
sm_change = false;
|
|
|
|
fill_random(&v_in, sizeof(v_in));
|
|
memcpy(v_expected, v_in, sizeof(v_in));
|
|
memset(v_out, 0, sizeof(v_out));
|
|
|
|
/* Changes will be handled in the test case */
|
|
if (sve_supported() || (config->svcr_in & SVCR_SM)) {
|
|
/* The low 128 bits of Z are shared with the V registers */
|
|
fill_random(&z_in, __SVE_ZREGS_SIZE(vq));
|
|
fpsimd_to_sve(v_in, z_in, vl_in(config));
|
|
memcpy(z_expected, z_in, __SVE_ZREGS_SIZE(vq));
|
|
memset(z_out, 0, sizeof(z_out));
|
|
|
|
fill_random(&p_in, __SVE_PREGS_SIZE(vq));
|
|
memcpy(p_expected, p_in, __SVE_PREGS_SIZE(vq));
|
|
memset(p_out, 0, sizeof(p_out));
|
|
|
|
if ((config->svcr_in & SVCR_SM) && !fa64_supported())
|
|
memset(ffr_in, 0, __SVE_PREG_SIZE(vq));
|
|
else
|
|
fill_random_ffr(&ffr_in, vq);
|
|
memcpy(ffr_expected, ffr_in, __SVE_PREG_SIZE(vq));
|
|
memset(ffr_out, 0, __SVE_PREG_SIZE(vq));
|
|
}
|
|
|
|
if (config->svcr_in & SVCR_ZA)
|
|
fill_random(za_in, ZA_SIG_REGS_SIZE(sme_vq));
|
|
else
|
|
memset(za_in, 0, ZA_SIG_REGS_SIZE(sme_vq));
|
|
if (config->svcr_expected & SVCR_ZA)
|
|
memcpy(za_expected, za_in, ZA_SIG_REGS_SIZE(sme_vq));
|
|
else
|
|
memset(za_expected, 0, ZA_SIG_REGS_SIZE(sme_vq));
|
|
if (sme_supported())
|
|
memset(za_out, 0, sizeof(za_out));
|
|
|
|
if (sme2_supported()) {
|
|
if (config->svcr_in & SVCR_ZA)
|
|
fill_random(zt_in, ZT_SIG_REG_BYTES);
|
|
else
|
|
memset(zt_in, 0, ZT_SIG_REG_BYTES);
|
|
if (config->svcr_expected & SVCR_ZA)
|
|
memcpy(zt_expected, zt_in, ZT_SIG_REG_BYTES);
|
|
else
|
|
memset(zt_expected, 0, ZT_SIG_REG_BYTES);
|
|
memset(zt_out, 0, sizeof(zt_out));
|
|
}
|
|
|
|
if (fpmr_supported()) {
|
|
fill_random(&fpmr_in, sizeof(fpmr_in));
|
|
fpmr_in &= FPMR_SAFE_BITS;
|
|
|
|
/* Entering or exiting streaming mode clears FPMR */
|
|
if (sm_change)
|
|
fpmr_expected = 0;
|
|
else
|
|
fpmr_expected = fpmr_in;
|
|
} else {
|
|
fpmr_in = 0;
|
|
fpmr_expected = 0;
|
|
fpmr_out = 0;
|
|
}
|
|
}
|
|
|
|
static bool check_memory_values(struct test_config *config)
|
|
{
|
|
bool pass = true;
|
|
int vq, sme_vq;
|
|
|
|
if (!compare_buffer("saved V", v_out, v_expected, sizeof(v_out)))
|
|
pass = false;
|
|
|
|
vq = __sve_vq_from_vl(vl_expected(config));
|
|
sme_vq = __sve_vq_from_vl(config->sme_vl_expected);
|
|
|
|
if (svcr_out != svcr_expected) {
|
|
ksft_print_msg("Mismatch in saved SVCR %lx != %lx\n",
|
|
svcr_out, svcr_expected);
|
|
pass = false;
|
|
}
|
|
|
|
if (sve_vl_out != config->sve_vl_expected) {
|
|
ksft_print_msg("Mismatch in SVE VL: %ld != %d\n",
|
|
sve_vl_out, config->sve_vl_expected);
|
|
pass = false;
|
|
}
|
|
|
|
if (sme_vl_out != config->sme_vl_expected) {
|
|
ksft_print_msg("Mismatch in SME VL: %ld != %d\n",
|
|
sme_vl_out, config->sme_vl_expected);
|
|
pass = false;
|
|
}
|
|
|
|
if (!compare_buffer("saved Z", z_out, z_expected,
|
|
__SVE_ZREGS_SIZE(vq)))
|
|
pass = false;
|
|
|
|
if (!compare_buffer("saved P", p_out, p_expected,
|
|
__SVE_PREGS_SIZE(vq)))
|
|
pass = false;
|
|
|
|
if (!compare_buffer("saved FFR", ffr_out, ffr_expected,
|
|
__SVE_PREG_SIZE(vq)))
|
|
pass = false;
|
|
|
|
if (!compare_buffer("saved ZA", za_out, za_expected,
|
|
ZA_PT_ZA_SIZE(sme_vq)))
|
|
pass = false;
|
|
|
|
if (!compare_buffer("saved ZT", zt_out, zt_expected, ZT_SIG_REG_BYTES))
|
|
pass = false;
|
|
|
|
if (fpmr_out != fpmr_expected) {
|
|
ksft_print_msg("Mismatch in saved FPMR: %lx != %lx\n",
|
|
fpmr_out, fpmr_expected);
|
|
pass = false;
|
|
}
|
|
|
|
return pass;
|
|
}
|
|
|
|
static bool sve_sme_same(struct test_config *config)
|
|
{
|
|
if (config->sve_vl_in != config->sve_vl_expected)
|
|
return false;
|
|
|
|
if (config->sme_vl_in != config->sme_vl_expected)
|
|
return false;
|
|
|
|
if (config->svcr_in != config->svcr_expected)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool sve_write_supported(struct test_config *config)
|
|
{
|
|
if (!sve_supported() && !sme_supported())
|
|
return false;
|
|
|
|
if ((config->svcr_in & SVCR_ZA) != (config->svcr_expected & SVCR_ZA))
|
|
return false;
|
|
|
|
if (config->svcr_expected & SVCR_SM) {
|
|
if (config->sve_vl_in != config->sve_vl_expected) {
|
|
return false;
|
|
}
|
|
|
|
/* Changing the SME VL disables ZA */
|
|
if ((config->svcr_expected & SVCR_ZA) &&
|
|
(config->sme_vl_in != config->sme_vl_expected)) {
|
|
return false;
|
|
}
|
|
} else {
|
|
if (config->sme_vl_in != config->sme_vl_expected) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void fpsimd_write_expected(struct test_config *config)
|
|
{
|
|
int vl;
|
|
|
|
fill_random(&v_expected, sizeof(v_expected));
|
|
|
|
/* The SVE registers are flushed by a FPSIMD write */
|
|
vl = vl_expected(config);
|
|
|
|
memset(z_expected, 0, __SVE_ZREGS_SIZE(__sve_vq_from_vl(vl)));
|
|
memset(p_expected, 0, __SVE_PREGS_SIZE(__sve_vq_from_vl(vl)));
|
|
memset(ffr_expected, 0, __SVE_PREG_SIZE(__sve_vq_from_vl(vl)));
|
|
|
|
fpsimd_to_sve(v_expected, z_expected, vl);
|
|
}
|
|
|
|
static void fpsimd_write(pid_t child, struct test_config *test_config)
|
|
{
|
|
struct user_fpsimd_state fpsimd;
|
|
struct iovec iov;
|
|
int ret;
|
|
|
|
memset(&fpsimd, 0, sizeof(fpsimd));
|
|
memcpy(&fpsimd.vregs, v_expected, sizeof(v_expected));
|
|
|
|
iov.iov_base = &fpsimd;
|
|
iov.iov_len = sizeof(fpsimd);
|
|
ret = ptrace(PTRACE_SETREGSET, child, NT_PRFPREG, &iov);
|
|
if (ret == -1)
|
|
ksft_print_msg("FPSIMD set failed: (%s) %d\n",
|
|
strerror(errno), errno);
|
|
}
|
|
|
|
static bool fpmr_write_supported(struct test_config *config)
|
|
{
|
|
if (!fpmr_supported())
|
|
return false;
|
|
|
|
if (!sve_sme_same(config))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static void fpmr_write_expected(struct test_config *config)
|
|
{
|
|
fill_random(&fpmr_expected, sizeof(fpmr_expected));
|
|
fpmr_expected &= FPMR_SAFE_BITS;
|
|
}
|
|
|
|
static void fpmr_write(pid_t child, struct test_config *config)
|
|
{
|
|
struct iovec iov;
|
|
int ret;
|
|
|
|
iov.iov_len = sizeof(fpmr_expected);
|
|
iov.iov_base = &fpmr_expected;
|
|
ret = ptrace(PTRACE_SETREGSET, child, NT_ARM_FPMR, &iov);
|
|
if (ret != 0)
|
|
ksft_print_msg("Failed to write FPMR: %s (%d)\n",
|
|
strerror(errno), errno);
|
|
}
|
|
|
|
static void sve_write_expected(struct test_config *config)
|
|
{
|
|
int vl = vl_expected(config);
|
|
int sme_vq = __sve_vq_from_vl(config->sme_vl_expected);
|
|
|
|
fill_random(z_expected, __SVE_ZREGS_SIZE(__sve_vq_from_vl(vl)));
|
|
fill_random(p_expected, __SVE_PREGS_SIZE(__sve_vq_from_vl(vl)));
|
|
|
|
if ((svcr_expected & SVCR_SM) && !fa64_supported())
|
|
memset(ffr_expected, 0, __SVE_PREG_SIZE(sme_vq));
|
|
else
|
|
fill_random_ffr(ffr_expected, __sve_vq_from_vl(vl));
|
|
|
|
/* Share the low bits of Z with V */
|
|
fill_random(&v_expected, sizeof(v_expected));
|
|
fpsimd_to_sve(v_expected, z_expected, vl);
|
|
|
|
if (config->sme_vl_in != config->sme_vl_expected) {
|
|
memset(za_expected, 0, ZA_PT_ZA_SIZE(sme_vq));
|
|
memset(zt_expected, 0, sizeof(zt_expected));
|
|
}
|
|
}
|
|
|
|
static void sve_write(pid_t child, struct test_config *config)
|
|
{
|
|
struct user_sve_header *sve;
|
|
struct iovec iov;
|
|
int ret, vl, vq, regset;
|
|
|
|
vl = vl_expected(config);
|
|
vq = __sve_vq_from_vl(vl);
|
|
|
|
iov.iov_len = SVE_PT_SVE_OFFSET + SVE_PT_SVE_SIZE(vq, SVE_PT_REGS_SVE);
|
|
iov.iov_base = malloc(iov.iov_len);
|
|
if (!iov.iov_base) {
|
|
ksft_print_msg("Failed allocating %lu byte SVE write buffer\n",
|
|
iov.iov_len);
|
|
return;
|
|
}
|
|
memset(iov.iov_base, 0, iov.iov_len);
|
|
|
|
sve = iov.iov_base;
|
|
sve->size = iov.iov_len;
|
|
sve->flags = SVE_PT_REGS_SVE;
|
|
sve->vl = vl;
|
|
|
|
memcpy(iov.iov_base + SVE_PT_SVE_ZREG_OFFSET(vq, 0),
|
|
z_expected, SVE_PT_SVE_ZREGS_SIZE(vq));
|
|
memcpy(iov.iov_base + SVE_PT_SVE_PREG_OFFSET(vq, 0),
|
|
p_expected, SVE_PT_SVE_PREGS_SIZE(vq));
|
|
memcpy(iov.iov_base + SVE_PT_SVE_FFR_OFFSET(vq),
|
|
ffr_expected, SVE_PT_SVE_PREG_SIZE(vq));
|
|
|
|
if (svcr_expected & SVCR_SM)
|
|
regset = NT_ARM_SSVE;
|
|
else
|
|
regset = NT_ARM_SVE;
|
|
|
|
ret = ptrace(PTRACE_SETREGSET, child, regset, &iov);
|
|
if (ret != 0)
|
|
ksft_print_msg("Failed to write SVE: %s (%d)\n",
|
|
strerror(errno), errno);
|
|
|
|
free(iov.iov_base);
|
|
}
|
|
|
|
static bool za_write_supported(struct test_config *config)
|
|
{
|
|
if (config->sme_vl_in != config->sme_vl_expected) {
|
|
/* Changing the SME VL exits streaming mode. */
|
|
if (config->svcr_expected & SVCR_SM) {
|
|
return false;
|
|
}
|
|
} else {
|
|
/* Otherwise we can't change streaming mode */
|
|
if ((config->svcr_in & SVCR_SM) !=
|
|
(config->svcr_expected & SVCR_SM)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void za_write_expected(struct test_config *config)
|
|
{
|
|
int sme_vq, sve_vq;
|
|
|
|
sme_vq = __sve_vq_from_vl(config->sme_vl_expected);
|
|
|
|
if (config->svcr_expected & SVCR_ZA) {
|
|
fill_random(za_expected, ZA_PT_ZA_SIZE(sme_vq));
|
|
} else {
|
|
memset(za_expected, 0, ZA_PT_ZA_SIZE(sme_vq));
|
|
memset(zt_expected, 0, sizeof(zt_expected));
|
|
}
|
|
|
|
/* Changing the SME VL flushes ZT, SVE state and exits SM */
|
|
if (config->sme_vl_in != config->sme_vl_expected) {
|
|
svcr_expected &= ~SVCR_SM;
|
|
|
|
sve_vq = __sve_vq_from_vl(vl_expected(config));
|
|
memset(z_expected, 0, __SVE_ZREGS_SIZE(sve_vq));
|
|
memset(p_expected, 0, __SVE_PREGS_SIZE(sve_vq));
|
|
memset(ffr_expected, 0, __SVE_PREG_SIZE(sve_vq));
|
|
memset(zt_expected, 0, sizeof(zt_expected));
|
|
|
|
fpsimd_to_sve(v_expected, z_expected, vl_expected(config));
|
|
}
|
|
}
|
|
|
|
static void za_write(pid_t child, struct test_config *config)
|
|
{
|
|
struct user_za_header *za;
|
|
struct iovec iov;
|
|
int ret, vq;
|
|
|
|
vq = __sve_vq_from_vl(config->sme_vl_expected);
|
|
|
|
if (config->svcr_expected & SVCR_ZA)
|
|
iov.iov_len = ZA_PT_SIZE(vq);
|
|
else
|
|
iov.iov_len = sizeof(*za);
|
|
iov.iov_base = malloc(iov.iov_len);
|
|
if (!iov.iov_base) {
|
|
ksft_print_msg("Failed allocating %lu byte ZA write buffer\n",
|
|
iov.iov_len);
|
|
return;
|
|
}
|
|
memset(iov.iov_base, 0, iov.iov_len);
|
|
|
|
za = iov.iov_base;
|
|
za->size = iov.iov_len;
|
|
za->vl = config->sme_vl_expected;
|
|
if (config->svcr_expected & SVCR_ZA)
|
|
memcpy(iov.iov_base + ZA_PT_ZA_OFFSET, za_expected,
|
|
ZA_PT_ZA_SIZE(vq));
|
|
|
|
ret = ptrace(PTRACE_SETREGSET, child, NT_ARM_ZA, &iov);
|
|
if (ret != 0)
|
|
ksft_print_msg("Failed to write ZA: %s (%d)\n",
|
|
strerror(errno), errno);
|
|
|
|
free(iov.iov_base);
|
|
}
|
|
|
|
static bool zt_write_supported(struct test_config *config)
|
|
{
|
|
if (!sme2_supported())
|
|
return false;
|
|
if (config->sme_vl_in != config->sme_vl_expected)
|
|
return false;
|
|
if (!(config->svcr_expected & SVCR_ZA))
|
|
return false;
|
|
if ((config->svcr_in & SVCR_SM) != (config->svcr_expected & SVCR_SM))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static void zt_write_expected(struct test_config *config)
|
|
{
|
|
int sme_vq;
|
|
|
|
sme_vq = __sve_vq_from_vl(config->sme_vl_expected);
|
|
|
|
if (config->svcr_expected & SVCR_ZA) {
|
|
fill_random(zt_expected, sizeof(zt_expected));
|
|
} else {
|
|
memset(za_expected, 0, ZA_PT_ZA_SIZE(sme_vq));
|
|
memset(zt_expected, 0, sizeof(zt_expected));
|
|
}
|
|
}
|
|
|
|
static void zt_write(pid_t child, struct test_config *config)
|
|
{
|
|
struct iovec iov;
|
|
int ret;
|
|
|
|
iov.iov_len = ZT_SIG_REG_BYTES;
|
|
iov.iov_base = zt_expected;
|
|
ret = ptrace(PTRACE_SETREGSET, child, NT_ARM_ZT, &iov);
|
|
if (ret != 0)
|
|
ksft_print_msg("Failed to write ZT: %s (%d)\n",
|
|
strerror(errno), errno);
|
|
}
|
|
|
|
/* Actually run a test */
|
|
static void run_test(struct test_definition *test, struct test_config *config)
|
|
{
|
|
pid_t child;
|
|
char name[1024];
|
|
bool pass;
|
|
|
|
if (sve_supported() && sme_supported())
|
|
snprintf(name, sizeof(name), "%s, SVE %d->%d, SME %d/%x->%d/%x",
|
|
test->name,
|
|
config->sve_vl_in, config->sve_vl_expected,
|
|
config->sme_vl_in, config->svcr_in,
|
|
config->sme_vl_expected, config->svcr_expected);
|
|
else if (sve_supported())
|
|
snprintf(name, sizeof(name), "%s, SVE %d->%d", test->name,
|
|
config->sve_vl_in, config->sve_vl_expected);
|
|
else if (sme_supported())
|
|
snprintf(name, sizeof(name), "%s, SME %d/%x->%d/%x",
|
|
test->name,
|
|
config->sme_vl_in, config->svcr_in,
|
|
config->sme_vl_expected, config->svcr_expected);
|
|
else
|
|
snprintf(name, sizeof(name), "%s", test->name);
|
|
|
|
if (test->supported && !test->supported(config)) {
|
|
ksft_test_result_skip("%s\n", name);
|
|
return;
|
|
}
|
|
|
|
set_initial_values(config);
|
|
|
|
if (test->set_expected_values)
|
|
test->set_expected_values(config);
|
|
|
|
child = fork();
|
|
if (child < 0)
|
|
ksft_exit_fail_msg("fork() failed: %s (%d)\n",
|
|
strerror(errno), errno);
|
|
/* run_child() never returns */
|
|
if (child == 0)
|
|
run_child(config);
|
|
|
|
pass = run_parent(child, test, config);
|
|
if (!check_memory_values(config))
|
|
pass = false;
|
|
|
|
ksft_test_result(pass, "%s\n", name);
|
|
}
|
|
|
|
static void run_tests(struct test_definition defs[], int count,
|
|
struct test_config *config)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < count; i++)
|
|
run_test(&defs[i], config);
|
|
}
|
|
|
|
static struct test_definition base_test_defs[] = {
|
|
{
|
|
.name = "No writes",
|
|
.supported = sve_sme_same,
|
|
},
|
|
{
|
|
.name = "FPSIMD write",
|
|
.supported = sve_sme_same,
|
|
.set_expected_values = fpsimd_write_expected,
|
|
.modify_values = fpsimd_write,
|
|
},
|
|
{
|
|
.name = "FPMR write",
|
|
.supported = fpmr_write_supported,
|
|
.set_expected_values = fpmr_write_expected,
|
|
.modify_values = fpmr_write,
|
|
},
|
|
};
|
|
|
|
static struct test_definition sve_test_defs[] = {
|
|
{
|
|
.name = "SVE write",
|
|
.supported = sve_write_supported,
|
|
.set_expected_values = sve_write_expected,
|
|
.modify_values = sve_write,
|
|
},
|
|
};
|
|
|
|
static struct test_definition za_test_defs[] = {
|
|
{
|
|
.name = "ZA write",
|
|
.supported = za_write_supported,
|
|
.set_expected_values = za_write_expected,
|
|
.modify_values = za_write,
|
|
},
|
|
};
|
|
|
|
static struct test_definition zt_test_defs[] = {
|
|
{
|
|
.name = "ZT write",
|
|
.supported = zt_write_supported,
|
|
.set_expected_values = zt_write_expected,
|
|
.modify_values = zt_write,
|
|
},
|
|
};
|
|
|
|
static int sve_vls[MAX_NUM_VLS], sme_vls[MAX_NUM_VLS];
|
|
static int sve_vl_count, sme_vl_count;
|
|
|
|
static void probe_vls(const char *name, int vls[], int *vl_count, int set_vl)
|
|
{
|
|
unsigned int vq;
|
|
int vl;
|
|
|
|
*vl_count = 0;
|
|
|
|
for (vq = ARCH_VQ_MAX; vq > 0; vq /= 2) {
|
|
vl = prctl(set_vl, vq * 16);
|
|
if (vl == -1)
|
|
ksft_exit_fail_msg("SET_VL failed: %s (%d)\n",
|
|
strerror(errno), errno);
|
|
|
|
vl &= PR_SVE_VL_LEN_MASK;
|
|
|
|
if (*vl_count && (vl == vls[*vl_count - 1]))
|
|
break;
|
|
|
|
vq = sve_vq_from_vl(vl);
|
|
|
|
vls[*vl_count] = vl;
|
|
*vl_count += 1;
|
|
}
|
|
|
|
if (*vl_count > 2) {
|
|
/* Just use the minimum and maximum */
|
|
vls[1] = vls[*vl_count - 1];
|
|
ksft_print_msg("%d %s VLs, using %d and %d\n",
|
|
*vl_count, name, vls[0], vls[1]);
|
|
*vl_count = 2;
|
|
} else {
|
|
ksft_print_msg("%d %s VLs\n", *vl_count, name);
|
|
}
|
|
}
|
|
|
|
static struct {
|
|
int svcr_in, svcr_expected;
|
|
} svcr_combinations[] = {
|
|
{ .svcr_in = 0, .svcr_expected = 0, },
|
|
{ .svcr_in = 0, .svcr_expected = SVCR_SM, },
|
|
{ .svcr_in = 0, .svcr_expected = SVCR_ZA, },
|
|
/* Can't enable both SM and ZA with a single ptrace write */
|
|
|
|
{ .svcr_in = SVCR_SM, .svcr_expected = 0, },
|
|
{ .svcr_in = SVCR_SM, .svcr_expected = SVCR_SM, },
|
|
{ .svcr_in = SVCR_SM, .svcr_expected = SVCR_ZA, },
|
|
{ .svcr_in = SVCR_SM, .svcr_expected = SVCR_SM | SVCR_ZA, },
|
|
|
|
{ .svcr_in = SVCR_ZA, .svcr_expected = 0, },
|
|
{ .svcr_in = SVCR_ZA, .svcr_expected = SVCR_SM, },
|
|
{ .svcr_in = SVCR_ZA, .svcr_expected = SVCR_ZA, },
|
|
{ .svcr_in = SVCR_ZA, .svcr_expected = SVCR_SM | SVCR_ZA, },
|
|
|
|
{ .svcr_in = SVCR_SM | SVCR_ZA, .svcr_expected = 0, },
|
|
{ .svcr_in = SVCR_SM | SVCR_ZA, .svcr_expected = SVCR_SM, },
|
|
{ .svcr_in = SVCR_SM | SVCR_ZA, .svcr_expected = SVCR_ZA, },
|
|
{ .svcr_in = SVCR_SM | SVCR_ZA, .svcr_expected = SVCR_SM | SVCR_ZA, },
|
|
};
|
|
|
|
static void run_sve_tests(void)
|
|
{
|
|
struct test_config test_config;
|
|
int i, j;
|
|
|
|
if (!sve_supported())
|
|
return;
|
|
|
|
test_config.sme_vl_in = sme_vls[0];
|
|
test_config.sme_vl_expected = sme_vls[0];
|
|
test_config.svcr_in = 0;
|
|
test_config.svcr_expected = 0;
|
|
|
|
for (i = 0; i < sve_vl_count; i++) {
|
|
test_config.sve_vl_in = sve_vls[i];
|
|
|
|
for (j = 0; j < sve_vl_count; j++) {
|
|
test_config.sve_vl_expected = sve_vls[j];
|
|
|
|
run_tests(base_test_defs,
|
|
ARRAY_SIZE(base_test_defs),
|
|
&test_config);
|
|
if (sve_supported())
|
|
run_tests(sve_test_defs,
|
|
ARRAY_SIZE(sve_test_defs),
|
|
&test_config);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
static void run_sme_tests(void)
|
|
{
|
|
struct test_config test_config;
|
|
int i, j, k;
|
|
|
|
if (!sme_supported())
|
|
return;
|
|
|
|
test_config.sve_vl_in = sve_vls[0];
|
|
test_config.sve_vl_expected = sve_vls[0];
|
|
|
|
/*
|
|
* Every SME VL/SVCR combination
|
|
*/
|
|
for (i = 0; i < sme_vl_count; i++) {
|
|
test_config.sme_vl_in = sme_vls[i];
|
|
|
|
for (j = 0; j < sme_vl_count; j++) {
|
|
test_config.sme_vl_expected = sme_vls[j];
|
|
|
|
for (k = 0; k < ARRAY_SIZE(svcr_combinations); k++) {
|
|
test_config.svcr_in = svcr_combinations[k].svcr_in;
|
|
test_config.svcr_expected = svcr_combinations[k].svcr_expected;
|
|
|
|
run_tests(base_test_defs,
|
|
ARRAY_SIZE(base_test_defs),
|
|
&test_config);
|
|
run_tests(sve_test_defs,
|
|
ARRAY_SIZE(sve_test_defs),
|
|
&test_config);
|
|
run_tests(za_test_defs,
|
|
ARRAY_SIZE(za_test_defs),
|
|
&test_config);
|
|
|
|
if (sme2_supported())
|
|
run_tests(zt_test_defs,
|
|
ARRAY_SIZE(zt_test_defs),
|
|
&test_config);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
struct test_config test_config;
|
|
struct sigaction sa;
|
|
int tests, ret, tmp;
|
|
|
|
srandom(getpid());
|
|
|
|
ksft_print_header();
|
|
|
|
if (sve_supported()) {
|
|
probe_vls("SVE", sve_vls, &sve_vl_count, PR_SVE_SET_VL);
|
|
|
|
tests = ARRAY_SIZE(base_test_defs) +
|
|
ARRAY_SIZE(sve_test_defs);
|
|
tests *= sve_vl_count * sve_vl_count;
|
|
} else {
|
|
/* Only run the FPSIMD tests */
|
|
sve_vl_count = 1;
|
|
tests = ARRAY_SIZE(base_test_defs);
|
|
}
|
|
|
|
if (sme_supported()) {
|
|
probe_vls("SME", sme_vls, &sme_vl_count, PR_SME_SET_VL);
|
|
|
|
tmp = ARRAY_SIZE(base_test_defs) + ARRAY_SIZE(sve_test_defs)
|
|
+ ARRAY_SIZE(za_test_defs);
|
|
|
|
if (sme2_supported())
|
|
tmp += ARRAY_SIZE(zt_test_defs);
|
|
|
|
tmp *= sme_vl_count * sme_vl_count;
|
|
tmp *= ARRAY_SIZE(svcr_combinations);
|
|
tests += tmp;
|
|
} else {
|
|
sme_vl_count = 1;
|
|
}
|
|
|
|
if (sme2_supported())
|
|
ksft_print_msg("SME2 supported\n");
|
|
|
|
if (fa64_supported())
|
|
ksft_print_msg("FA64 supported\n");
|
|
|
|
if (fpmr_supported())
|
|
ksft_print_msg("FPMR supported\n");
|
|
|
|
ksft_set_plan(tests);
|
|
|
|
/* Get signal handers ready before we start any children */
|
|
memset(&sa, 0, sizeof(sa));
|
|
sa.sa_sigaction = handle_alarm;
|
|
sa.sa_flags = SA_RESTART | SA_SIGINFO;
|
|
sigemptyset(&sa.sa_mask);
|
|
ret = sigaction(SIGALRM, &sa, NULL);
|
|
if (ret < 0)
|
|
ksft_print_msg("Failed to install SIGALRM handler: %s (%d)\n",
|
|
strerror(errno), errno);
|
|
|
|
/*
|
|
* Run the test set if there is no SVE or SME, with those we
|
|
* have to pick a VL for each run.
|
|
*/
|
|
if (!sve_supported()) {
|
|
test_config.sve_vl_in = 0;
|
|
test_config.sve_vl_expected = 0;
|
|
test_config.sme_vl_in = 0;
|
|
test_config.sme_vl_expected = 0;
|
|
test_config.svcr_in = 0;
|
|
test_config.svcr_expected = 0;
|
|
|
|
run_tests(base_test_defs, ARRAY_SIZE(base_test_defs),
|
|
&test_config);
|
|
}
|
|
|
|
run_sve_tests();
|
|
run_sme_tests();
|
|
|
|
ksft_finished();
|
|
}
|