Merge branch 'for-next/selftests' into for-next/core
* for-next/selftests: (22 commits) kselftest/arm64: Fix hwcaps selftest build kselftest/arm64: add jscvt feature to hwcap test kselftest/arm64: add pmull feature to hwcap test kselftest/arm64: add AES feature check to hwcap test kselftest/arm64: add SHA1 and related features to hwcap test kselftest/arm64: build BTI tests in output directory kselftest/arm64: fix a memleak in zt_regs_run() kselftest/arm64: Size sycall-abi buffers for the actual maximum VL kselftest/arm64: add lse and lse2 features to hwcap test kselftest/arm64: add test item that support to capturing the SIGBUS signal kselftest/arm64: add DEF_SIGHANDLER_FUNC() and DEF_INST_RAISE_SIG() helpers kselftest/arm64: add crc32 feature to hwcap test kselftest/arm64: add float-point feature to hwcap test kselftest/arm64: Use the tools/include compiler.h rather than our own kselftest/arm64: Use shared OPTIMZER_HIDE_VAR() definiton kselftest/arm64: Make the tools/include headers available tools include: Add some common function attributes tools compiler.h: Add OPTIMIZER_HIDE_VAR() kselftest/arm64: Exit streaming mode after collecting signal context kselftest/arm64: add RCpc load-acquire to hwcap test ...
This commit is contained in:
commit
e1df272139
@ -42,6 +42,18 @@
|
||||
# define __always_inline inline __attribute__((always_inline))
|
||||
#endif
|
||||
|
||||
#ifndef __always_unused
|
||||
#define __always_unused __attribute__((__unused__))
|
||||
#endif
|
||||
|
||||
#ifndef __noreturn
|
||||
#define __noreturn __attribute__((__noreturn__))
|
||||
#endif
|
||||
|
||||
#ifndef unreachable
|
||||
#define unreachable() __builtin_unreachable()
|
||||
#endif
|
||||
|
||||
#ifndef noinline
|
||||
#define noinline
|
||||
#endif
|
||||
@ -190,4 +202,10 @@ static __always_inline void __write_once_size(volatile void *p, void *res, int s
|
||||
#define ___PASTE(a, b) a##b
|
||||
#define __PASTE(a, b) ___PASTE(a, b)
|
||||
|
||||
#ifndef OPTIMIZER_HIDE_VAR
|
||||
/* Make the optimizer believe the variable can be manipulated arbitrarily. */
|
||||
#define OPTIMIZER_HIDE_VAR(var) \
|
||||
__asm__ ("" : "=r" (var) : "0" (var))
|
||||
#endif
|
||||
|
||||
#endif /* _TOOLS_LINUX_COMPILER_H */
|
||||
|
@ -19,6 +19,8 @@ CFLAGS += -I$(top_srcdir)/tools/testing/selftests/
|
||||
|
||||
CFLAGS += $(KHDR_INCLUDES)
|
||||
|
||||
CFLAGS += -I$(top_srcdir)/tools/include
|
||||
|
||||
export CFLAGS
|
||||
export top_srcdir
|
||||
|
||||
|
@ -19,19 +19,38 @@
|
||||
|
||||
#include "../../kselftest.h"
|
||||
|
||||
#define TESTS_PER_HWCAP 2
|
||||
#define TESTS_PER_HWCAP 3
|
||||
|
||||
/*
|
||||
* 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.
|
||||
* Function expected to generate exception when the feature is not
|
||||
* supported and return when it is supported. If the specific exception
|
||||
* 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);
|
||||
typedef void (*sig_fn)(void);
|
||||
|
||||
static void aes_sigill(void)
|
||||
{
|
||||
/* AESE V0.16B, V0.16B */
|
||||
asm volatile(".inst 0x4e284800" : : : );
|
||||
}
|
||||
|
||||
static void atomics_sigill(void)
|
||||
{
|
||||
/* STADD W0, [SP] */
|
||||
asm volatile(".inst 0xb82003ff" : : : );
|
||||
}
|
||||
|
||||
static void crc32_sigill(void)
|
||||
{
|
||||
/* CRC32W W0, W0, W1 */
|
||||
asm volatile(".inst 0x1ac14800" : : : );
|
||||
}
|
||||
|
||||
static void cssc_sigill(void)
|
||||
{
|
||||
@ -39,6 +58,29 @@ static void cssc_sigill(void)
|
||||
asm volatile(".inst 0xdac01c00" : : : "x0");
|
||||
}
|
||||
|
||||
static void fp_sigill(void)
|
||||
{
|
||||
asm volatile("fmov s0, #1");
|
||||
}
|
||||
|
||||
static void ilrcpc_sigill(void)
|
||||
{
|
||||
/* LDAPUR W0, [SP, #8] */
|
||||
asm volatile(".inst 0x994083e0" : : : );
|
||||
}
|
||||
|
||||
static void jscvt_sigill(void)
|
||||
{
|
||||
/* FJCVTZS W0, D0 */
|
||||
asm volatile(".inst 0x1e7e0000" : : : );
|
||||
}
|
||||
|
||||
static void lrcpc_sigill(void)
|
||||
{
|
||||
/* LDAPR W0, [SP, #0] */
|
||||
asm volatile(".inst 0xb8bfc3e0" : : : );
|
||||
}
|
||||
|
||||
static void mops_sigill(void)
|
||||
{
|
||||
char dst[1], src[1];
|
||||
@ -53,11 +95,35 @@ static void mops_sigill(void)
|
||||
: "cc", "memory");
|
||||
}
|
||||
|
||||
static void pmull_sigill(void)
|
||||
{
|
||||
/* PMULL V0.1Q, V0.1D, V0.1D */
|
||||
asm volatile(".inst 0x0ee0e000" : : : );
|
||||
}
|
||||
|
||||
static void rng_sigill(void)
|
||||
{
|
||||
asm volatile("mrs x0, S3_3_C2_C4_0" : : : "x0");
|
||||
}
|
||||
|
||||
static void sha1_sigill(void)
|
||||
{
|
||||
/* SHA1H S0, S0 */
|
||||
asm volatile(".inst 0x5e280800" : : : );
|
||||
}
|
||||
|
||||
static void sha2_sigill(void)
|
||||
{
|
||||
/* SHA256H Q0, Q0, V0.4S */
|
||||
asm volatile(".inst 0x5e004000" : : : );
|
||||
}
|
||||
|
||||
static void sha512_sigill(void)
|
||||
{
|
||||
/* SHA512H Q0, Q0, V0.2D */
|
||||
asm volatile(".inst 0xce608000" : : : );
|
||||
}
|
||||
|
||||
static void sme_sigill(void)
|
||||
{
|
||||
/* RDSVL x0, #0 */
|
||||
@ -215,14 +281,38 @@ static void hbc_sigill(void)
|
||||
".inst 0x54000030" : : : "cc");
|
||||
}
|
||||
|
||||
static void uscat_sigbus(void)
|
||||
{
|
||||
/* unaligned atomic access */
|
||||
asm volatile("ADD x1, sp, #2" : : : );
|
||||
/* STADD W0, [X1] */
|
||||
asm volatile(".inst 0xb820003f" : : : );
|
||||
}
|
||||
|
||||
static const struct hwcap_data {
|
||||
const char *name;
|
||||
unsigned long at_hwcap;
|
||||
unsigned long hwcap_bit;
|
||||
const char *cpuinfo;
|
||||
sigill_fn sigill_fn;
|
||||
sig_fn sigill_fn;
|
||||
bool sigill_reliable;
|
||||
sig_fn sigbus_fn;
|
||||
bool sigbus_reliable;
|
||||
} hwcaps[] = {
|
||||
{
|
||||
.name = "AES",
|
||||
.at_hwcap = AT_HWCAP,
|
||||
.hwcap_bit = HWCAP_AES,
|
||||
.cpuinfo = "aes",
|
||||
.sigill_fn = aes_sigill,
|
||||
},
|
||||
{
|
||||
.name = "CRC32",
|
||||
.at_hwcap = AT_HWCAP,
|
||||
.hwcap_bit = HWCAP_CRC32,
|
||||
.cpuinfo = "crc32",
|
||||
.sigill_fn = crc32_sigill,
|
||||
},
|
||||
{
|
||||
.name = "CSSC",
|
||||
.at_hwcap = AT_HWCAP2,
|
||||
@ -230,6 +320,50 @@ static const struct hwcap_data {
|
||||
.cpuinfo = "cssc",
|
||||
.sigill_fn = cssc_sigill,
|
||||
},
|
||||
{
|
||||
.name = "FP",
|
||||
.at_hwcap = AT_HWCAP,
|
||||
.hwcap_bit = HWCAP_FP,
|
||||
.cpuinfo = "fp",
|
||||
.sigill_fn = fp_sigill,
|
||||
},
|
||||
{
|
||||
.name = "JSCVT",
|
||||
.at_hwcap = AT_HWCAP,
|
||||
.hwcap_bit = HWCAP_JSCVT,
|
||||
.cpuinfo = "jscvt",
|
||||
.sigill_fn = jscvt_sigill,
|
||||
},
|
||||
{
|
||||
.name = "LRCPC",
|
||||
.at_hwcap = AT_HWCAP,
|
||||
.hwcap_bit = HWCAP_LRCPC,
|
||||
.cpuinfo = "lrcpc",
|
||||
.sigill_fn = lrcpc_sigill,
|
||||
},
|
||||
{
|
||||
.name = "LRCPC2",
|
||||
.at_hwcap = AT_HWCAP,
|
||||
.hwcap_bit = HWCAP_ILRCPC,
|
||||
.cpuinfo = "ilrcpc",
|
||||
.sigill_fn = ilrcpc_sigill,
|
||||
},
|
||||
{
|
||||
.name = "LSE",
|
||||
.at_hwcap = AT_HWCAP,
|
||||
.hwcap_bit = HWCAP_ATOMICS,
|
||||
.cpuinfo = "atomics",
|
||||
.sigill_fn = atomics_sigill,
|
||||
},
|
||||
{
|
||||
.name = "LSE2",
|
||||
.at_hwcap = AT_HWCAP,
|
||||
.hwcap_bit = HWCAP_USCAT,
|
||||
.cpuinfo = "uscat",
|
||||
.sigill_fn = atomics_sigill,
|
||||
.sigbus_fn = uscat_sigbus,
|
||||
.sigbus_reliable = true,
|
||||
},
|
||||
{
|
||||
.name = "MOPS",
|
||||
.at_hwcap = AT_HWCAP2,
|
||||
@ -238,6 +372,13 @@ static const struct hwcap_data {
|
||||
.sigill_fn = mops_sigill,
|
||||
.sigill_reliable = true,
|
||||
},
|
||||
{
|
||||
.name = "PMULL",
|
||||
.at_hwcap = AT_HWCAP,
|
||||
.hwcap_bit = HWCAP_PMULL,
|
||||
.cpuinfo = "pmull",
|
||||
.sigill_fn = pmull_sigill,
|
||||
},
|
||||
{
|
||||
.name = "RNG",
|
||||
.at_hwcap = AT_HWCAP2,
|
||||
@ -251,6 +392,27 @@ static const struct hwcap_data {
|
||||
.hwcap_bit = HWCAP2_RPRFM,
|
||||
.cpuinfo = "rprfm",
|
||||
},
|
||||
{
|
||||
.name = "SHA1",
|
||||
.at_hwcap = AT_HWCAP,
|
||||
.hwcap_bit = HWCAP_SHA1,
|
||||
.cpuinfo = "sha1",
|
||||
.sigill_fn = sha1_sigill,
|
||||
},
|
||||
{
|
||||
.name = "SHA2",
|
||||
.at_hwcap = AT_HWCAP,
|
||||
.hwcap_bit = HWCAP_SHA2,
|
||||
.cpuinfo = "sha2",
|
||||
.sigill_fn = sha2_sigill,
|
||||
},
|
||||
{
|
||||
.name = "SHA512",
|
||||
.at_hwcap = AT_HWCAP,
|
||||
.hwcap_bit = HWCAP_SHA512,
|
||||
.cpuinfo = "sha512",
|
||||
.sigill_fn = sha512_sigill,
|
||||
},
|
||||
{
|
||||
.name = "SME",
|
||||
.at_hwcap = AT_HWCAP2,
|
||||
@ -403,18 +565,22 @@ static const struct hwcap_data {
|
||||
},
|
||||
};
|
||||
|
||||
static bool seen_sigill;
|
||||
typedef void (*sighandler_fn)(int, siginfo_t *, void *);
|
||||
|
||||
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;
|
||||
#define DEF_SIGHANDLER_FUNC(SIG, NUM) \
|
||||
static bool seen_##SIG; \
|
||||
static void handle_##SIG(int sig, siginfo_t *info, void *context) \
|
||||
{ \
|
||||
ucontext_t *uc = context; \
|
||||
\
|
||||
seen_##SIG = true; \
|
||||
/* Skip over the offending instruction */ \
|
||||
uc->uc_mcontext.pc += 4; \
|
||||
}
|
||||
|
||||
DEF_SIGHANDLER_FUNC(sigill, SIGILL);
|
||||
DEF_SIGHANDLER_FUNC(sigbus, SIGBUS);
|
||||
|
||||
bool cpuinfo_present(const char *name)
|
||||
{
|
||||
FILE *f;
|
||||
@ -457,25 +623,78 @@ bool cpuinfo_present(const char *name)
|
||||
return false;
|
||||
}
|
||||
|
||||
static int install_sigaction(int signum, sighandler_fn handler)
|
||||
{
|
||||
int ret;
|
||||
struct sigaction sa;
|
||||
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa.sa_sigaction = handler;
|
||||
sa.sa_flags = SA_RESTART | SA_SIGINFO;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
ret = sigaction(signum, &sa, NULL);
|
||||
if (ret < 0)
|
||||
ksft_exit_fail_msg("Failed to install SIGNAL handler: %s (%d)\n",
|
||||
strerror(errno), errno);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void uninstall_sigaction(int signum)
|
||||
{
|
||||
if (sigaction(signum, NULL, NULL) < 0)
|
||||
ksft_exit_fail_msg("Failed to uninstall SIGNAL handler: %s (%d)\n",
|
||||
strerror(errno), errno);
|
||||
}
|
||||
|
||||
#define DEF_INST_RAISE_SIG(SIG, NUM) \
|
||||
static bool inst_raise_##SIG(const struct hwcap_data *hwcap, \
|
||||
bool have_hwcap) \
|
||||
{ \
|
||||
if (!hwcap->SIG##_fn) { \
|
||||
ksft_test_result_skip(#SIG"_%s\n", hwcap->name); \
|
||||
/* assume that it would raise exception in default */ \
|
||||
return true; \
|
||||
} \
|
||||
\
|
||||
install_sigaction(NUM, handle_##SIG); \
|
||||
\
|
||||
seen_##SIG = false; \
|
||||
hwcap->SIG##_fn(); \
|
||||
\
|
||||
if (have_hwcap) { \
|
||||
/* Should be able to use the extension */ \
|
||||
ksft_test_result(!seen_##SIG, \
|
||||
#SIG"_%s\n", hwcap->name); \
|
||||
} else if (hwcap->SIG##_reliable) { \
|
||||
/* Guaranteed a SIGNAL */ \
|
||||
ksft_test_result(seen_##SIG, \
|
||||
#SIG"_%s\n", hwcap->name); \
|
||||
} else { \
|
||||
/* Missing SIGNAL might be fine */ \
|
||||
ksft_print_msg(#SIG"_%sreported for %s\n", \
|
||||
seen_##SIG ? "" : "not ", \
|
||||
hwcap->name); \
|
||||
ksft_test_result_skip(#SIG"_%s\n", \
|
||||
hwcap->name); \
|
||||
} \
|
||||
\
|
||||
uninstall_sigaction(NUM); \
|
||||
return seen_##SIG; \
|
||||
}
|
||||
|
||||
DEF_INST_RAISE_SIG(sigill, SIGILL);
|
||||
DEF_INST_RAISE_SIG(sigbus, SIGBUS);
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int i;
|
||||
const struct hwcap_data *hwcap;
|
||||
int i, ret;
|
||||
bool have_cpuinfo, have_hwcap;
|
||||
struct sigaction sa;
|
||||
bool have_cpuinfo, have_hwcap, raise_sigill;
|
||||
|
||||
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];
|
||||
|
||||
@ -488,30 +707,15 @@ int main(void)
|
||||
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);
|
||||
}
|
||||
/*
|
||||
* Testing for SIGBUS only makes sense after make sure
|
||||
* that the instruction does not cause a SIGILL signal.
|
||||
*/
|
||||
raise_sigill = inst_raise_sigill(hwcap, have_hwcap);
|
||||
if (!raise_sigill)
|
||||
inst_raise_sigbus(hwcap, have_hwcap);
|
||||
else
|
||||
ksft_test_result_skip("sigbus_%s\n", hwcap->name);
|
||||
}
|
||||
|
||||
ksft_print_cnts();
|
||||
|
@ -20,12 +20,20 @@
|
||||
|
||||
#include "syscall-abi.h"
|
||||
|
||||
/*
|
||||
* The kernel defines a much larger SVE_VQ_MAX than is expressable in
|
||||
* the architecture, this creates a *lot* of overhead filling the
|
||||
* buffers (especially ZA) on emulated platforms so use the actual
|
||||
* architectural maximum instead.
|
||||
*/
|
||||
#define ARCH_SVE_VQ_MAX 16
|
||||
|
||||
static int default_sme_vl;
|
||||
|
||||
static int sve_vl_count;
|
||||
static unsigned int sve_vls[SVE_VQ_MAX];
|
||||
static unsigned int sve_vls[ARCH_SVE_VQ_MAX];
|
||||
static int sme_vl_count;
|
||||
static unsigned int sme_vls[SVE_VQ_MAX];
|
||||
static unsigned int sme_vls[ARCH_SVE_VQ_MAX];
|
||||
|
||||
extern void do_syscall(int sve_vl, int sme_vl);
|
||||
|
||||
@ -130,9 +138,9 @@ static int check_fpr(struct syscall_cfg *cfg, int sve_vl, int sme_vl,
|
||||
|
||||
#define SVE_Z_SHARED_BYTES (128 / 8)
|
||||
|
||||
static uint8_t z_zero[__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 uint8_t z_zero[__SVE_ZREG_SIZE(ARCH_SVE_VQ_MAX)];
|
||||
uint8_t z_in[SVE_NUM_ZREGS * __SVE_ZREG_SIZE(ARCH_SVE_VQ_MAX)];
|
||||
uint8_t z_out[SVE_NUM_ZREGS * __SVE_ZREG_SIZE(ARCH_SVE_VQ_MAX)];
|
||||
|
||||
static void setup_z(struct syscall_cfg *cfg, int sve_vl, int sme_vl,
|
||||
uint64_t svcr)
|
||||
@ -190,8 +198,8 @@ static int check_z(struct syscall_cfg *cfg, int sve_vl, int sme_vl,
|
||||
return errors;
|
||||
}
|
||||
|
||||
uint8_t p_in[SVE_NUM_PREGS * __SVE_PREG_SIZE(SVE_VQ_MAX)];
|
||||
uint8_t p_out[SVE_NUM_PREGS * __SVE_PREG_SIZE(SVE_VQ_MAX)];
|
||||
uint8_t p_in[SVE_NUM_PREGS * __SVE_PREG_SIZE(ARCH_SVE_VQ_MAX)];
|
||||
uint8_t p_out[SVE_NUM_PREGS * __SVE_PREG_SIZE(ARCH_SVE_VQ_MAX)];
|
||||
|
||||
static void setup_p(struct syscall_cfg *cfg, int sve_vl, int sme_vl,
|
||||
uint64_t svcr)
|
||||
@ -222,8 +230,8 @@ static int check_p(struct syscall_cfg *cfg, int sve_vl, int sme_vl,
|
||||
return errors;
|
||||
}
|
||||
|
||||
uint8_t ffr_in[__SVE_PREG_SIZE(SVE_VQ_MAX)];
|
||||
uint8_t ffr_out[__SVE_PREG_SIZE(SVE_VQ_MAX)];
|
||||
uint8_t ffr_in[__SVE_PREG_SIZE(ARCH_SVE_VQ_MAX)];
|
||||
uint8_t ffr_out[__SVE_PREG_SIZE(ARCH_SVE_VQ_MAX)];
|
||||
|
||||
static void setup_ffr(struct syscall_cfg *cfg, int sve_vl, int sme_vl,
|
||||
uint64_t svcr)
|
||||
@ -300,8 +308,8 @@ static int check_svcr(struct syscall_cfg *cfg, int sve_vl, int sme_vl,
|
||||
return errors;
|
||||
}
|
||||
|
||||
uint8_t za_in[ZA_SIG_REGS_SIZE(SVE_VQ_MAX)];
|
||||
uint8_t za_out[ZA_SIG_REGS_SIZE(SVE_VQ_MAX)];
|
||||
uint8_t za_in[ZA_SIG_REGS_SIZE(ARCH_SVE_VQ_MAX)];
|
||||
uint8_t za_out[ZA_SIG_REGS_SIZE(ARCH_SVE_VQ_MAX)];
|
||||
|
||||
static void setup_za(struct syscall_cfg *cfg, int sve_vl, int sme_vl,
|
||||
uint64_t svcr)
|
||||
@ -470,9 +478,9 @@ void sve_count_vls(void)
|
||||
return;
|
||||
|
||||
/*
|
||||
* Enumerate up to SVE_VQ_MAX vector lengths
|
||||
* Enumerate up to ARCH_SVE_VQ_MAX vector lengths
|
||||
*/
|
||||
for (vq = SVE_VQ_MAX; vq > 0; vq /= 2) {
|
||||
for (vq = ARCH_SVE_VQ_MAX; vq > 0; vq /= 2) {
|
||||
vl = prctl(PR_SVE_SET_VL, vq * 16);
|
||||
if (vl == -1)
|
||||
ksft_exit_fail_msg("PR_SVE_SET_VL failed: %s (%d)\n",
|
||||
@ -496,9 +504,9 @@ void sme_count_vls(void)
|
||||
return;
|
||||
|
||||
/*
|
||||
* Enumerate up to SVE_VQ_MAX vector lengths
|
||||
* Enumerate up to ARCH_SVE_VQ_MAX vector lengths
|
||||
*/
|
||||
for (vq = SVE_VQ_MAX; vq > 0; vq /= 2) {
|
||||
for (vq = ARCH_SVE_VQ_MAX; vq > 0; vq /= 2) {
|
||||
vl = prctl(PR_SME_SET_VL, vq * 16);
|
||||
if (vl == -1)
|
||||
ksft_exit_fail_msg("PR_SME_SET_VL failed: %s (%d)\n",
|
||||
|
@ -2,8 +2,6 @@
|
||||
|
||||
TEST_GEN_PROGS := btitest nobtitest
|
||||
|
||||
PROGS := $(patsubst %,gen/%,$(TEST_GEN_PROGS))
|
||||
|
||||
# These tests are built as freestanding binaries since otherwise BTI
|
||||
# support in ld.so is required which is not currently widespread; when
|
||||
# it is available it will still be useful to test this separately as the
|
||||
@ -18,44 +16,41 @@ CFLAGS_COMMON = -ffreestanding -Wall -Wextra $(CFLAGS)
|
||||
BTI_CC_COMMAND = $(CC) $(CFLAGS_BTI) $(CFLAGS_COMMON) -c -o $@ $<
|
||||
NOBTI_CC_COMMAND = $(CC) $(CFLAGS_NOBTI) $(CFLAGS_COMMON) -c -o $@ $<
|
||||
|
||||
%-bti.o: %.c
|
||||
$(OUTPUT)/%-bti.o: %.c
|
||||
$(BTI_CC_COMMAND)
|
||||
|
||||
%-bti.o: %.S
|
||||
$(OUTPUT)/%-bti.o: %.S
|
||||
$(BTI_CC_COMMAND)
|
||||
|
||||
%-nobti.o: %.c
|
||||
$(OUTPUT)/%-nobti.o: %.c
|
||||
$(NOBTI_CC_COMMAND)
|
||||
|
||||
%-nobti.o: %.S
|
||||
$(OUTPUT)/%-nobti.o: %.S
|
||||
$(NOBTI_CC_COMMAND)
|
||||
|
||||
BTI_OBJS = \
|
||||
test-bti.o \
|
||||
signal-bti.o \
|
||||
start-bti.o \
|
||||
syscall-bti.o \
|
||||
system-bti.o \
|
||||
teststubs-bti.o \
|
||||
trampoline-bti.o
|
||||
gen/btitest: $(BTI_OBJS)
|
||||
$(OUTPUT)/test-bti.o \
|
||||
$(OUTPUT)/signal-bti.o \
|
||||
$(OUTPUT)/start-bti.o \
|
||||
$(OUTPUT)/syscall-bti.o \
|
||||
$(OUTPUT)/system-bti.o \
|
||||
$(OUTPUT)/teststubs-bti.o \
|
||||
$(OUTPUT)/trampoline-bti.o
|
||||
$(OUTPUT)/btitest: $(BTI_OBJS)
|
||||
$(CC) $(CFLAGS_BTI) $(CFLAGS_COMMON) -nostdlib -static -o $@ $^
|
||||
|
||||
NOBTI_OBJS = \
|
||||
test-nobti.o \
|
||||
signal-nobti.o \
|
||||
start-nobti.o \
|
||||
syscall-nobti.o \
|
||||
system-nobti.o \
|
||||
teststubs-nobti.o \
|
||||
trampoline-nobti.o
|
||||
gen/nobtitest: $(NOBTI_OBJS)
|
||||
$(OUTPUT)/test-nobti.o \
|
||||
$(OUTPUT)/signal-nobti.o \
|
||||
$(OUTPUT)/start-nobti.o \
|
||||
$(OUTPUT)/syscall-nobti.o \
|
||||
$(OUTPUT)/system-nobti.o \
|
||||
$(OUTPUT)/teststubs-nobti.o \
|
||||
$(OUTPUT)/trampoline-nobti.o
|
||||
$(OUTPUT)/nobtitest: $(NOBTI_OBJS)
|
||||
$(CC) $(CFLAGS_BTI) $(CFLAGS_COMMON) -nostdlib -static -o $@ $^
|
||||
|
||||
# Including KSFT lib.mk here will also mangle the TEST_GEN_PROGS list
|
||||
# to account for any OUTPUT target-dirs optionally provided by
|
||||
# the toplevel makefile
|
||||
include ../../lib.mk
|
||||
|
||||
$(TEST_GEN_PROGS): $(PROGS)
|
||||
cp $(PROGS) $(OUTPUT)/
|
||||
|
@ -1,21 +0,0 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Copyright (C) 2019 Arm Limited
|
||||
* Original author: Dave Martin <Dave.Martin@arm.com>
|
||||
*/
|
||||
|
||||
#ifndef COMPILER_H
|
||||
#define COMPILER_H
|
||||
|
||||
#define __always_unused __attribute__((__unused__))
|
||||
#define __noreturn __attribute__((__noreturn__))
|
||||
#define __unreachable() __builtin_unreachable()
|
||||
|
||||
/* curse(e) has value e, but the compiler cannot assume so */
|
||||
#define curse(e) ({ \
|
||||
__typeof__(e) __curse_e = (e); \
|
||||
asm ("" : "+r" (__curse_e)); \
|
||||
__curse_e; \
|
||||
})
|
||||
|
||||
#endif /* ! COMPILER_H */
|
@ -1,2 +0,0 @@
|
||||
btitest
|
||||
nobtitest
|
@ -8,12 +8,10 @@
|
||||
|
||||
#include <asm/unistd.h>
|
||||
|
||||
#include "compiler.h"
|
||||
|
||||
void __noreturn exit(int n)
|
||||
{
|
||||
syscall(__NR_exit, n);
|
||||
__unreachable();
|
||||
unreachable();
|
||||
}
|
||||
|
||||
ssize_t write(int fd, const void *buf, size_t size)
|
||||
|
@ -14,12 +14,12 @@ typedef __kernel_size_t size_t;
|
||||
typedef __kernel_ssize_t ssize_t;
|
||||
|
||||
#include <linux/errno.h>
|
||||
#include <linux/compiler.h>
|
||||
|
||||
#include <asm/hwcap.h>
|
||||
#include <asm/ptrace.h>
|
||||
#include <asm/unistd.h>
|
||||
|
||||
#include "compiler.h"
|
||||
|
||||
long syscall(int nr, ...);
|
||||
|
||||
void __noreturn exit(int n);
|
||||
|
@ -17,7 +17,6 @@
|
||||
typedef struct ucontext ucontext_t;
|
||||
|
||||
#include "btitest.h"
|
||||
#include "compiler.h"
|
||||
#include "signal.h"
|
||||
|
||||
#define EXPECTED_TESTS 18
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@ -39,9 +40,11 @@ struct vec_data {
|
||||
int max_vl;
|
||||
};
|
||||
|
||||
#define VEC_SVE 0
|
||||
#define VEC_SME 1
|
||||
|
||||
static struct vec_data vec_data[] = {
|
||||
{
|
||||
[VEC_SVE] = {
|
||||
.name = "SVE",
|
||||
.hwcap_type = AT_HWCAP,
|
||||
.hwcap = HWCAP_SVE,
|
||||
@ -51,7 +54,7 @@ static struct vec_data vec_data[] = {
|
||||
.prctl_set = PR_SVE_SET_VL,
|
||||
.default_vl_file = "/proc/sys/abi/sve_default_vector_length",
|
||||
},
|
||||
{
|
||||
[VEC_SME] = {
|
||||
.name = "SME",
|
||||
.hwcap_type = AT_HWCAP2,
|
||||
.hwcap = HWCAP2_SME,
|
||||
@ -551,7 +554,8 @@ static void prctl_set_onexec(struct vec_data *data)
|
||||
/* For each VQ verify that setting via prctl() does the right thing */
|
||||
static void prctl_set_all_vqs(struct vec_data *data)
|
||||
{
|
||||
int ret, vq, vl, new_vl;
|
||||
int ret, vq, vl, new_vl, i;
|
||||
int orig_vls[ARRAY_SIZE(vec_data)];
|
||||
int errors = 0;
|
||||
|
||||
if (!data->min_vl || !data->max_vl) {
|
||||
@ -560,6 +564,9 @@ static void prctl_set_all_vqs(struct vec_data *data)
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(vec_data); i++)
|
||||
orig_vls[i] = vec_data[i].rdvl();
|
||||
|
||||
for (vq = SVE_VQ_MIN; vq <= SVE_VQ_MAX; vq++) {
|
||||
vl = sve_vl_from_vq(vq);
|
||||
|
||||
@ -582,6 +589,22 @@ static void prctl_set_all_vqs(struct vec_data *data)
|
||||
errors++;
|
||||
}
|
||||
|
||||
/* Did any other VLs change? */
|
||||
for (i = 0; i < ARRAY_SIZE(vec_data); i++) {
|
||||
if (&vec_data[i] == data)
|
||||
continue;
|
||||
|
||||
if (!(getauxval(vec_data[i].hwcap_type) & vec_data[i].hwcap))
|
||||
continue;
|
||||
|
||||
if (vec_data[i].rdvl() != orig_vls[i]) {
|
||||
ksft_print_msg("%s VL changed from %d to %d\n",
|
||||
vec_data[i].name, orig_vls[i],
|
||||
vec_data[i].rdvl());
|
||||
errors++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Was that the VL we asked for? */
|
||||
if (new_vl == vl)
|
||||
continue;
|
||||
@ -644,18 +667,107 @@ static const test_type tests[] = {
|
||||
prctl_set_all_vqs,
|
||||
};
|
||||
|
||||
static inline void smstart(void)
|
||||
{
|
||||
asm volatile("msr S0_3_C4_C7_3, xzr");
|
||||
}
|
||||
|
||||
static inline void smstart_sm(void)
|
||||
{
|
||||
asm volatile("msr S0_3_C4_C3_3, xzr");
|
||||
}
|
||||
|
||||
static inline void smstop(void)
|
||||
{
|
||||
asm volatile("msr S0_3_C4_C6_3, xzr");
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Verify we can change the SVE vector length while SME is active and
|
||||
* continue to use SME afterwards.
|
||||
*/
|
||||
static void change_sve_with_za(void)
|
||||
{
|
||||
struct vec_data *sve_data = &vec_data[VEC_SVE];
|
||||
bool pass = true;
|
||||
int ret, i;
|
||||
|
||||
if (sve_data->min_vl == sve_data->max_vl) {
|
||||
ksft_print_msg("Only one SVE VL supported, can't change\n");
|
||||
ksft_test_result_skip("change_sve_while_sme\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Ensure we will trigger a change when we set the maximum */
|
||||
ret = prctl(sve_data->prctl_set, sve_data->min_vl);
|
||||
if (ret != sve_data->min_vl) {
|
||||
ksft_print_msg("Failed to set SVE VL %d: %d\n",
|
||||
sve_data->min_vl, ret);
|
||||
pass = false;
|
||||
}
|
||||
|
||||
/* Enable SM and ZA */
|
||||
smstart();
|
||||
|
||||
/* Trigger another VL change */
|
||||
ret = prctl(sve_data->prctl_set, sve_data->max_vl);
|
||||
if (ret != sve_data->max_vl) {
|
||||
ksft_print_msg("Failed to set SVE VL %d: %d\n",
|
||||
sve_data->max_vl, ret);
|
||||
pass = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Spin for a bit with SM enabled to try to trigger another
|
||||
* save/restore. We can't use syscalls without exiting
|
||||
* streaming mode.
|
||||
*/
|
||||
for (i = 0; i < 100000000; i++)
|
||||
smstart_sm();
|
||||
|
||||
/*
|
||||
* TODO: Verify that ZA was preserved over the VL change and
|
||||
* spin.
|
||||
*/
|
||||
|
||||
/* Clean up after ourselves */
|
||||
smstop();
|
||||
ret = prctl(sve_data->prctl_set, sve_data->default_vl);
|
||||
if (ret != sve_data->default_vl) {
|
||||
ksft_print_msg("Failed to restore SVE VL %d: %d\n",
|
||||
sve_data->default_vl, ret);
|
||||
pass = false;
|
||||
}
|
||||
|
||||
ksft_test_result(pass, "change_sve_with_za\n");
|
||||
}
|
||||
|
||||
typedef void (*test_all_type)(void);
|
||||
|
||||
static const struct {
|
||||
const char *name;
|
||||
test_all_type test;
|
||||
} all_types_tests[] = {
|
||||
{ "change_sve_with_za", change_sve_with_za },
|
||||
};
|
||||
|
||||
int main(void)
|
||||
{
|
||||
bool all_supported = true;
|
||||
int i, j;
|
||||
|
||||
ksft_print_header();
|
||||
ksft_set_plan(ARRAY_SIZE(tests) * ARRAY_SIZE(vec_data));
|
||||
ksft_set_plan(ARRAY_SIZE(tests) * ARRAY_SIZE(vec_data) +
|
||||
ARRAY_SIZE(all_types_tests));
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(vec_data); i++) {
|
||||
struct vec_data *data = &vec_data[i];
|
||||
unsigned long supported;
|
||||
|
||||
supported = getauxval(data->hwcap_type) & data->hwcap;
|
||||
if (!supported)
|
||||
all_supported = false;
|
||||
|
||||
for (j = 0; j < ARRAY_SIZE(tests); j++) {
|
||||
if (supported)
|
||||
@ -666,5 +778,12 @@ int main(void)
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(all_types_tests); i++) {
|
||||
if (all_supported)
|
||||
all_types_tests[i].test();
|
||||
else
|
||||
ksft_test_result_skip("%s\n", all_types_tests[i].name);
|
||||
}
|
||||
|
||||
ksft_exit_pass();
|
||||
}
|
||||
|
@ -8,6 +8,8 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <linux/compiler.h>
|
||||
|
||||
#include "test_signals.h"
|
||||
|
||||
int test_init(struct tdescr *td);
|
||||
@ -60,13 +62,25 @@ static __always_inline bool get_current_context(struct tdescr *td,
|
||||
size_t dest_sz)
|
||||
{
|
||||
static volatile bool seen_already;
|
||||
int i;
|
||||
char *uc = (char *)dest_uc;
|
||||
|
||||
assert(td && dest_uc);
|
||||
/* it's a genuine invocation..reinit */
|
||||
seen_already = 0;
|
||||
td->live_uc_valid = 0;
|
||||
td->live_sz = dest_sz;
|
||||
memset(dest_uc, 0x00, td->live_sz);
|
||||
|
||||
/*
|
||||
* This is a memset() but we don't want the compiler to
|
||||
* optimise it into either instructions or a library call
|
||||
* which might be incompatible with streaming mode.
|
||||
*/
|
||||
for (i = 0; i < td->live_sz; i++) {
|
||||
uc[i] = 0;
|
||||
OPTIMIZER_HIDE_VAR(uc[0]);
|
||||
}
|
||||
|
||||
td->live_uc = dest_uc;
|
||||
/*
|
||||
* Grab ucontext_t triggering a SIGTRAP.
|
||||
@ -103,6 +117,17 @@ static __always_inline bool get_current_context(struct tdescr *td,
|
||||
:
|
||||
: "memory");
|
||||
|
||||
/*
|
||||
* If we were grabbing a streaming mode context then we may
|
||||
* have entered streaming mode behind the system's back and
|
||||
* libc or compiler generated code might decide to do
|
||||
* something invalid in streaming mode, or potentially even
|
||||
* the state of ZA. Issue a SMSTOP to exit both now we have
|
||||
* grabbed the state.
|
||||
*/
|
||||
if (td->feats_supported & FEAT_SME)
|
||||
asm volatile("msr S0_3_C4_C6_3, xzr");
|
||||
|
||||
/*
|
||||
* If we get here with seen_already==1 it implies the td->live_uc
|
||||
* context has been used to get back here....this probably means
|
||||
|
@ -65,6 +65,7 @@ int zt_regs_run(struct tdescr *td, siginfo_t *si, ucontext_t *uc)
|
||||
if (memcmp(zeros, (char *)zt + ZT_SIG_REGS_OFFSET,
|
||||
ZT_SIG_REGS_SIZE(zt->nregs)) != 0) {
|
||||
fprintf(stderr, "ZT data invalid\n");
|
||||
free(zeros);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user