[PATCH v11 32/40] kselftest/arm64: Add tests for TPIDR2

Shuah Khan skhan at linuxfoundation.org
Mon Feb 7 16:23:15 PST 2022


On 2/7/22 8:21 AM, Mark Brown wrote:
> The Scalable Matrix Extension adds a new system register TPIDR2 intended to
> be used by libc for its own thread specific use, add some kselftests which
> exercise the ABI for it.
> 
> Since this test should with some adjustment work for TPIDR and any other
> similar registers added in future add tests for it in a separate
> directory rather than placing it with the other floating point tests,
> nothing existing looked suitable so I created a new test directory
> called "abi".
> 
> Since this feature is intended to be used by libc the test is built as
> freestanding code using nolibc so we don't end up with the test program
> and libc both trying to manage the register simultaneously and
> distrupting each other. As a result of being written using nolibc rather
> than using hwcaps to identify if SME is available in the system we check
> for the default SME vector length configuration in proc, adding hwcap
> support to nolibc seems like disproportionate effort and didn't feel
> entirely idiomatic for what nolibc is trying to do.
> 
> Signed-off-by: Mark Brown <broonie at kernel.org>
> ---
>   tools/testing/selftests/arm64/abi/.gitignore |   1 +
>   tools/testing/selftests/arm64/abi/Makefile   |   9 +-
>   tools/testing/selftests/arm64/abi/tpidr2.c   | 298 +++++++++++++++++++
>   3 files changed, 307 insertions(+), 1 deletion(-)
>   create mode 100644 tools/testing/selftests/arm64/abi/tpidr2.c
> 
> diff --git a/tools/testing/selftests/arm64/abi/.gitignore b/tools/testing/selftests/arm64/abi/.gitignore
> index b79cf5814c23..b9e54417250d 100644
> --- a/tools/testing/selftests/arm64/abi/.gitignore
> +++ b/tools/testing/selftests/arm64/abi/.gitignore
> @@ -1 +1,2 @@
>   syscall-abi
> +tpidr2
> diff --git a/tools/testing/selftests/arm64/abi/Makefile b/tools/testing/selftests/arm64/abi/Makefile
> index 96eba974ac8d..c8d7f2495eb2 100644
> --- a/tools/testing/selftests/arm64/abi/Makefile
> +++ b/tools/testing/selftests/arm64/abi/Makefile
> @@ -1,8 +1,15 @@
>   # SPDX-License-Identifier: GPL-2.0
>   # Copyright (C) 2021 ARM Limited
>   
> -TEST_GEN_PROGS := syscall-abi
> +TEST_GEN_PROGS := syscall-abi tpidr2
>   
>   include ../../lib.mk
>   
>   $(OUTPUT)/syscall-abi: syscall-abi.c syscall-abi-asm.S
> +
> +# Build with nolibc since TPIDR2 is intended to be actively managed by
> +# libc and we're trying to test the functionality that it depends on here.
> +$(OUTPUT)/tpidr2: tpidr2.c
> +	$(CC) -fno-asynchronous-unwind-tables -fno-ident -s -Os -nostdlib \
> +		-static -include ../../../../include/nolibc/nolibc.h \
> +		-ffreestanding -Wall $^ -o $@ -lgcc
> diff --git a/tools/testing/selftests/arm64/abi/tpidr2.c b/tools/testing/selftests/arm64/abi/tpidr2.c
> new file mode 100644
> index 000000000000..351a098b503a
> --- /dev/null
> +++ b/tools/testing/selftests/arm64/abi/tpidr2.c
> @@ -0,0 +1,298 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +
> +#include <linux/sched.h>
> +#include <linux/wait.h>
> +
> +#define SYS_TPIDR2 "S3_3_C13_C0_5"
> +
> +#define EXPECTED_TESTS 5
> +
> +static void putstr(const char *str)
> +{
> +	write(1, str, strlen(str));
> +}
> +
> +static void putnum(unsigned int num)
> +{
> +	char c;
> +
> +	if (num / 10)
> +		putnum(num / 10);
> +
> +	c = '0' + (num % 10);
> +	write(1, &c, 1);
> +}
> +
> +static int tests_run;
> +static int tests_passed;
> +static int tests_failed;
> +static int tests_skipped;
> +
> +static void set_tpidr2(uint64_t val)
> +{
> +	asm volatile (
> +		"msr	" SYS_TPIDR2 ", %0\n"
> +		:
> +		: "r"(val)
> +		: "cc");
> +}
> +
> +static uint64_t get_tpidr2(void)
> +{
> +	uint64_t val;
> +
> +	asm volatile (
> +		"mrs	%0, " SYS_TPIDR2 "\n"
> +		: "=r"(val)
> +		:
> +		: "cc");
> +
> +	return val;
> +}
> +
> +static void print_summary(void)
> +{
> +	if (tests_passed + tests_failed + tests_skipped != EXPECTED_TESTS)
> +		putstr("# UNEXPECTED TEST COUNT: ");
> +
> +	putstr("# Totals: pass:");
> +	putnum(tests_passed);
> +	putstr(" fail:");
> +	putnum(tests_failed);
> +	putstr(" xfail:0 xpass:0 skip:");
> +	putnum(tests_skipped);
> +	putstr(" error:0\n");
> +}
> +
> +/* Processes should start with TPIDR2 == 0 */
> +static int default_value(void)
> +{
> +	return get_tpidr2() == 0;
> +}
> +
> +/* If we set TPIDR2 we should read that value */
> +static int write_read(void)
> +{
> +	set_tpidr2(getpid());
> +
> +	return getpid() == get_tpidr2();
> +}
> +
> +/* If we set a value we should read the same value after scheduling out */
> +static int write_sleep_read(void)
> +{
> +	set_tpidr2(getpid());
> +
> +	msleep(100);
> +
> +	return getpid() == get_tpidr2();
> +}
> +
> +/*
> + * If we fork the value in the parent should be unchanged and the
> + * child should start with the same value and be able to set its own
> + * value.
> + */
> +static int write_fork_read(void)
> +{
> +	pid_t newpid, waiting, oldpid;
> +	int status;
> +
> +	set_tpidr2(getpid());
> +
> +	oldpid = getpid();
> +	newpid = fork();
> +	if (newpid == 0) {
> +		/* In child */
> +		if (get_tpidr2() != oldpid) {
> +			putstr("# TPIDR2 changed in child: ");
> +			putnum(get_tpidr2());
> +			putstr("\n");
> +			exit(0);
> +		}
> +
> +		set_tpidr2(getpid());
> +		if (get_tpidr2() == getpid()) {
> +			exit(1);
> +		} else {
> +			putstr("# Failed to set TPIDR2 in child\n");
> +			exit(0);

Is this test expected to fail? Is that why it exits with 0 status?


> +		}
> +	}
> +	if (newpid < 0) {
> +		putstr("# fork() failed: -");
> +		putnum(-newpid);
> +		putstr("\n");
> +		return 0;

Same here - returning 0 indicates test passing which could mean a false
positive.

> +	}
> +
> +	for (;;) {
> +		waiting = waitpid(newpid, &status, 0);
> +
> +		if (waiting < 0) {
> +			if (errno == EINTR)
> +				continue;
> +			putstr("# waitpid() failed: ");
> +			putnum(errno);
> +			putstr("\n");
> +			return 0;
> +		}
> +		if (waiting != newpid) {
> +			putstr("# waitpid() returned wrong PID\n");
> +			return 0;
> +		}
> +
> +		if (!WIFEXITED(status)) {
> +			putstr("# child did not exit\n");
> +			return 0;
> +		}
> +
> +		if (getpid() != get_tpidr2()) {
> +			putstr("# TPIDR2 corrupted in parent\n");
> +			return 0;
> +		}
> +
> +		return WEXITSTATUS(status);
> +	}
> +}
> +
> +/*
> + * sys_clone() has a lot of per architecture variation so just define
> + * it here rather than adding it to nolibc, plus the raw API is a
> + * little more convenient for this test.
> + */
> +static int sys_clone(unsigned long clone_flags, unsigned long newsp,
> +		     int *parent_tidptr, unsigned long tls,
> +		     int *child_tidptr)
> +{
> +	return my_syscall5(__NR_clone, clone_flags, newsp, parent_tidptr, tls,
> +			   child_tidptr);
> +}
> +
> +/*
> + * If we clone with CLONE_SETTLS then the value in the parent should
> + * be unchanged and the child should start with zero and be able to
> + * set its own value.
> + */
> +static int write_clone_read(void)
> +{
> +	int parent_tid, child_tid;
> +	pid_t parent, waiting;
> +	int ret, status;
> +
> +	parent = getpid();
> +	set_tpidr2(parent);
> +
> +	ret = sys_clone(CLONE_SETTLS, 0, &parent_tid, 0, &child_tid);
> +	if (ret == -1) {
> +		putstr("# clone() failed\n");
> +		putnum(errno);
> +		putstr("\n");
> +		return 0;

Same commnet here - just making sure there are no false positives

> +	}
> +
> +	if (ret == 0) {
> +		/* In child */
> +		if (get_tpidr2() != 0) {
> +			putstr("# TPIDR2 non-zero in child: ");
> +			putnum(get_tpidr2());
> +			putstr("\n");
> +			exit(0);
> +		}
> +
> +		if (gettid() == 0)
> +			putstr("# Child TID==0\n");
> +		set_tpidr2(gettid());
> +		if (get_tpidr2() == gettid()) {
> +			exit(1);
> +		} else {
> +			putstr("# Failed to set TPIDR2 in child\n");
> +			exit(0);
> +		}
> +	}
> +
> +	for (;;) {
> +		waiting = wait4(ret, &status, __WCLONE, NULL);
> +
> +		if (waiting < 0) {
> +			if (errno == EINTR)
> +				continue;
> +			putstr("# wait4() failed: ");
> +			putnum(errno);
> +			putstr("\n");
> +			return 0;
> +		}
> +		if (waiting != ret) {
> +			putstr("# wait4() returned wrong PID ");
> +			putnum(waiting);
> +			putstr("\n");
> +			return 0;
> +		}
> +
> +		if (!WIFEXITED(status)) {
> +			putstr("# child did not exit\n");
> +			return 0;
> +		}
> +
> +		if (parent != get_tpidr2()) {
> +			putstr("# TPIDR2 corrupted in parent\n");
> +			return 0;
> +		}
> +
> +		return WEXITSTATUS(status);
> +	}
> +}
> +
> +#define run_test(name)			     \
> +	if (name()) {			     \
> +		tests_passed++;		     \
> +	} else {			     \
> +		tests_failed++;		     \
> +		putstr("not ");		     \
> +	}				     \
> +	putstr("ok ");			     \
> +	putnum(++tests_run);		     \
> +	putstr(" " #name "\n");
> +
> +int main(int argc, char **argv)
> +{
> +	int ret, i;
> +
> +	putstr("TAP version 13\n");
> +	putstr("1..");
> +	putnum(EXPECTED_TESTS);
> +	putstr("\n");
> +
> +	putstr("# PID: ");
> +	putnum(getpid());
> +	putstr("\n");
> +
> +	/*
> +	 * This test is run with nolibc which doesn't support hwcap and
> +	 * it's probably disproportionate to implement so instead check
> +	 * for the default vector length configuration in /proc.
> +	 */
> +	ret = open("/proc/sys/abi/sme_default_vector_length", O_RDONLY, 0);
> +	if (ret >= 0) {
> +		run_test(default_value);
> +		run_test(write_read);
> +		run_test(write_sleep_read);
> +		run_test(write_fork_read);
> +		run_test(write_clone_read);
> +
> +	} else {
> +		putstr("# SME support not present\n");
> +
> +		for (i = 0; i < EXPECTED_TESTS; i++) {
> +			putstr("ok ");
> +			putnum(i);
> +			putstr(" skipped, TPIDR2 not supported\n");
> +		}
> +
> +		tests_skipped += EXPECTED_TESTS;
> +	}
> +
> +	print_summary();
> +
> +	return 0;
> +}
> 

With the comments explained or addressed:

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

thanks,
-- Shuah



More information about the linux-arm-kernel mailing list