Merge branch 'for-next/kselftest' into for-next/core
* for-next/kselftest: (28 commits) : Kselftest updates for arm64 kselftest/arm64: Handle EINTR while reading data from children kselftest/arm64: Flag fp-stress as exiting when we begin finishing up kselftest/arm64: Don't repeat termination handler for fp-stress kselftest/arm64: Don't enable v8.5 for MTE selftest builds kselftest/arm64: Fix typo in hwcap check kselftest/arm64: Add hwcap test for RNG kselftest/arm64: Add SVE 2 to the tested hwcaps kselftest/arm64: Add missing newline in hwcap output kselftest/arm64: Fix spelling misakes of signal names kselftest/arm64: Enforce actual ABI for SVE syscalls kselftest/arm64: Correct buffer allocation for SVE Z registers kselftest/arm64: Include larger SVE and SME VLs in signal tests kselftest/arm64: Allow larger buffers in get_signal_context() kselftest/arm64: Preserve any EXTRA_CONTEXT in handle_signal_copyctx() kselftest/arm64: Validate contents of EXTRA_CONTEXT blocks kselftest/arm64: Only validate each signal context once kselftest/arm64: Remove unneeded protype for validate_extra_context() kselftest/arm64: Fix validation of EXTRA_CONTEXT signal context location kselftest/arm64: Fix validatation termination record after EXTRA_CONTEXT kselftest/arm64: Validate signal ucontext in place ...
This commit is contained in:
commit
c397623262
|
@ -1,3 +1,4 @@
|
|||
hwcap
|
||||
ptrace
|
||||
syscall-abi
|
||||
tpidr2
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Copyright (C) 2021 ARM Limited
|
||||
|
||||
TEST_GEN_PROGS := ptrace syscall-abi tpidr2
|
||||
TEST_GEN_PROGS := hwcap ptrace syscall-abi tpidr2
|
||||
|
||||
include ../../lib.mk
|
||||
|
||||
|
|
|
@ -0,0 +1,336 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (C) 2022 ARM Limited.
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <signal.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 <asm/hwcap.h>
|
||||
#include <asm/sigcontext.h>
|
||||
#include <asm/unistd.h>
|
||||
|
||||
#include "../../kselftest.h"
|
||||
|
||||
#define TESTS_PER_HWCAP 2
|
||||
|
||||
/*
|
||||
* Function expected to generate SIGILL when the feature is not
|
||||
* supported and return when it is supported. If SIGILL is generated
|
||||
* then the handler must be able to skip over the instruction safely.
|
||||
*
|
||||
* Note that it is expected that for many architecture extensions
|
||||
* there are no specific traps due to no architecture state being
|
||||
* added so we may not fault if running on a kernel which doesn't know
|
||||
* to add the hwcap.
|
||||
*/
|
||||
typedef void (*sigill_fn)(void);
|
||||
|
||||
static void rng_sigill(void)
|
||||
{
|
||||
asm volatile("mrs x0, S3_3_C2_C4_0" : : : "x0");
|
||||
}
|
||||
|
||||
static void sme_sigill(void)
|
||||
{
|
||||
/* RDSVL x0, #0 */
|
||||
asm volatile(".inst 0x04bf5800" : : : "x0");
|
||||
}
|
||||
|
||||
static void sve_sigill(void)
|
||||
{
|
||||
/* RDVL x0, #0 */
|
||||
asm volatile(".inst 0x04bf5000" : : : "x0");
|
||||
}
|
||||
|
||||
static void sve2_sigill(void)
|
||||
{
|
||||
/* SQABS Z0.b, P0/M, Z0.B */
|
||||
asm volatile(".inst 0x4408A000" : : : "z0");
|
||||
}
|
||||
|
||||
static void sveaes_sigill(void)
|
||||
{
|
||||
/* AESD z0.b, z0.b, z0.b */
|
||||
asm volatile(".inst 0x4522e400" : : : "z0");
|
||||
}
|
||||
|
||||
static void svepmull_sigill(void)
|
||||
{
|
||||
/* PMULLB Z0.Q, Z0.D, Z0.D */
|
||||
asm volatile(".inst 0x45006800" : : : "z0");
|
||||
}
|
||||
|
||||
static void svebitperm_sigill(void)
|
||||
{
|
||||
/* BDEP Z0.B, Z0.B, Z0.B */
|
||||
asm volatile(".inst 0x4500b400" : : : "z0");
|
||||
}
|
||||
|
||||
static void svesha3_sigill(void)
|
||||
{
|
||||
/* EOR3 Z0.D, Z0.D, Z0.D, Z0.D */
|
||||
asm volatile(".inst 0x4203800" : : : "z0");
|
||||
}
|
||||
|
||||
static void svesm4_sigill(void)
|
||||
{
|
||||
/* SM4E Z0.S, Z0.S, Z0.S */
|
||||
asm volatile(".inst 0x4523e000" : : : "z0");
|
||||
}
|
||||
|
||||
static void svei8mm_sigill(void)
|
||||
{
|
||||
/* USDOT Z0.S, Z0.B, Z0.B[0] */
|
||||
asm volatile(".inst 0x44a01800" : : : "z0");
|
||||
}
|
||||
|
||||
static void svef32mm_sigill(void)
|
||||
{
|
||||
/* FMMLA Z0.S, Z0.S, Z0.S */
|
||||
asm volatile(".inst 0x64a0e400" : : : "z0");
|
||||
}
|
||||
|
||||
static void svef64mm_sigill(void)
|
||||
{
|
||||
/* FMMLA Z0.D, Z0.D, Z0.D */
|
||||
asm volatile(".inst 0x64e0e400" : : : "z0");
|
||||
}
|
||||
|
||||
static void svebf16_sigill(void)
|
||||
{
|
||||
/* BFCVT Z0.H, P0/M, Z0.S */
|
||||
asm volatile(".inst 0x658aa000" : : : "z0");
|
||||
}
|
||||
|
||||
static const struct hwcap_data {
|
||||
const char *name;
|
||||
unsigned long at_hwcap;
|
||||
unsigned long hwcap_bit;
|
||||
const char *cpuinfo;
|
||||
sigill_fn sigill_fn;
|
||||
bool sigill_reliable;
|
||||
} hwcaps[] = {
|
||||
{
|
||||
.name = "RNG",
|
||||
.at_hwcap = AT_HWCAP2,
|
||||
.hwcap_bit = HWCAP2_RNG,
|
||||
.cpuinfo = "rng",
|
||||
.sigill_fn = rng_sigill,
|
||||
},
|
||||
{
|
||||
.name = "SME",
|
||||
.at_hwcap = AT_HWCAP2,
|
||||
.hwcap_bit = HWCAP2_SME,
|
||||
.cpuinfo = "sme",
|
||||
.sigill_fn = sme_sigill,
|
||||
.sigill_reliable = true,
|
||||
},
|
||||
{
|
||||
.name = "SVE",
|
||||
.at_hwcap = AT_HWCAP,
|
||||
.hwcap_bit = HWCAP_SVE,
|
||||
.cpuinfo = "sve",
|
||||
.sigill_fn = sve_sigill,
|
||||
.sigill_reliable = true,
|
||||
},
|
||||
{
|
||||
.name = "SVE 2",
|
||||
.at_hwcap = AT_HWCAP2,
|
||||
.hwcap_bit = HWCAP2_SVE2,
|
||||
.cpuinfo = "sve2",
|
||||
.sigill_fn = sve2_sigill,
|
||||
},
|
||||
{
|
||||
.name = "SVE AES",
|
||||
.at_hwcap = AT_HWCAP2,
|
||||
.hwcap_bit = HWCAP2_SVEAES,
|
||||
.cpuinfo = "sveaes",
|
||||
.sigill_fn = sveaes_sigill,
|
||||
},
|
||||
{
|
||||
.name = "SVE2 PMULL",
|
||||
.at_hwcap = AT_HWCAP2,
|
||||
.hwcap_bit = HWCAP2_SVEPMULL,
|
||||
.cpuinfo = "svepmull",
|
||||
.sigill_fn = svepmull_sigill,
|
||||
},
|
||||
{
|
||||
.name = "SVE2 BITPERM",
|
||||
.at_hwcap = AT_HWCAP2,
|
||||
.hwcap_bit = HWCAP2_SVEBITPERM,
|
||||
.cpuinfo = "svebitperm",
|
||||
.sigill_fn = svebitperm_sigill,
|
||||
},
|
||||
{
|
||||
.name = "SVE2 SHA3",
|
||||
.at_hwcap = AT_HWCAP2,
|
||||
.hwcap_bit = HWCAP2_SVESHA3,
|
||||
.cpuinfo = "svesha3",
|
||||
.sigill_fn = svesha3_sigill,
|
||||
},
|
||||
{
|
||||
.name = "SVE2 SM4",
|
||||
.at_hwcap = AT_HWCAP2,
|
||||
.hwcap_bit = HWCAP2_SVESM4,
|
||||
.cpuinfo = "svesm4",
|
||||
.sigill_fn = svesm4_sigill,
|
||||
},
|
||||
{
|
||||
.name = "SVE2 I8MM",
|
||||
.at_hwcap = AT_HWCAP2,
|
||||
.hwcap_bit = HWCAP2_SVEI8MM,
|
||||
.cpuinfo = "svei8mm",
|
||||
.sigill_fn = svei8mm_sigill,
|
||||
},
|
||||
{
|
||||
.name = "SVE2 F32MM",
|
||||
.at_hwcap = AT_HWCAP2,
|
||||
.hwcap_bit = HWCAP2_SVEF32MM,
|
||||
.cpuinfo = "svef32mm",
|
||||
.sigill_fn = svef32mm_sigill,
|
||||
},
|
||||
{
|
||||
.name = "SVE2 F64MM",
|
||||
.at_hwcap = AT_HWCAP2,
|
||||
.hwcap_bit = HWCAP2_SVEF64MM,
|
||||
.cpuinfo = "svef64mm",
|
||||
.sigill_fn = svef64mm_sigill,
|
||||
},
|
||||
{
|
||||
.name = "SVE2 BF16",
|
||||
.at_hwcap = AT_HWCAP2,
|
||||
.hwcap_bit = HWCAP2_SVEBF16,
|
||||
.cpuinfo = "svebf16",
|
||||
.sigill_fn = svebf16_sigill,
|
||||
},
|
||||
{
|
||||
.name = "SVE2 EBF16",
|
||||
.at_hwcap = AT_HWCAP2,
|
||||
.hwcap_bit = HWCAP2_SVE_EBF16,
|
||||
.cpuinfo = "sveebf16",
|
||||
},
|
||||
};
|
||||
|
||||
static bool seen_sigill;
|
||||
|
||||
static void handle_sigill(int sig, siginfo_t *info, void *context)
|
||||
{
|
||||
ucontext_t *uc = context;
|
||||
|
||||
seen_sigill = true;
|
||||
|
||||
/* Skip over the offending instruction */
|
||||
uc->uc_mcontext.pc += 4;
|
||||
}
|
||||
|
||||
bool cpuinfo_present(const char *name)
|
||||
{
|
||||
FILE *f;
|
||||
char buf[2048], name_space[30], name_newline[30];
|
||||
char *s;
|
||||
|
||||
/*
|
||||
* The feature should appear with a leading space and either a
|
||||
* trailing space or a newline.
|
||||
*/
|
||||
snprintf(name_space, sizeof(name_space), " %s ", name);
|
||||
snprintf(name_newline, sizeof(name_newline), " %s\n", name);
|
||||
|
||||
f = fopen("/proc/cpuinfo", "r");
|
||||
if (!f) {
|
||||
ksft_print_msg("Failed to open /proc/cpuinfo\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
while (fgets(buf, sizeof(buf), f)) {
|
||||
/* Features: line? */
|
||||
if (strncmp(buf, "Features\t:", strlen("Features\t:")) != 0)
|
||||
continue;
|
||||
|
||||
/* All CPUs should be symmetric, don't read any more */
|
||||
fclose(f);
|
||||
|
||||
s = strstr(buf, name_space);
|
||||
if (s)
|
||||
return true;
|
||||
s = strstr(buf, name_newline);
|
||||
if (s)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
ksft_print_msg("Failed to find Features in /proc/cpuinfo\n");
|
||||
fclose(f);
|
||||
return false;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
const struct hwcap_data *hwcap;
|
||||
int i, ret;
|
||||
bool have_cpuinfo, have_hwcap;
|
||||
struct sigaction sa;
|
||||
|
||||
ksft_print_header();
|
||||
ksft_set_plan(ARRAY_SIZE(hwcaps) * TESTS_PER_HWCAP);
|
||||
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa.sa_sigaction = handle_sigill;
|
||||
sa.sa_flags = SA_RESTART | SA_SIGINFO;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
ret = sigaction(SIGILL, &sa, NULL);
|
||||
if (ret < 0)
|
||||
ksft_exit_fail_msg("Failed to install SIGILL handler: %s (%d)\n",
|
||||
strerror(errno), errno);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(hwcaps); i++) {
|
||||
hwcap = &hwcaps[i];
|
||||
|
||||
have_hwcap = getauxval(hwcap->at_hwcap) & hwcap->hwcap_bit;
|
||||
have_cpuinfo = cpuinfo_present(hwcap->cpuinfo);
|
||||
|
||||
if (have_hwcap)
|
||||
ksft_print_msg("%s present\n", hwcap->name);
|
||||
|
||||
ksft_test_result(have_hwcap == have_cpuinfo,
|
||||
"cpuinfo_match_%s\n", hwcap->name);
|
||||
|
||||
if (hwcap->sigill_fn) {
|
||||
seen_sigill = false;
|
||||
hwcap->sigill_fn();
|
||||
|
||||
if (have_hwcap) {
|
||||
/* Should be able to use the extension */
|
||||
ksft_test_result(!seen_sigill, "sigill_%s\n",
|
||||
hwcap->name);
|
||||
} else if (hwcap->sigill_reliable) {
|
||||
/* Guaranteed a SIGILL */
|
||||
ksft_test_result(seen_sigill, "sigill_%s\n",
|
||||
hwcap->name);
|
||||
} else {
|
||||
/* Missing SIGILL might be fine */
|
||||
ksft_print_msg("SIGILL %sreported for %s\n",
|
||||
seen_sigill ? "" : "not ",
|
||||
hwcap->name);
|
||||
ksft_test_result_skip("sigill_%s\n",
|
||||
hwcap->name);
|
||||
}
|
||||
} else {
|
||||
ksft_test_result_skip("sigill_%s\n",
|
||||
hwcap->name);
|
||||
}
|
||||
}
|
||||
|
||||
ksft_print_cnts();
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -112,9 +112,11 @@ static int check_fpr(struct syscall_cfg *cfg, int sve_vl, int sme_vl,
|
|||
return errors;
|
||||
}
|
||||
|
||||
#define SVE_Z_SHARED_BYTES (128 / 8)
|
||||
|
||||
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)];
|
||||
uint8_t z_in[SVE_NUM_ZREGS * __SVE_ZREG_SIZE(SVE_VQ_MAX)];
|
||||
uint8_t z_out[SVE_NUM_ZREGS * __SVE_ZREG_SIZE(SVE_VQ_MAX)];
|
||||
|
||||
static void setup_z(struct syscall_cfg *cfg, int sve_vl, int sme_vl,
|
||||
uint64_t svcr)
|
||||
|
@ -133,22 +135,39 @@ static int check_z(struct syscall_cfg *cfg, int sve_vl, int sme_vl,
|
|||
if (!sve_vl)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* After a syscall the low 128 bits of the Z registers should
|
||||
* 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];
|
||||
uint8_t *in = &z_in[reg_size * i];
|
||||
uint8_t *out = &z_out[reg_size * i];
|
||||
|
||||
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++;
|
||||
if (svcr & SVCR_SM_MASK) {
|
||||
/*
|
||||
* In streaming mode the whole register should
|
||||
* be cleared by the transition out of
|
||||
* streaming mode.
|
||||
*/
|
||||
if (memcmp(z_zero, out, reg_size) != 0) {
|
||||
ksft_print_msg("%s SVE VL %d Z%d non-zero\n",
|
||||
cfg->name, sve_vl, i);
|
||||
errors++;
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* For standard SVE the low 128 bits should be
|
||||
* preserved and any additional bits cleared.
|
||||
*/
|
||||
if (memcmp(in, out, SVE_Z_SHARED_BYTES) != 0) {
|
||||
ksft_print_msg("%s SVE VL %d Z%d low 128 bits changed\n",
|
||||
cfg->name, sve_vl, i);
|
||||
errors++;
|
||||
}
|
||||
|
||||
if (reg_size > SVE_Z_SHARED_BYTES &&
|
||||
(memcmp(z_zero, out + SVE_Z_SHARED_BYTES,
|
||||
reg_size - SVE_Z_SHARED_BYTES) != 0)) {
|
||||
ksft_print_msg("%s SVE VL %d Z%d high bits non-zero\n",
|
||||
cfg->name, sve_vl, i);
|
||||
errors++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -176,9 +195,9 @@ static int check_p(struct syscall_cfg *cfg, int sve_vl, int sme_vl,
|
|||
if (!sve_vl)
|
||||
return 0;
|
||||
|
||||
/* After a syscall the P registers should be preserved or zeroed */
|
||||
/* After a syscall the P registers should be zeroed */
|
||||
for (i = 0; i < SVE_NUM_PREGS * reg_size; i++)
|
||||
if (p_out[i] && (p_in[i] != p_out[i]))
|
||||
if (p_out[i])
|
||||
errors++;
|
||||
if (errors)
|
||||
ksft_print_msg("%s SVE VL %d predicate registers non-zero\n",
|
||||
|
@ -226,9 +245,9 @@ static int check_ffr(struct syscall_cfg *cfg, int sve_vl, int sme_vl,
|
|||
!(getauxval(AT_HWCAP2) & HWCAP2_SME_FA64))
|
||||
return 0;
|
||||
|
||||
/* After a syscall the P registers should be preserved or zeroed */
|
||||
/* After a syscall FFR should be zeroed */
|
||||
for (i = 0; i < reg_size; i++)
|
||||
if (ffr_out[i] && (ffr_in[i] != ffr_out[i]))
|
||||
if (ffr_out[i])
|
||||
errors++;
|
||||
if (errors)
|
||||
ksft_print_msg("%s SVE VL %d FFR non-zero\n",
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
fp-pidbench
|
||||
fp-stress
|
||||
fpsimd-test
|
||||
rdvl-sme
|
||||
rdvl-sve
|
||||
|
|
|
@ -5,7 +5,10 @@ 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 := fp-stress \
|
||||
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 \
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#define sa_handler 0
|
||||
#define sa_mask_sz 8
|
||||
#define SIGUSR1 10
|
||||
#define SIGUSR2 12
|
||||
#define SIGTERM 15
|
||||
#define SIGINT 2
|
||||
#define SIGABRT 6
|
||||
|
|
|
@ -0,0 +1,555 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (C) 2022 ARM Limited.
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#define _POSIX_C_SOURCE 199309L
|
||||
|
||||
#include <errno.h>
|
||||
#include <getopt.h>
|
||||
#include <poll.h>
|
||||
#include <signal.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/epoll.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/uio.h>
|
||||
#include <sys/wait.h>
|
||||
#include <asm/hwcap.h>
|
||||
|
||||
#include "../../kselftest.h"
|
||||
|
||||
#define MAX_VLS 16
|
||||
|
||||
struct child_data {
|
||||
char *name, *output;
|
||||
pid_t pid;
|
||||
int stdout;
|
||||
bool output_seen;
|
||||
bool exited;
|
||||
int exit_status;
|
||||
};
|
||||
|
||||
static int epoll_fd;
|
||||
static struct child_data *children;
|
||||
static int num_children;
|
||||
static bool terminate;
|
||||
|
||||
static void drain_output(bool flush);
|
||||
|
||||
static int num_processors(void)
|
||||
{
|
||||
long nproc = sysconf(_SC_NPROCESSORS_CONF);
|
||||
if (nproc < 0) {
|
||||
perror("Unable to read number of processors\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
return nproc;
|
||||
}
|
||||
|
||||
static void child_start(struct child_data *child, const char *program)
|
||||
{
|
||||
int ret, pipefd[2], i;
|
||||
struct epoll_event ev;
|
||||
|
||||
ret = pipe(pipefd);
|
||||
if (ret != 0)
|
||||
ksft_exit_fail_msg("Failed to create stdout pipe: %s (%d)\n",
|
||||
strerror(errno), errno);
|
||||
|
||||
child->pid = fork();
|
||||
if (child->pid == -1)
|
||||
ksft_exit_fail_msg("fork() failed: %s (%d)\n",
|
||||
strerror(errno), errno);
|
||||
|
||||
if (!child->pid) {
|
||||
/*
|
||||
* In child, replace stdout with the pipe, errors to
|
||||
* stderr from here as kselftest prints to stdout.
|
||||
*/
|
||||
ret = dup2(pipefd[1], 1);
|
||||
if (ret == -1) {
|
||||
fprintf(stderr, "dup2() %d\n", errno);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/*
|
||||
* Very dumb mechanism to clean open FDs other than
|
||||
* stdio. We don't want O_CLOEXEC for the pipes...
|
||||
*/
|
||||
for (i = 3; i < 8192; i++)
|
||||
close(i);
|
||||
|
||||
ret = execl(program, program, NULL);
|
||||
fprintf(stderr, "execl(%s) failed: %d (%s)\n",
|
||||
program, errno, strerror(errno));
|
||||
|
||||
exit(EXIT_FAILURE);
|
||||
} else {
|
||||
/*
|
||||
* In parent, remember the child and close our copy of the
|
||||
* write side of stdout.
|
||||
*/
|
||||
close(pipefd[1]);
|
||||
child->stdout = pipefd[0];
|
||||
child->output = NULL;
|
||||
child->exited = false;
|
||||
child->output_seen = false;
|
||||
|
||||
ev.events = EPOLLIN | EPOLLHUP;
|
||||
ev.data.ptr = child;
|
||||
|
||||
ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, child->stdout, &ev);
|
||||
if (ret < 0) {
|
||||
ksft_exit_fail_msg("%s EPOLL_CTL_ADD failed: %s (%d)\n",
|
||||
child->name, strerror(errno), errno);
|
||||
}
|
||||
|
||||
/*
|
||||
* Keep output flowing during child startup so logs
|
||||
* are more timely, can help debugging.
|
||||
*/
|
||||
drain_output(false);
|
||||
}
|
||||
}
|
||||
|
||||
static bool child_output_read(struct child_data *child)
|
||||
{
|
||||
char read_data[1024];
|
||||
char work[1024];
|
||||
int ret, len, cur_work, cur_read;
|
||||
|
||||
ret = read(child->stdout, read_data, sizeof(read_data));
|
||||
if (ret < 0) {
|
||||
if (errno == EINTR)
|
||||
return true;
|
||||
|
||||
ksft_print_msg("%s: read() failed: %s (%d)\n",
|
||||
child->name, strerror(errno),
|
||||
errno);
|
||||
return false;
|
||||
}
|
||||
len = ret;
|
||||
|
||||
child->output_seen = true;
|
||||
|
||||
/* Pick up any partial read */
|
||||
if (child->output) {
|
||||
strncpy(work, child->output, sizeof(work) - 1);
|
||||
cur_work = strnlen(work, sizeof(work));
|
||||
free(child->output);
|
||||
child->output = NULL;
|
||||
} else {
|
||||
cur_work = 0;
|
||||
}
|
||||
|
||||
cur_read = 0;
|
||||
while (cur_read < len) {
|
||||
work[cur_work] = read_data[cur_read++];
|
||||
|
||||
if (work[cur_work] == '\n') {
|
||||
work[cur_work] = '\0';
|
||||
ksft_print_msg("%s: %s\n", child->name, work);
|
||||
cur_work = 0;
|
||||
} else {
|
||||
cur_work++;
|
||||
}
|
||||
}
|
||||
|
||||
if (cur_work) {
|
||||
work[cur_work] = '\0';
|
||||
ret = asprintf(&child->output, "%s", work);
|
||||
if (ret == -1)
|
||||
ksft_exit_fail_msg("Out of memory\n");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void child_output(struct child_data *child, uint32_t events,
|
||||
bool flush)
|
||||
{
|
||||
bool read_more;
|
||||
|
||||
if (events & EPOLLIN) {
|
||||
do {
|
||||
read_more = child_output_read(child);
|
||||
} while (read_more);
|
||||
}
|
||||
|
||||
if (events & EPOLLHUP) {
|
||||
close(child->stdout);
|
||||
child->stdout = -1;
|
||||
flush = true;
|
||||
}
|
||||
|
||||
if (flush && child->output) {
|
||||
ksft_print_msg("%s: %s<EOF>\n", child->name, child->output);
|
||||
free(child->output);
|
||||
child->output = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void child_tickle(struct child_data *child)
|
||||
{
|
||||
if (child->output_seen && !child->exited)
|
||||
kill(child->pid, SIGUSR2);
|
||||
}
|
||||
|
||||
static void child_stop(struct child_data *child)
|
||||
{
|
||||
if (!child->exited)
|
||||
kill(child->pid, SIGTERM);
|
||||
}
|
||||
|
||||
static void child_cleanup(struct child_data *child)
|
||||
{
|
||||
pid_t ret;
|
||||
int status;
|
||||
bool fail = false;
|
||||
|
||||
if (!child->exited) {
|
||||
do {
|
||||
ret = waitpid(child->pid, &status, 0);
|
||||
if (ret == -1 && errno == EINTR)
|
||||
continue;
|
||||
|
||||
if (ret == -1) {
|
||||
ksft_print_msg("waitpid(%d) failed: %s (%d)\n",
|
||||
child->pid, strerror(errno),
|
||||
errno);
|
||||
fail = true;
|
||||
break;
|
||||
}
|
||||
} while (!WIFEXITED(status));
|
||||
child->exit_status = WEXITSTATUS(status);
|
||||
}
|
||||
|
||||
if (!child->output_seen) {
|
||||
ksft_print_msg("%s no output seen\n", child->name);
|
||||
fail = true;
|
||||
}
|
||||
|
||||
if (child->exit_status != 0) {
|
||||
ksft_print_msg("%s exited with error code %d\n",
|
||||
child->name, child->exit_status);
|
||||
fail = true;
|
||||
}
|
||||
|
||||
ksft_test_result(!fail, "%s\n", child->name);
|
||||
}
|
||||
|
||||
static void handle_child_signal(int sig, siginfo_t *info, void *context)
|
||||
{
|
||||
int i;
|
||||
bool found = false;
|
||||
|
||||
for (i = 0; i < num_children; i++) {
|
||||
if (children[i].pid == info->si_pid) {
|
||||
children[i].exited = true;
|
||||
children[i].exit_status = info->si_status;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
ksft_print_msg("SIGCHLD for unknown PID %d with status %d\n",
|
||||
info->si_pid, info->si_status);
|
||||
}
|
||||
|
||||
static void handle_exit_signal(int sig, siginfo_t *info, void *context)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* If we're already exiting then don't signal again */
|
||||
if (terminate)
|
||||
return;
|
||||
|
||||
ksft_print_msg("Got signal, exiting...\n");
|
||||
|
||||
terminate = true;
|
||||
|
||||
/*
|
||||
* This should be redundant, the main loop should clean up
|
||||
* after us, but for safety stop everything we can here.
|
||||
*/
|
||||
for (i = 0; i < num_children; i++)
|
||||
child_stop(&children[i]);
|
||||
}
|
||||
|
||||
static void start_fpsimd(struct child_data *child, int cpu, int copy)
|
||||
{
|
||||
int ret;
|
||||
|
||||
child_start(child, "./fpsimd-test");
|
||||
|
||||
ret = asprintf(&child->name, "FPSIMD-%d-%d", cpu, copy);
|
||||
if (ret == -1)
|
||||
ksft_exit_fail_msg("asprintf() failed\n");
|
||||
|
||||
ksft_print_msg("Started %s\n", child->name);
|
||||
}
|
||||
|
||||
static void start_sve(struct child_data *child, int vl, int cpu)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = prctl(PR_SVE_SET_VL, vl | PR_SVE_VL_INHERIT);
|
||||
if (ret < 0)
|
||||
ksft_exit_fail_msg("Failed to set SVE VL %d\n", vl);
|
||||
|
||||
child_start(child, "./sve-test");
|
||||
|
||||
ret = asprintf(&child->name, "SVE-VL-%d-%d", vl, cpu);
|
||||
if (ret == -1)
|
||||
ksft_exit_fail_msg("asprintf() failed\n");
|
||||
|
||||
ksft_print_msg("Started %s\n", child->name);
|
||||
}
|
||||
|
||||
static void start_ssve(struct child_data *child, int vl, int cpu)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = prctl(PR_SME_SET_VL, vl | PR_SME_VL_INHERIT);
|
||||
if (ret < 0)
|
||||
ksft_exit_fail_msg("Failed to set SME VL %d\n", ret);
|
||||
|
||||
child_start(child, "./ssve-test");
|
||||
|
||||
ret = asprintf(&child->name, "SSVE-VL-%d-%d", vl, cpu);
|
||||
if (ret == -1)
|
||||
ksft_exit_fail_msg("asprintf() failed\n");
|
||||
|
||||
ksft_print_msg("Started %s\n", child->name);
|
||||
}
|
||||
|
||||
static void start_za(struct child_data *child, int vl, int cpu)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = prctl(PR_SME_SET_VL, vl | PR_SVE_VL_INHERIT);
|
||||
if (ret < 0)
|
||||
ksft_exit_fail_msg("Failed to set SME VL %d\n", ret);
|
||||
|
||||
child_start(child, "./za-test");
|
||||
|
||||
ret = asprintf(&child->name, "ZA-VL-%d-%d", vl, cpu);
|
||||
if (ret == -1)
|
||||
ksft_exit_fail_msg("asprintf() failed\n");
|
||||
|
||||
ksft_print_msg("Started %s\n", child->name);
|
||||
}
|
||||
|
||||
static void probe_vls(int vls[], int *vl_count, int set_vl)
|
||||
{
|
||||
unsigned int vq;
|
||||
int vl;
|
||||
|
||||
*vl_count = 0;
|
||||
|
||||
for (vq = SVE_VQ_MAX; vq > 0; --vq) {
|
||||
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;
|
||||
|
||||
vq = sve_vq_from_vl(vl);
|
||||
|
||||
vls[*vl_count] = vl;
|
||||
*vl_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Handle any pending output without blocking */
|
||||
static void drain_output(bool flush)
|
||||
{
|
||||
struct epoll_event ev;
|
||||
int ret = 1;
|
||||
|
||||
while (ret > 0) {
|
||||
ret = epoll_wait(epoll_fd, &ev, 1, 0);
|
||||
if (ret < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
ksft_print_msg("epoll_wait() failed: %s (%d)\n",
|
||||
strerror(errno), errno);
|
||||
}
|
||||
|
||||
if (ret == 1)
|
||||
child_output(ev.data.ptr, ev.events, flush);
|
||||
}
|
||||
}
|
||||
|
||||
static const struct option options[] = {
|
||||
{ "timeout", required_argument, NULL, 't' },
|
||||
{ }
|
||||
};
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int ret;
|
||||
int timeout = 10;
|
||||
int cpus, tests, i, j, c;
|
||||
int sve_vl_count, sme_vl_count, fpsimd_per_cpu;
|
||||
int sve_vls[MAX_VLS], sme_vls[MAX_VLS];
|
||||
struct epoll_event ev;
|
||||
struct sigaction sa;
|
||||
|
||||
while ((c = getopt_long(argc, argv, "t:", options, NULL)) != -1) {
|
||||
switch (c) {
|
||||
case 't':
|
||||
ret = sscanf(optarg, "%d", &timeout);
|
||||
if (ret != 1)
|
||||
ksft_exit_fail_msg("Failed to parse timeout %s\n",
|
||||
optarg);
|
||||
break;
|
||||
default:
|
||||
ksft_exit_fail_msg("Unknown argument\n");
|
||||
}
|
||||
}
|
||||
|
||||
cpus = num_processors();
|
||||
tests = 0;
|
||||
|
||||
if (getauxval(AT_HWCAP) & HWCAP_SVE) {
|
||||
probe_vls(sve_vls, &sve_vl_count, PR_SVE_SET_VL);
|
||||
tests += sve_vl_count * cpus;
|
||||
} else {
|
||||
sve_vl_count = 0;
|
||||
}
|
||||
|
||||
if (getauxval(AT_HWCAP2) & HWCAP2_SME) {
|
||||
probe_vls(sme_vls, &sme_vl_count, PR_SME_SET_VL);
|
||||
tests += sme_vl_count * cpus * 2;
|
||||
} else {
|
||||
sme_vl_count = 0;
|
||||
}
|
||||
|
||||
/* Force context switching if we only have FPSIMD */
|
||||
if (!sve_vl_count && !sme_vl_count)
|
||||
fpsimd_per_cpu = 2;
|
||||
else
|
||||
fpsimd_per_cpu = 1;
|
||||
tests += cpus * fpsimd_per_cpu;
|
||||
|
||||
ksft_print_header();
|
||||
ksft_set_plan(tests);
|
||||
|
||||
ksft_print_msg("%d CPUs, %d SVE VLs, %d SME VLs\n",
|
||||
cpus, sve_vl_count, sme_vl_count);
|
||||
|
||||
if (timeout > 0)
|
||||
ksft_print_msg("Will run for %ds\n", timeout);
|
||||
else
|
||||
ksft_print_msg("Will run until terminated\n");
|
||||
|
||||
children = calloc(sizeof(*children), tests);
|
||||
if (!children)
|
||||
ksft_exit_fail_msg("Unable to allocate child data\n");
|
||||
|
||||
ret = epoll_create1(EPOLL_CLOEXEC);
|
||||
if (ret < 0)
|
||||
ksft_exit_fail_msg("epoll_create1() failed: %s (%d)\n",
|
||||
strerror(errno), ret);
|
||||
epoll_fd = ret;
|
||||
|
||||
/* Get signal handers ready before we start any children */
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa.sa_sigaction = handle_exit_signal;
|
||||
sa.sa_flags = SA_RESTART | SA_SIGINFO;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
ret = sigaction(SIGINT, &sa, NULL);
|
||||
if (ret < 0)
|
||||
ksft_print_msg("Failed to install SIGINT handler: %s (%d)\n",
|
||||
strerror(errno), errno);
|
||||
ret = sigaction(SIGTERM, &sa, NULL);
|
||||
if (ret < 0)
|
||||
ksft_print_msg("Failed to install SIGTERM handler: %s (%d)\n",
|
||||
strerror(errno), errno);
|
||||
sa.sa_sigaction = handle_child_signal;
|
||||
ret = sigaction(SIGCHLD, &sa, NULL);
|
||||
if (ret < 0)
|
||||
ksft_print_msg("Failed to install SIGCHLD handler: %s (%d)\n",
|
||||
strerror(errno), errno);
|
||||
|
||||
for (i = 0; i < cpus; i++) {
|
||||
for (j = 0; j < fpsimd_per_cpu; j++)
|
||||
start_fpsimd(&children[num_children++], i, j);
|
||||
|
||||
for (j = 0; j < sve_vl_count; j++)
|
||||
start_sve(&children[num_children++], sve_vls[j], i);
|
||||
|
||||
for (j = 0; j < sme_vl_count; j++) {
|
||||
start_ssve(&children[num_children++], sme_vls[j], i);
|
||||
start_za(&children[num_children++], sme_vls[j], i);
|
||||
}
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
/* Did we get a signal asking us to exit? */
|
||||
if (terminate)
|
||||
break;
|
||||
|
||||
/*
|
||||
* Timeout is counted in seconds with no output, the
|
||||
* tests print during startup then are silent when
|
||||
* running so this should ensure they all ran enough
|
||||
* to install the signal handler, this is especially
|
||||
* useful in emulation where we will both be slow and
|
||||
* likely to have a large set of VLs.
|
||||
*/
|
||||
ret = epoll_wait(epoll_fd, &ev, 1, 1000);
|
||||
if (ret < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
ksft_exit_fail_msg("epoll_wait() failed: %s (%d)\n",
|
||||
strerror(errno), errno);
|
||||
}
|
||||
|
||||
/* Output? */
|
||||
if (ret == 1) {
|
||||
child_output(ev.data.ptr, ev.events, false);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Otherwise epoll_wait() timed out */
|
||||
|
||||
for (i = 0; i < num_children; i++)
|
||||
child_tickle(&children[i]);
|
||||
|
||||
/* Negative timeout means run indefinitely */
|
||||
if (timeout < 0)
|
||||
continue;
|
||||
if (--timeout == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
ksft_print_msg("Finishing up...\n");
|
||||
terminate = true;
|
||||
|
||||
for (i = 0; i < tests; i++)
|
||||
child_stop(&children[i]);
|
||||
|
||||
drain_output(false);
|
||||
|
||||
for (i = 0; i < tests; i++)
|
||||
child_cleanup(&children[i]);
|
||||
|
||||
drain_output(true);
|
||||
|
||||
ksft_print_cnts();
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -151,6 +151,15 @@ function irritator_handler
|
|||
ret
|
||||
endfunction
|
||||
|
||||
function tickle_handler
|
||||
// Increment the signal count (x23):
|
||||
ldr x0, [x2, #ucontext_regs + 8 * 23]
|
||||
add x0, x0, #1
|
||||
str x0, [x2, #ucontext_regs + 8 * 23]
|
||||
|
||||
ret
|
||||
endfunction
|
||||
|
||||
function terminate_handler
|
||||
mov w21, w0
|
||||
mov x20, x2
|
||||
|
@ -207,6 +216,30 @@ endfunction
|
|||
.globl _start
|
||||
function _start
|
||||
_start:
|
||||
mov x23, #0 // 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 w0, #SIGUSR2
|
||||
adr x1, tickle_handler
|
||||
mov w2, #SA_SIGINFO
|
||||
orr w2, w2, #SA_NODEFER
|
||||
bl setsignal
|
||||
|
||||
// Sanity-check and report the vector length
|
||||
|
||||
mov x19, #128
|
||||
|
@ -237,24 +270,6 @@ _start:
|
|||
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:
|
||||
|
||||
|
|
|
@ -314,6 +314,15 @@ function irritator_handler
|
|||
ret
|
||||
endfunction
|
||||
|
||||
function tickle_handler
|
||||
// Increment the signal count (x23):
|
||||
ldr x0, [x2, #ucontext_regs + 8 * 23]
|
||||
add x0, x0, #1
|
||||
str x0, [x2, #ucontext_regs + 8 * 23]
|
||||
|
||||
ret
|
||||
endfunction
|
||||
|
||||
function terminate_handler
|
||||
mov w21, w0
|
||||
mov x20, x2
|
||||
|
@ -370,6 +379,30 @@ endfunction
|
|||
.globl _start
|
||||
function _start
|
||||
_start:
|
||||
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 w0, #SIGUSR2
|
||||
adr x1, tickle_handler
|
||||
mov w2, #SA_SIGINFO
|
||||
orr w2, w2, #SA_NODEFER
|
||||
bl setsignal
|
||||
|
||||
#ifdef SSVE
|
||||
puts "Streaming mode "
|
||||
smstart_sm
|
||||
|
@ -405,24 +438,6 @@ _start:
|
|||
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
|
||||
|
||||
#ifdef SSVE
|
||||
smstart_sm // syscalls will have exited streaming mode
|
||||
#endif
|
||||
|
|
|
@ -167,6 +167,15 @@ function irritator_handler
|
|||
ret
|
||||
endfunction
|
||||
|
||||
function tickle_handler
|
||||
// Increment the signal count (x23):
|
||||
ldr x0, [x2, #ucontext_regs + 8 * 23]
|
||||
add x0, x0, #1
|
||||
str x0, [x2, #ucontext_regs + 8 * 23]
|
||||
|
||||
ret
|
||||
endfunction
|
||||
|
||||
function terminate_handler
|
||||
mov w21, w0
|
||||
mov x20, x2
|
||||
|
@ -223,6 +232,30 @@ endfunction
|
|||
.globl _start
|
||||
function _start
|
||||
_start:
|
||||
mov x23, #0 // 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 w0, #SIGUSR2
|
||||
adr x1, tickle_handler
|
||||
mov w2, #SA_SIGINFO
|
||||
orr w2, w2, #SA_NODEFER
|
||||
bl setsignal
|
||||
|
||||
puts "Streaming mode "
|
||||
smstart_za
|
||||
|
||||
|
@ -255,24 +288,6 @@ _start:
|
|||
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
|
||||
|
@ -287,12 +302,7 @@ _start:
|
|||
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
|
||||
mov x8, #__NR_sched_yield // encourage preemption
|
||||
1:
|
||||
svc #0
|
||||
|
||||
|
|
|
@ -11,11 +11,8 @@ LDFLAGS += -pthread
|
|||
SRCS := $(filter-out mte_common_util.c,$(wildcard *.c))
|
||||
PROGS := $(patsubst %.c,%,$(SRCS))
|
||||
|
||||
#Add mte compiler option
|
||||
CFLAGS += -march=armv8.5-a+memtag
|
||||
|
||||
#check if the compiler works well
|
||||
mte_cc_support := $(shell if ($(CC) $(CFLAGS) -E -x c /dev/null -o /dev/null 2>&1) then echo "1"; fi)
|
||||
mte_cc_support := $(shell if ($(CC) $(CFLAGS) -march=armv8.5-a+memtag -E -x c /dev/null -o /dev/null 2>&1) then echo "1"; fi)
|
||||
|
||||
ifeq ($(mte_cc_support),1)
|
||||
# Generated binaries to be installed by top KSFT script
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
#include "mte_def.h"
|
||||
|
||||
.arch armv8.5-a+memtag
|
||||
|
||||
#define ENTRY(name) \
|
||||
.globl name ;\
|
||||
.p2align 2;\
|
||||
|
|
|
@ -165,15 +165,64 @@ static bool handle_signal_ok(struct tdescr *td,
|
|||
}
|
||||
|
||||
static bool handle_signal_copyctx(struct tdescr *td,
|
||||
siginfo_t *si, void *uc)
|
||||
siginfo_t *si, void *uc_in)
|
||||
{
|
||||
ucontext_t *uc = uc_in;
|
||||
struct _aarch64_ctx *head;
|
||||
struct extra_context *extra, *copied_extra;
|
||||
size_t offset = 0;
|
||||
size_t to_copy;
|
||||
|
||||
ASSERT_GOOD_CONTEXT(uc);
|
||||
|
||||
/* Mangling PC to avoid loops on original BRK instr */
|
||||
((ucontext_t *)uc)->uc_mcontext.pc += 4;
|
||||
memcpy(td->live_uc, uc, td->live_sz);
|
||||
ASSERT_GOOD_CONTEXT(td->live_uc);
|
||||
uc->uc_mcontext.pc += 4;
|
||||
|
||||
/*
|
||||
* Check for an preserve any extra data too with fixups.
|
||||
*/
|
||||
head = (struct _aarch64_ctx *)uc->uc_mcontext.__reserved;
|
||||
head = get_header(head, EXTRA_MAGIC, td->live_sz, &offset);
|
||||
if (head) {
|
||||
extra = (struct extra_context *)head;
|
||||
|
||||
/*
|
||||
* The extra buffer must be immediately after the
|
||||
* extra_context and a 16 byte terminator. Include it
|
||||
* in the copy, this was previously validated in
|
||||
* ASSERT_GOOD_CONTEXT().
|
||||
*/
|
||||
to_copy = offset + sizeof(struct extra_context) + 16 +
|
||||
extra->size;
|
||||
copied_extra = (struct extra_context *)&(td->live_uc->uc_mcontext.__reserved[offset]);
|
||||
} else {
|
||||
copied_extra = NULL;
|
||||
to_copy = sizeof(ucontext_t);
|
||||
}
|
||||
|
||||
if (to_copy > td->live_sz) {
|
||||
fprintf(stderr,
|
||||
"Not enough space to grab context, %lu/%lu bytes\n",
|
||||
td->live_sz, to_copy);
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(td->live_uc, uc, to_copy);
|
||||
|
||||
/*
|
||||
* If there was any EXTRA_CONTEXT fix up the size to be the
|
||||
* struct extra_context and the following terminator record,
|
||||
* this means that the rest of the code does not need to have
|
||||
* special handling for the record and we don't need to fix up
|
||||
* datap for the new location.
|
||||
*/
|
||||
if (copied_extra)
|
||||
copied_extra->head.size = sizeof(*copied_extra) + 16;
|
||||
|
||||
td->live_uc_valid = 1;
|
||||
fprintf(stderr,
|
||||
"GOOD CONTEXT grabbed from sig_copyctx handler\n");
|
||||
"%lu byte GOOD CONTEXT grabbed from sig_copyctx handler\n",
|
||||
to_copy);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -56,7 +56,8 @@ static inline bool feats_ok(struct tdescr *td)
|
|||
* at sizeof(ucontext_t).
|
||||
*/
|
||||
static __always_inline bool get_current_context(struct tdescr *td,
|
||||
ucontext_t *dest_uc)
|
||||
ucontext_t *dest_uc,
|
||||
size_t dest_sz)
|
||||
{
|
||||
static volatile bool seen_already;
|
||||
|
||||
|
@ -64,7 +65,7 @@ static __always_inline bool get_current_context(struct tdescr *td,
|
|||
/* it's a genuine invocation..reinit */
|
||||
seen_already = 0;
|
||||
td->live_uc_valid = 0;
|
||||
td->live_sz = sizeof(*dest_uc);
|
||||
td->live_sz = dest_sz;
|
||||
memset(dest_uc, 0x00, td->live_sz);
|
||||
td->live_uc = dest_uc;
|
||||
/*
|
||||
|
|
|
@ -21,7 +21,7 @@ static int fake_sigreturn_bad_magic_run(struct tdescr *td,
|
|||
struct _aarch64_ctx *shead = GET_SF_RESV_HEAD(sf), *head;
|
||||
|
||||
/* just to fill the ucontext_t with something real */
|
||||
if (!get_current_context(td, &sf.uc))
|
||||
if (!get_current_context(td, &sf.uc, sizeof(sf.uc)))
|
||||
return 1;
|
||||
|
||||
/* need at least 2*HDR_SZ space: KSFT_BAD_MAGIC + terminator. */
|
||||
|
|
|
@ -24,7 +24,7 @@ static int fake_sigreturn_bad_size_run(struct tdescr *td,
|
|||
struct _aarch64_ctx *shead = GET_SF_RESV_HEAD(sf), *head;
|
||||
|
||||
/* just to fill the ucontext_t with something real */
|
||||
if (!get_current_context(td, &sf.uc))
|
||||
if (!get_current_context(td, &sf.uc, sizeof(sf.uc)))
|
||||
return 1;
|
||||
|
||||
resv_sz = GET_SF_RESV_SIZE(sf);
|
||||
|
|
|
@ -21,7 +21,7 @@ static int fake_sigreturn_bad_size_for_magic0_run(struct tdescr *td,
|
|||
struct _aarch64_ctx *shead = GET_SF_RESV_HEAD(sf), *head;
|
||||
|
||||
/* just to fill the ucontext_t with something real */
|
||||
if (!get_current_context(td, &sf.uc))
|
||||
if (!get_current_context(td, &sf.uc, sizeof(sf.uc)))
|
||||
return 1;
|
||||
|
||||
/* at least HDR_SZ for the badly sized terminator. */
|
||||
|
|
|
@ -21,7 +21,7 @@ static int fake_sigreturn_duplicated_fpsimd_run(struct tdescr *td,
|
|||
struct _aarch64_ctx *shead = GET_SF_RESV_HEAD(sf), *head;
|
||||
|
||||
/* just to fill the ucontext_t with something real */
|
||||
if (!get_current_context(td, &sf.uc))
|
||||
if (!get_current_context(td, &sf.uc, sizeof(sf.uc)))
|
||||
return 1;
|
||||
|
||||
head = get_starting_head(shead, sizeof(struct fpsimd_context) + HDR_SZ,
|
||||
|
|
|
@ -19,7 +19,7 @@ static int fake_sigreturn_misaligned_run(struct tdescr *td,
|
|||
siginfo_t *si, ucontext_t *uc)
|
||||
{
|
||||
/* just to fill the ucontext_t with something real */
|
||||
if (!get_current_context(td, &sf.uc))
|
||||
if (!get_current_context(td, &sf.uc, sizeof(sf.uc)))
|
||||
return 1;
|
||||
|
||||
/* Forcing sigframe on misaligned SP (16 + 3) */
|
||||
|
|
|
@ -23,7 +23,7 @@ static int fake_sigreturn_missing_fpsimd_run(struct tdescr *td,
|
|||
struct _aarch64_ctx *head = GET_SF_RESV_HEAD(sf);
|
||||
|
||||
/* just to fill the ucontext_t with something real */
|
||||
if (!get_current_context(td, &sf.uc))
|
||||
if (!get_current_context(td, &sf.uc, sizeof(sf.uc)))
|
||||
return 1;
|
||||
|
||||
resv_sz = GET_SF_RESV_SIZE(sf);
|
||||
|
|
|
@ -54,7 +54,7 @@ static int fake_sigreturn_ssve_change_vl(struct tdescr *td,
|
|||
struct sve_context *sve;
|
||||
|
||||
/* Get a signal context with a SME ZA frame in it */
|
||||
if (!get_current_context(td, &sf.uc))
|
||||
if (!get_current_context(td, &sf.uc, sizeof(sf.uc)))
|
||||
return 1;
|
||||
|
||||
resv_sz = GET_SF_RESV_SIZE(sf);
|
||||
|
|
|
@ -56,7 +56,7 @@ static int fake_sigreturn_sve_change_vl(struct tdescr *td,
|
|||
struct sve_context *sve;
|
||||
|
||||
/* Get a signal context with a SVE frame in it */
|
||||
if (!get_current_context(td, &sf.uc))
|
||||
if (!get_current_context(td, &sf.uc, sizeof(sf.uc)))
|
||||
return 1;
|
||||
|
||||
resv_sz = GET_SF_RESV_SIZE(sf);
|
||||
|
|
|
@ -34,7 +34,7 @@ static int sme_vl(struct tdescr *td, siginfo_t *si, ucontext_t *uc)
|
|||
struct za_context *za;
|
||||
|
||||
/* Get a signal context which should have a ZA frame in it */
|
||||
if (!get_current_context(td, &sf.uc))
|
||||
if (!get_current_context(td, &sf.uc, sizeof(sf.uc)))
|
||||
return 1;
|
||||
|
||||
resv_sz = GET_SF_RESV_SIZE(sf);
|
||||
|
|
|
@ -13,7 +13,10 @@
|
|||
#include "test_signals_utils.h"
|
||||
#include "testcases.h"
|
||||
|
||||
struct fake_sigframe sf;
|
||||
static union {
|
||||
ucontext_t uc;
|
||||
char buf[1024 * 64];
|
||||
} context;
|
||||
static unsigned int vls[SVE_VQ_MAX];
|
||||
unsigned int nvls = 0;
|
||||
|
||||
|
@ -55,8 +58,8 @@ static void setup_ssve_regs(void)
|
|||
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);
|
||||
size_t offset;
|
||||
struct _aarch64_ctx *head = GET_BUF_RESV_HEAD(context);
|
||||
struct sve_context *ssve;
|
||||
int ret;
|
||||
|
||||
|
@ -73,11 +76,11 @@ static int do_one_sme_vl(struct tdescr *td, siginfo_t *si, ucontext_t *uc,
|
|||
* in it.
|
||||
*/
|
||||
setup_ssve_regs();
|
||||
if (!get_current_context(td, &sf.uc))
|
||||
if (!get_current_context(td, &context.uc, sizeof(context)))
|
||||
return 1;
|
||||
|
||||
resv_sz = GET_SF_RESV_SIZE(sf);
|
||||
head = get_header(head, SVE_MAGIC, resv_sz, &offset);
|
||||
head = get_header(head, SVE_MAGIC, GET_BUF_RESV_SIZE(context),
|
||||
&offset);
|
||||
if (!head) {
|
||||
fprintf(stderr, "No SVE context\n");
|
||||
return 1;
|
||||
|
@ -101,16 +104,6 @@ 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;
|
||||
}
|
||||
|
|
|
@ -13,7 +13,10 @@
|
|||
#include "test_signals_utils.h"
|
||||
#include "testcases.h"
|
||||
|
||||
struct fake_sigframe sf;
|
||||
static union {
|
||||
ucontext_t uc;
|
||||
char buf[1024 * 64];
|
||||
} context;
|
||||
static unsigned int vls[SVE_VQ_MAX];
|
||||
unsigned int nvls = 0;
|
||||
|
||||
|
@ -55,8 +58,8 @@ static void setup_sve_regs(void)
|
|||
static int do_one_sve_vl(struct tdescr *td, siginfo_t *si, ucontext_t *uc,
|
||||
unsigned int vl)
|
||||
{
|
||||
size_t resv_sz, offset;
|
||||
struct _aarch64_ctx *head = GET_SF_RESV_HEAD(sf);
|
||||
size_t offset;
|
||||
struct _aarch64_ctx *head = GET_BUF_RESV_HEAD(context);
|
||||
struct sve_context *sve;
|
||||
|
||||
fprintf(stderr, "Testing VL %d\n", vl);
|
||||
|
@ -71,11 +74,11 @@ static int do_one_sve_vl(struct tdescr *td, siginfo_t *si, ucontext_t *uc,
|
|||
* in it.
|
||||
*/
|
||||
setup_sve_regs();
|
||||
if (!get_current_context(td, &sf.uc))
|
||||
if (!get_current_context(td, &context.uc, sizeof(context)))
|
||||
return 1;
|
||||
|
||||
resv_sz = GET_SF_RESV_SIZE(sf);
|
||||
head = get_header(head, SVE_MAGIC, resv_sz, &offset);
|
||||
head = get_header(head, SVE_MAGIC, GET_BUF_RESV_SIZE(context),
|
||||
&offset);
|
||||
if (!head) {
|
||||
fprintf(stderr, "No SVE context\n");
|
||||
return 1;
|
||||
|
@ -99,14 +102,6 @@ static int sve_regs(struct tdescr *td, siginfo_t *si, ucontext_t *uc)
|
|||
int i;
|
||||
|
||||
for (i = 0; i < nvls; i++) {
|
||||
/*
|
||||
* TODO: the signal test helpers can't currently cope
|
||||
* with signal frames bigger than struct sigcontext,
|
||||
* skip VLs that will trigger that.
|
||||
*/
|
||||
if (vls[i] > 64)
|
||||
continue;
|
||||
|
||||
if (do_one_sve_vl(td, si, uc, vls[i]))
|
||||
return 1;
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ static int sve_vl(struct tdescr *td, siginfo_t *si, ucontext_t *uc)
|
|||
struct sve_context *sve;
|
||||
|
||||
/* Get a signal context which should have a SVE frame in it */
|
||||
if (!get_current_context(td, &sf.uc))
|
||||
if (!get_current_context(td, &sf.uc, sizeof(sf.uc)))
|
||||
return 1;
|
||||
|
||||
resv_sz = GET_SF_RESV_SIZE(sf);
|
||||
|
|
|
@ -25,7 +25,8 @@ struct _aarch64_ctx *get_header(struct _aarch64_ctx *head, uint32_t magic,
|
|||
return found;
|
||||
}
|
||||
|
||||
bool validate_extra_context(struct extra_context *extra, char **err)
|
||||
bool validate_extra_context(struct extra_context *extra, char **err,
|
||||
void **extra_data, size_t *extra_size)
|
||||
{
|
||||
struct _aarch64_ctx *term;
|
||||
|
||||
|
@ -33,7 +34,7 @@ bool validate_extra_context(struct extra_context *extra, char **err)
|
|||
return false;
|
||||
|
||||
fprintf(stderr, "Validating EXTRA...\n");
|
||||
term = GET_RESV_NEXT_HEAD(extra);
|
||||
term = GET_RESV_NEXT_HEAD(&extra->head);
|
||||
if (!term || term->magic || term->size) {
|
||||
*err = "Missing terminator after EXTRA context";
|
||||
return false;
|
||||
|
@ -42,11 +43,14 @@ bool validate_extra_context(struct extra_context *extra, char **err)
|
|||
*err = "Extra DATAP misaligned";
|
||||
else if (extra->size & 0x0fUL)
|
||||
*err = "Extra SIZE misaligned";
|
||||
else if (extra->datap != (uint64_t)term + sizeof(*term))
|
||||
else if (extra->datap != (uint64_t)term + 0x10UL)
|
||||
*err = "Extra DATAP misplaced (not contiguous)";
|
||||
if (*err)
|
||||
return false;
|
||||
|
||||
*extra_data = (void *)extra->datap;
|
||||
*extra_size = extra->size;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -105,11 +109,14 @@ bool validate_reserved(ucontext_t *uc, size_t resv_sz, char **err)
|
|||
bool terminated = false;
|
||||
size_t offs = 0;
|
||||
int flags = 0;
|
||||
int new_flags;
|
||||
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;
|
||||
void *extra_data = NULL;
|
||||
size_t extra_sz = 0;
|
||||
|
||||
if (!err)
|
||||
return false;
|
||||
|
@ -120,12 +127,24 @@ bool validate_reserved(ucontext_t *uc, size_t resv_sz, char **err)
|
|||
return false;
|
||||
}
|
||||
|
||||
new_flags = 0;
|
||||
|
||||
switch (head->magic) {
|
||||
case 0:
|
||||
if (head->size)
|
||||
if (head->size) {
|
||||
*err = "Bad size for terminator";
|
||||
else
|
||||
} else if (extra_data) {
|
||||
/* End of main data, walking the extra data */
|
||||
head = extra_data;
|
||||
resv_sz = extra_sz;
|
||||
offs = 0;
|
||||
|
||||
extra_data = NULL;
|
||||
extra_sz = 0;
|
||||
continue;
|
||||
} else {
|
||||
terminated = true;
|
||||
}
|
||||
break;
|
||||
case FPSIMD_MAGIC:
|
||||
if (flags & FPSIMD_CTX)
|
||||
|
@ -133,7 +152,7 @@ bool validate_reserved(ucontext_t *uc, size_t resv_sz, char **err)
|
|||
else if (head->size !=
|
||||
sizeof(struct fpsimd_context))
|
||||
*err = "Bad size for fpsimd_context";
|
||||
flags |= FPSIMD_CTX;
|
||||
new_flags |= FPSIMD_CTX;
|
||||
break;
|
||||
case ESR_MAGIC:
|
||||
if (head->size != sizeof(struct esr_context))
|
||||
|
@ -144,14 +163,14 @@ bool validate_reserved(ucontext_t *uc, size_t resv_sz, char **err)
|
|||
*err = "Multiple SVE_MAGIC";
|
||||
/* Size is validated in validate_sve_context() */
|
||||
sve = (struct sve_context *)head;
|
||||
flags |= SVE_CTX;
|
||||
new_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;
|
||||
new_flags |= ZA_CTX;
|
||||
break;
|
||||
case EXTRA_MAGIC:
|
||||
if (flags & EXTRA_CTX)
|
||||
|
@ -159,7 +178,7 @@ bool validate_reserved(ucontext_t *uc, size_t resv_sz, char **err)
|
|||
else if (head->size !=
|
||||
sizeof(struct extra_context))
|
||||
*err = "Bad size for extra_context";
|
||||
flags |= EXTRA_CTX;
|
||||
new_flags |= EXTRA_CTX;
|
||||
extra = (struct extra_context *)head;
|
||||
break;
|
||||
case KSFT_BAD_MAGIC:
|
||||
|
@ -192,16 +211,19 @@ bool validate_reserved(ucontext_t *uc, size_t resv_sz, char **err)
|
|||
return false;
|
||||
}
|
||||
|
||||
if (flags & EXTRA_CTX)
|
||||
if (!validate_extra_context(extra, err))
|
||||
if (new_flags & EXTRA_CTX)
|
||||
if (!validate_extra_context(extra, err,
|
||||
&extra_data, &extra_sz))
|
||||
return false;
|
||||
if (flags & SVE_CTX)
|
||||
if (new_flags & SVE_CTX)
|
||||
if (!validate_sve_context(sve, err))
|
||||
return false;
|
||||
if (flags & ZA_CTX)
|
||||
if (new_flags & ZA_CTX)
|
||||
if (!validate_za_context(za, err))
|
||||
return false;
|
||||
|
||||
flags |= new_flags;
|
||||
|
||||
head = GET_RESV_NEXT_HEAD(head);
|
||||
}
|
||||
|
||||
|
|
|
@ -30,6 +30,13 @@
|
|||
#define GET_SF_RESV_SIZE(sf) \
|
||||
sizeof((sf).uc.uc_mcontext.__reserved)
|
||||
|
||||
#define GET_BUF_RESV_HEAD(buf) \
|
||||
(struct _aarch64_ctx *)(&(buf).uc.uc_mcontext.__reserved)
|
||||
|
||||
#define GET_BUF_RESV_SIZE(buf) \
|
||||
(sizeof(buf) - sizeof(buf.uc) + \
|
||||
sizeof((buf).uc.uc_mcontext.__reserved))
|
||||
|
||||
#define GET_UCP_RESV_SIZE(ucp) \
|
||||
sizeof((ucp)->uc_mcontext.__reserved)
|
||||
|
||||
|
@ -79,8 +86,6 @@ struct fake_sigframe {
|
|||
|
||||
bool validate_reserved(ucontext_t *uc, size_t resv_sz, char **err);
|
||||
|
||||
bool validate_extra_context(struct extra_context *extra, char **err);
|
||||
|
||||
struct _aarch64_ctx *get_header(struct _aarch64_ctx *head, uint32_t magic,
|
||||
size_t resv_sz, size_t *offset);
|
||||
|
||||
|
|
|
@ -0,0 +1,119 @@
|
|||
// 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"
|
||||
|
||||
static union {
|
||||
ucontext_t uc;
|
||||
char buf[1024 * 128];
|
||||
} context;
|
||||
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 SME_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 int do_one_sme_vl(struct tdescr *td, siginfo_t *si, ucontext_t *uc,
|
||||
unsigned int vl)
|
||||
{
|
||||
size_t offset;
|
||||
struct _aarch64_ctx *head = GET_BUF_RESV_HEAD(context);
|
||||
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.
|
||||
*/
|
||||
if (!get_current_context(td, &context.uc, sizeof(context)))
|
||||
return 1;
|
||||
|
||||
head = get_header(head, ZA_MAGIC, GET_BUF_RESV_SIZE(context), &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;
|
||||
}
|
||||
|
||||
if (head->size != ZA_SIG_REGS_OFFSET) {
|
||||
fprintf(stderr, "Context size %u, expected %lu\n",
|
||||
head->size, ZA_SIG_REGS_OFFSET);
|
||||
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++) {
|
||||
if (do_one_sme_vl(td, si, uc, vls[i]))
|
||||
return 1;
|
||||
}
|
||||
|
||||
td->pass = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct tdescr tde = {
|
||||
.name = "ZA registers - ZA disabled",
|
||||
.descr = "Check ZA context with ZA disabled",
|
||||
.feats_required = FEAT_SME,
|
||||
.timeout = 3,
|
||||
.init = sme_get_vls,
|
||||
.run = sme_regs,
|
||||
};
|
|
@ -13,7 +13,10 @@
|
|||
#include "test_signals_utils.h"
|
||||
#include "testcases.h"
|
||||
|
||||
struct fake_sigframe sf;
|
||||
static union {
|
||||
ucontext_t uc;
|
||||
char buf[1024 * 128];
|
||||
} context;
|
||||
static unsigned int vls[SVE_VQ_MAX];
|
||||
unsigned int nvls = 0;
|
||||
|
||||
|
@ -22,10 +25,10 @@ static bool sme_get_vls(struct tdescr *td)
|
|||
int vq, vl;
|
||||
|
||||
/*
|
||||
* Enumerate up to SVE_VQ_MAX vector lengths
|
||||
* Enumerate up to SME_VQ_MAX vector lengths
|
||||
*/
|
||||
for (vq = SVE_VQ_MAX; vq > 0; --vq) {
|
||||
vl = prctl(PR_SVE_SET_VL, vq * 16);
|
||||
vl = prctl(PR_SME_SET_VL, vq * 16);
|
||||
if (vl == -1)
|
||||
return false;
|
||||
|
||||
|
@ -52,11 +55,13 @@ static void setup_za_regs(void)
|
|||
asm volatile(".inst 0xd503457f" : : : );
|
||||
}
|
||||
|
||||
static char zeros[ZA_SIG_REGS_SIZE(SVE_VQ_MAX)];
|
||||
|
||||
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);
|
||||
size_t offset;
|
||||
struct _aarch64_ctx *head = GET_BUF_RESV_HEAD(context);
|
||||
struct za_context *za;
|
||||
|
||||
fprintf(stderr, "Testing VL %d\n", vl);
|
||||
|
@ -71,11 +76,10 @@ static int do_one_sme_vl(struct tdescr *td, siginfo_t *si, ucontext_t *uc,
|
|||
* in it.
|
||||
*/
|
||||
setup_za_regs();
|
||||
if (!get_current_context(td, &sf.uc))
|
||||
if (!get_current_context(td, &context.uc, sizeof(context)))
|
||||
return 1;
|
||||
|
||||
resv_sz = GET_SF_RESV_SIZE(sf);
|
||||
head = get_header(head, ZA_MAGIC, resv_sz, &offset);
|
||||
head = get_header(head, ZA_MAGIC, GET_BUF_RESV_SIZE(context), &offset);
|
||||
if (!head) {
|
||||
fprintf(stderr, "No ZA context\n");
|
||||
return 1;
|
||||
|
@ -87,10 +91,22 @@ static int do_one_sme_vl(struct tdescr *td, siginfo_t *si, ucontext_t *uc,
|
|||
return 1;
|
||||
}
|
||||
|
||||
/* The actual size validation is done in get_current_context() */
|
||||
if (head->size != ZA_SIG_CONTEXT_SIZE(sve_vq_from_vl(vl))) {
|
||||
fprintf(stderr, "ZA context size %u, expected %lu\n",
|
||||
head->size, ZA_SIG_CONTEXT_SIZE(sve_vq_from_vl(vl)));
|
||||
return 1;
|
||||
}
|
||||
|
||||
fprintf(stderr, "Got expected size %u and VL %d\n",
|
||||
head->size, za->vl);
|
||||
|
||||
/* We didn't load any data into ZA so it should be all zeros */
|
||||
if (memcmp(zeros, (char *)za + ZA_SIG_REGS_OFFSET,
|
||||
ZA_SIG_REGS_SIZE(sve_vq_from_vl(za->vl))) != 0) {
|
||||
fprintf(stderr, "ZA data invalid\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -99,16 +115,6 @@ 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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue