[PATCH v11 40/40] kselftest/arm64: Add SME support to syscall ABI test

Shuah Khan skhan at linuxfoundation.org
Mon Feb 7 17:52:06 PST 2022


On 2/7/22 8:21 AM, Mark Brown wrote:
> For every possible combination of SVE and SME vector length verify that for
> each possible value of SVCR after a syscall we leave streaming mode and ZA
> is preserved. We don't need to take account of any streaming/non streaming
> SVE vector length changes in the assembler code since the store instructions
> will handle the vector length for us. We log if the system supports FA64 and
> only try to set FFR in streaming mode if it does.
> 
> Signed-off-by: Mark Brown <broonie at kernel.org>
> ---
>   .../selftests/arm64/abi/syscall-abi-asm.S     |  69 +++++-
>   .../testing/selftests/arm64/abi/syscall-abi.c | 204 ++++++++++++++++--
>   .../testing/selftests/arm64/abi/syscall-abi.h |  15 ++
>   3 files changed, 265 insertions(+), 23 deletions(-)
>   create mode 100644 tools/testing/selftests/arm64/abi/syscall-abi.h
> 

> diff --git a/tools/testing/selftests/arm64/abi/syscall-abi.c b/tools/testing/selftests/arm64/abi/syscall-abi.c
> index 1e13b7523918..b632bfe9e022 100644
> --- a/tools/testing/selftests/arm64/abi/syscall-abi.c
> +++ b/tools/testing/selftests/arm64/abi/syscall-abi.c
> @@ -18,9 +18,13 @@
>   
>   #include "../../kselftest.h"
>   
> +#include "syscall-abi.h"
> +
>   #define NUM_VL ((SVE_VQ_MAX - SVE_VQ_MIN) + 1)
>   
> -extern void do_syscall(int sve_vl);
> +static int default_sme_vl;
> +
> +extern void do_syscall(int sve_vl, int sme_vl);
>   
>   static void fill_random(void *buf, size_t size)
>   {
> @@ -48,14 +52,15 @@ static struct syscall_cfg {
>   uint64_t gpr_in[NUM_GPR];
>   uint64_t gpr_out[NUM_GPR];
>   
> -static void setup_gpr(struct syscall_cfg *cfg, int sve_vl)
> +static void setup_gpr(struct syscall_cfg *cfg, int sve_vl, int sme_vl,
> +		      uint64_t svcr)
>   {
>   	fill_random(gpr_in, sizeof(gpr_in));
>   	gpr_in[8] = cfg->syscall_nr;
>   	memset(gpr_out, 0, sizeof(gpr_out));
>   }
>   
> -static int check_gpr(struct syscall_cfg *cfg, int sve_vl)
> +static int check_gpr(struct syscall_cfg *cfg, int sve_vl, int sme_vl, uint64_t svcr)
>   {
>   	int errors = 0;
>   	int i;
> @@ -79,13 +84,15 @@ static int check_gpr(struct syscall_cfg *cfg, int sve_vl)
>   uint64_t fpr_in[NUM_FPR * 2];
>   uint64_t fpr_out[NUM_FPR * 2];
>   
> -static void setup_fpr(struct syscall_cfg *cfg, int sve_vl)
> +static void setup_fpr(struct syscall_cfg *cfg, int sve_vl, int sme_vl,
> +		      uint64_t svcr)
>   {
>   	fill_random(fpr_in, sizeof(fpr_in));
>   	memset(fpr_out, 0, sizeof(fpr_out));
>   }
>   
> -static int check_fpr(struct syscall_cfg *cfg, int sve_vl)
> +static int check_fpr(struct syscall_cfg *cfg, int sve_vl, int sme_vl,
> +		     uint64_t svcr)
>   {
>   	int errors = 0;
>   	int i;
> @@ -109,13 +116,15 @@ static uint8_t z_zero[__SVE_ZREG_SIZE(SVE_VQ_MAX)];
>   uint8_t z_in[SVE_NUM_PREGS * __SVE_ZREG_SIZE(SVE_VQ_MAX)];
>   uint8_t z_out[SVE_NUM_PREGS * __SVE_ZREG_SIZE(SVE_VQ_MAX)];
>   
> -static void setup_z(struct syscall_cfg *cfg, int sve_vl)
> +static void setup_z(struct syscall_cfg *cfg, int sve_vl, int sme_vl,
> +		    uint64_t svcr)
>   {
>   	fill_random(z_in, sizeof(z_in));
>   	fill_random(z_out, sizeof(z_out));
>   }
>   
> -static int check_z(struct syscall_cfg *cfg, int sve_vl)
> +static int check_z(struct syscall_cfg *cfg, int sve_vl, int sme_vl,
> +		   uint64_t svcr)
>   {
>   	size_t reg_size = sve_vl;
>   	int errors = 0;
> @@ -126,13 +135,17 @@ static int check_z(struct syscall_cfg *cfg, int sve_vl)
>   
>   	/*
>   	 * After a syscall the low 128 bits of the Z registers should
> -	 * be preserved and the rest be zeroed or preserved.
> +	 * be preserved and the rest be zeroed or preserved, except if
> +	 * we were in streaming mode in which case the low 128 bits may
> +	 * also be cleared by the transition out of streaming mode.
>   	 */
>   	for (i = 0; i < SVE_NUM_ZREGS; i++) {
>   		void *in = &z_in[reg_size * i];
>   		void *out = &z_out[reg_size * i];
>   
> -		if (memcmp(in, out, SVE_VQ_BYTES) != 0) {
> +		if ((memcmp(in, out, SVE_VQ_BYTES) != 0) &&
> +		    !((svcr & SVCR_SM_MASK) &&
> +		      memcmp(z_zero, out, SVE_VQ_BYTES) == 0)) {
>   			ksft_print_msg("%s SVE VL %d Z%d low 128 bits changed\n",
>   				       cfg->name, sve_vl, i);
>   			errors++;
> @@ -145,13 +158,15 @@ static int check_z(struct syscall_cfg *cfg, int sve_vl)
>   uint8_t p_in[SVE_NUM_PREGS * __SVE_PREG_SIZE(SVE_VQ_MAX)];
>   uint8_t p_out[SVE_NUM_PREGS * __SVE_PREG_SIZE(SVE_VQ_MAX)];
>   
> -static void setup_p(struct syscall_cfg *cfg, int sve_vl)
> +static void setup_p(struct syscall_cfg *cfg, int sve_vl, int sme_vl,
> +		    uint64_t svcr)
>   {
>   	fill_random(p_in, sizeof(p_in));
>   	fill_random(p_out, sizeof(p_out));
>   }
>   
> -static int check_p(struct syscall_cfg *cfg, int sve_vl)
> +static int check_p(struct syscall_cfg *cfg, int sve_vl, int sme_vl,
> +		   uint64_t svcr)
>   {
>   	size_t reg_size = sve_vq_from_vl(sve_vl) * 2; /* 1 bit per VL byte */
>   
> @@ -175,8 +190,19 @@ static int check_p(struct syscall_cfg *cfg, int sve_vl)
>   uint8_t ffr_in[__SVE_PREG_SIZE(SVE_VQ_MAX)];
>   uint8_t ffr_out[__SVE_PREG_SIZE(SVE_VQ_MAX)];
>   
> -static void setup_ffr(struct syscall_cfg *cfg, int sve_vl)
> +static void setup_ffr(struct syscall_cfg *cfg, int sve_vl, int sme_vl,
> +		      uint64_t svcr)
>   {
> +	/*
> +	 * If we are in streaming mode and do not have FA64 then FFR
> +	 * is unavailable.
> +	 */
> +	if ((svcr & SVCR_SM_MASK) &&
> +	    !(getauxval(AT_HWCAP2) & HWCAP2_SME_FA64)) {
> +		memset(&ffr_in, 0, sizeof(ffr_in));
> +		return;
> +	}
> +
>   	/*
>   	 * It is only valid to set a contiguous set of bits starting
>   	 * at 0.  For now since we're expecting this to be cleared by
> @@ -186,7 +212,8 @@ static void setup_ffr(struct syscall_cfg *cfg, int sve_vl)
>   	fill_random(ffr_out, sizeof(ffr_out));
>   }
>   
> -static int check_ffr(struct syscall_cfg *cfg, int sve_vl)
> +static int check_ffr(struct syscall_cfg *cfg, int sve_vl, int sme_vl,
> +		     uint64_t svcr)
>   {
>   	size_t reg_size = sve_vq_from_vl(sve_vl) * 2;  /* 1 bit per VL byte */
>   	int errors = 0;
> @@ -195,6 +222,10 @@ static int check_ffr(struct syscall_cfg *cfg, int sve_vl)
>   	if (!sve_vl)
>   		return 0;
>   
> +	if ((svcr & SVCR_SM_MASK) &&
> +	    !(getauxval(AT_HWCAP2) & HWCAP2_SME_FA64))
> +		return 0;
> +
>   	/* After a syscall the P registers should be preserved or zeroed */
>   	for (i = 0; i < reg_size; i++)
>   		if (ffr_out[i] && (ffr_in[i] != ffr_out[i]))
> @@ -206,8 +237,65 @@ static int check_ffr(struct syscall_cfg *cfg, int sve_vl)
>   	return errors;
>   }
>   
> -typedef void (*setup_fn)(struct syscall_cfg *cfg, int sve_vl);
> -typedef int (*check_fn)(struct syscall_cfg *cfg, int sve_vl);
> +uint64_t svcr_in, svcr_out;
> +
> +static void setup_svcr(struct syscall_cfg *cfg, int sve_vl, int sme_vl,
> +		    uint64_t svcr)
> +{
> +	svcr_in = svcr;
> +}
> +
> +static int check_svcr(struct syscall_cfg *cfg, int sve_vl, int sme_vl,
> +		      uint64_t svcr)
> +{
> +	int errors = 0;
> +
> +	if (svcr_out & SVCR_SM_MASK) {
> +		ksft_print_msg("%s Still in SM, SVCR %llx\n",
> +			       cfg->name, svcr_out);
> +		errors++;
> +	}
> +
> +	if ((svcr_in & SVCR_ZA_MASK) != (svcr_out & SVCR_ZA_MASK)) {
> +		ksft_print_msg("%s PSTATE.ZA changed, SVCR %llx != %llx\n",
> +			       cfg->name, svcr_in, svcr_out);
> +		errors++;
> +	}
> +
> +	return errors;
> +}
> +
> +uint8_t za_in[SVE_NUM_PREGS * __SVE_ZREG_SIZE(SVE_VQ_MAX)];
> +uint8_t za_out[SVE_NUM_PREGS * __SVE_ZREG_SIZE(SVE_VQ_MAX)];
> +
> +static void setup_za(struct syscall_cfg *cfg, int sve_vl, int sme_vl,
> +		     uint64_t svcr)
> +{
> +	fill_random(za_in, sizeof(za_in));
> +	memset(za_out, 0, sizeof(za_out));
> +}
> +
> +static int check_za(struct syscall_cfg *cfg, int sve_vl, int sme_vl,
> +		    uint64_t svcr)
> +{
> +	size_t reg_size = sme_vl * sme_vl;

Is there possibilty of size_t overfolow here?

> +	int errors = 0;
> +
> +	if (!(svcr & SVCR_ZA_MASK))
> +		return 0;
> +
> +	if (memcmp(za_in, za_out, reg_size) != 0) {
> +		ksft_print_msg("SME VL %d ZA does not match\n", sme_vl);

Print the expected value in addition to the sme_val.

> +		errors++;
> +	}
> +
> +	return errors;
> +}
> +
> +typedef void (*setup_fn)(struct syscall_cfg *cfg, int sve_vl, int sme_vl,
> +			 uint64_t svcr);
> +typedef int (*check_fn)(struct syscall_cfg *cfg, int sve_vl, int sme_vl,
> +			uint64_t svcr);
>   
>   /*
>    * Each set of registers has a setup function which is called before
> @@ -225,20 +313,23 @@ static struct {
>   	{ setup_z, check_z },
>   	{ setup_p, check_p },
>   	{ setup_ffr, check_ffr },
> +	{ setup_svcr, check_svcr },
> +	{ setup_za, check_za },
>   };
>   
> -static bool do_test(struct syscall_cfg *cfg, int sve_vl)
> +static bool do_test(struct syscall_cfg *cfg, int sve_vl, int sme_vl,
> +		    uint64_t svcr)
>   {
>   	int errors = 0;
>   	int i;
>   
>   	for (i = 0; i < ARRAY_SIZE(regset); i++)
> -		regset[i].setup(cfg, sve_vl);
> +		regset[i].setup(cfg, sve_vl, sme_vl, svcr);
>   
> -	do_syscall(sve_vl);
> +	do_syscall(sve_vl, sme_vl);
>   
>   	for (i = 0; i < ARRAY_SIZE(regset); i++)
> -		errors += regset[i].check(cfg, sve_vl);
> +		errors += regset[i].check(cfg, sve_vl, sme_vl, svcr);
>   
>   	return errors == 0;
>   }
> @@ -246,9 +337,10 @@ static bool do_test(struct syscall_cfg *cfg, int sve_vl)
>   static void test_one_syscall(struct syscall_cfg *cfg)
>   {
>   	int sve_vq, sve_vl;
> +	int sme_vq, sme_vl;
>   
>   	/* FPSIMD only case */
> -	ksft_test_result(do_test(cfg, 0),
> +	ksft_test_result(do_test(cfg, 0, default_sme_vl, 0),
>   			 "%s FPSIMD\n", cfg->name);
>   
>   	if (!(getauxval(AT_HWCAP) & HWCAP_SVE))
> @@ -265,8 +357,36 @@ static void test_one_syscall(struct syscall_cfg *cfg)
>   		if (sve_vq != sve_vq_from_vl(sve_vl))
>   			sve_vq = sve_vq_from_vl(sve_vl);
>   
> -		ksft_test_result(do_test(cfg, sve_vl),
> +		ksft_test_result(do_test(cfg, sve_vl, default_sme_vl, 0),
>   				 "%s SVE VL %d\n", cfg->name, sve_vl);

Print default_sme_vl as well.

> +
> +		if (!(getauxval(AT_HWCAP2) & HWCAP2_SME))
> +			continue;
> +
> +		for (sme_vq = SVE_VQ_MAX; sme_vq > 0; --sme_vq) {
> +			sme_vl = prctl(PR_SME_SET_VL, sme_vq * 16);
> +			if (sme_vl == -1)
> +				ksft_exit_fail_msg("PR_SME_SET_VL failed: %s (%d)\n",
> +						   strerror(errno), errno);
> +
> +			sme_vl &= PR_SME_VL_LEN_MASK;
> +
> +			if (sme_vq != sve_vq_from_vl(sme_vl))
> +				sme_vq = sve_vq_from_vl(sme_vl);
> +
> +			ksft_test_result(do_test(cfg, sve_vl, sme_vl,
> +						 SVCR_ZA_MASK | SVCR_SM_MASK),
> +					 "%s SVE VL %d/SME VL %d SM+ZA\n",
> +					 cfg->name, sve_vl, sme_vl);
> +			ksft_test_result(do_test(cfg, sve_vl, sme_vl,
> +						 SVCR_SM_MASK),
> +					 "%s SVE VL %d/SME VL %d SM\n",
> +					 cfg->name, sve_vl, sme_vl);
> +			ksft_test_result(do_test(cfg, sve_vl, sme_vl,
> +						 SVCR_ZA_MASK),
> +					 "%s SVE VL %d/SME VL %d ZA\n",
> +					 cfg->name, sve_vl, sme_vl);
> +		}
>   	}
>   }
>   
> @@ -299,14 +419,54 @@ int sve_count_vls(void)
>   	return vl_count;
>   }
>   
> +int sme_count_vls(void)
> +{
> +	unsigned int vq;
> +	int vl_count = 0;
> +	int vl;
> +
> +	if (!(getauxval(AT_HWCAP2) & HWCAP2_SME))
> +		return 0;
> +
> +	/* Ensure we configure a SME VL, used to flag if SVCR is set */
> +	default_sme_vl = 16;
> +
> +	/*
> +	 * Enumerate up to SVE_VQ_MAX vector lengths
> +	 */
> +	for (vq = SVE_VQ_MAX; vq > 0; --vq) {
> +		vl = prctl(PR_SME_SET_VL, vq * 16);
> +		if (vl == -1)
> +			ksft_exit_fail_msg("PR_SME_SET_VL failed: %s (%d)\n",
> +					   strerror(errno), errno);
> +
> +		vl &= PR_SME_VL_LEN_MASK;
> +
> +		if (vq != sve_vq_from_vl(vl))
> +			vq = sve_vq_from_vl(vl);
> +
> +		vl_count++;
> +	}
> +
> +	return vl_count;
> +}
> +
>   int main(void)
>   {
>   	int i;
> +	int tests = 1;  /* FPSIMD */
>   
>   	srandom(getpid());
>   
>   	ksft_print_header();
> -	ksft_set_plan(ARRAY_SIZE(syscalls) * (sve_count_vls() + 1));
> +	tests += sve_count_vls();
> +	tests += (sve_count_vls() * sme_count_vls()) * 3;
> +	ksft_set_plan(ARRAY_SIZE(syscalls) * tests);
> +
> +	if (getauxval(AT_HWCAP2) & HWCAP2_SME_FA64)
> +		ksft_print_msg("SME with FA64\n");
> +	else if (getauxval(AT_HWCAP2) & HWCAP2_SME)
> +		ksft_print_msg("SME without FA64\n");
>   
>   	for (i = 0; i < ARRAY_SIZE(syscalls); i++)
>   		test_one_syscall(&syscalls[i]);
> diff --git a/tools/testing/selftests/arm64/abi/syscall-abi.h b/tools/testing/selftests/arm64/abi/syscall-abi.h
> new file mode 100644
> index 000000000000..bda5a87ad381
> --- /dev/null
> +++ b/tools/testing/selftests/arm64/abi/syscall-abi.h
> @@ -0,0 +1,15 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (C) 2021 ARM Limited.
> + */
> +
> +#ifndef SYSCALL_ABI_H
> +#define SYSCALL_ABI_H
> +
> +#define SVCR_ZA_MASK		2
> +#define SVCR_SM_MASK		1
> +
> +#define SVCR_ZA_SHIFT		1
> +#define SVCR_SM_SHIFT		0
> +
> +#endif
> 

With these fixed or explained

Reviewed-by: Shuah Khan <skhan at linuxfoundation.org>

thanks,
-- Shuah



More information about the linux-arm-kernel mailing list