162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci
362306a36Sopenharmony_ci#include <linux/sched.h>
462306a36Sopenharmony_ci#include <linux/wait.h>
562306a36Sopenharmony_ci
662306a36Sopenharmony_ci#define SYS_TPIDR2 "S3_3_C13_C0_5"
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#define EXPECTED_TESTS 5
962306a36Sopenharmony_ci
1062306a36Sopenharmony_cistatic void putstr(const char *str)
1162306a36Sopenharmony_ci{
1262306a36Sopenharmony_ci	write(1, str, strlen(str));
1362306a36Sopenharmony_ci}
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_cistatic void putnum(unsigned int num)
1662306a36Sopenharmony_ci{
1762306a36Sopenharmony_ci	char c;
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci	if (num / 10)
2062306a36Sopenharmony_ci		putnum(num / 10);
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci	c = '0' + (num % 10);
2362306a36Sopenharmony_ci	write(1, &c, 1);
2462306a36Sopenharmony_ci}
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_cistatic int tests_run;
2762306a36Sopenharmony_cistatic int tests_passed;
2862306a36Sopenharmony_cistatic int tests_failed;
2962306a36Sopenharmony_cistatic int tests_skipped;
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_cistatic void set_tpidr2(uint64_t val)
3262306a36Sopenharmony_ci{
3362306a36Sopenharmony_ci	asm volatile (
3462306a36Sopenharmony_ci		"msr	" SYS_TPIDR2 ", %0\n"
3562306a36Sopenharmony_ci		:
3662306a36Sopenharmony_ci		: "r"(val)
3762306a36Sopenharmony_ci		: "cc");
3862306a36Sopenharmony_ci}
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_cistatic uint64_t get_tpidr2(void)
4162306a36Sopenharmony_ci{
4262306a36Sopenharmony_ci	uint64_t val;
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci	asm volatile (
4562306a36Sopenharmony_ci		"mrs	%0, " SYS_TPIDR2 "\n"
4662306a36Sopenharmony_ci		: "=r"(val)
4762306a36Sopenharmony_ci		:
4862306a36Sopenharmony_ci		: "cc");
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci	return val;
5162306a36Sopenharmony_ci}
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_cistatic void print_summary(void)
5462306a36Sopenharmony_ci{
5562306a36Sopenharmony_ci	if (tests_passed + tests_failed + tests_skipped != EXPECTED_TESTS)
5662306a36Sopenharmony_ci		putstr("# UNEXPECTED TEST COUNT: ");
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci	putstr("# Totals: pass:");
5962306a36Sopenharmony_ci	putnum(tests_passed);
6062306a36Sopenharmony_ci	putstr(" fail:");
6162306a36Sopenharmony_ci	putnum(tests_failed);
6262306a36Sopenharmony_ci	putstr(" xfail:0 xpass:0 skip:");
6362306a36Sopenharmony_ci	putnum(tests_skipped);
6462306a36Sopenharmony_ci	putstr(" error:0\n");
6562306a36Sopenharmony_ci}
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci/* Processes should start with TPIDR2 == 0 */
6862306a36Sopenharmony_cistatic int default_value(void)
6962306a36Sopenharmony_ci{
7062306a36Sopenharmony_ci	return get_tpidr2() == 0;
7162306a36Sopenharmony_ci}
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci/* If we set TPIDR2 we should read that value */
7462306a36Sopenharmony_cistatic int write_read(void)
7562306a36Sopenharmony_ci{
7662306a36Sopenharmony_ci	set_tpidr2(getpid());
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci	return getpid() == get_tpidr2();
7962306a36Sopenharmony_ci}
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci/* If we set a value we should read the same value after scheduling out */
8262306a36Sopenharmony_cistatic int write_sleep_read(void)
8362306a36Sopenharmony_ci{
8462306a36Sopenharmony_ci	set_tpidr2(getpid());
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	msleep(100);
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	return getpid() == get_tpidr2();
8962306a36Sopenharmony_ci}
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci/*
9262306a36Sopenharmony_ci * If we fork the value in the parent should be unchanged and the
9362306a36Sopenharmony_ci * child should start with the same value and be able to set its own
9462306a36Sopenharmony_ci * value.
9562306a36Sopenharmony_ci */
9662306a36Sopenharmony_cistatic int write_fork_read(void)
9762306a36Sopenharmony_ci{
9862306a36Sopenharmony_ci	pid_t newpid, waiting, oldpid;
9962306a36Sopenharmony_ci	int status;
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	set_tpidr2(getpid());
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci	oldpid = getpid();
10462306a36Sopenharmony_ci	newpid = fork();
10562306a36Sopenharmony_ci	if (newpid == 0) {
10662306a36Sopenharmony_ci		/* In child */
10762306a36Sopenharmony_ci		if (get_tpidr2() != oldpid) {
10862306a36Sopenharmony_ci			putstr("# TPIDR2 changed in child: ");
10962306a36Sopenharmony_ci			putnum(get_tpidr2());
11062306a36Sopenharmony_ci			putstr("\n");
11162306a36Sopenharmony_ci			exit(0);
11262306a36Sopenharmony_ci		}
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci		set_tpidr2(getpid());
11562306a36Sopenharmony_ci		if (get_tpidr2() == getpid()) {
11662306a36Sopenharmony_ci			exit(1);
11762306a36Sopenharmony_ci		} else {
11862306a36Sopenharmony_ci			putstr("# Failed to set TPIDR2 in child\n");
11962306a36Sopenharmony_ci			exit(0);
12062306a36Sopenharmony_ci		}
12162306a36Sopenharmony_ci	}
12262306a36Sopenharmony_ci	if (newpid < 0) {
12362306a36Sopenharmony_ci		putstr("# fork() failed: -");
12462306a36Sopenharmony_ci		putnum(-newpid);
12562306a36Sopenharmony_ci		putstr("\n");
12662306a36Sopenharmony_ci		return 0;
12762306a36Sopenharmony_ci	}
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci	for (;;) {
13062306a36Sopenharmony_ci		waiting = waitpid(newpid, &status, 0);
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci		if (waiting < 0) {
13362306a36Sopenharmony_ci			if (errno == EINTR)
13462306a36Sopenharmony_ci				continue;
13562306a36Sopenharmony_ci			putstr("# waitpid() failed: ");
13662306a36Sopenharmony_ci			putnum(errno);
13762306a36Sopenharmony_ci			putstr("\n");
13862306a36Sopenharmony_ci			return 0;
13962306a36Sopenharmony_ci		}
14062306a36Sopenharmony_ci		if (waiting != newpid) {
14162306a36Sopenharmony_ci			putstr("# waitpid() returned wrong PID\n");
14262306a36Sopenharmony_ci			return 0;
14362306a36Sopenharmony_ci		}
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci		if (!WIFEXITED(status)) {
14662306a36Sopenharmony_ci			putstr("# child did not exit\n");
14762306a36Sopenharmony_ci			return 0;
14862306a36Sopenharmony_ci		}
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci		if (getpid() != get_tpidr2()) {
15162306a36Sopenharmony_ci			putstr("# TPIDR2 corrupted in parent\n");
15262306a36Sopenharmony_ci			return 0;
15362306a36Sopenharmony_ci		}
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci		return WEXITSTATUS(status);
15662306a36Sopenharmony_ci	}
15762306a36Sopenharmony_ci}
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci/*
16062306a36Sopenharmony_ci * sys_clone() has a lot of per architecture variation so just define
16162306a36Sopenharmony_ci * it here rather than adding it to nolibc, plus the raw API is a
16262306a36Sopenharmony_ci * little more convenient for this test.
16362306a36Sopenharmony_ci */
16462306a36Sopenharmony_cistatic int sys_clone(unsigned long clone_flags, unsigned long newsp,
16562306a36Sopenharmony_ci		     int *parent_tidptr, unsigned long tls,
16662306a36Sopenharmony_ci		     int *child_tidptr)
16762306a36Sopenharmony_ci{
16862306a36Sopenharmony_ci	return my_syscall5(__NR_clone, clone_flags, newsp, parent_tidptr, tls,
16962306a36Sopenharmony_ci			   child_tidptr);
17062306a36Sopenharmony_ci}
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci/*
17362306a36Sopenharmony_ci * If we clone with CLONE_SETTLS then the value in the parent should
17462306a36Sopenharmony_ci * be unchanged and the child should start with zero and be able to
17562306a36Sopenharmony_ci * set its own value.
17662306a36Sopenharmony_ci */
17762306a36Sopenharmony_cistatic int write_clone_read(void)
17862306a36Sopenharmony_ci{
17962306a36Sopenharmony_ci	int parent_tid, child_tid;
18062306a36Sopenharmony_ci	pid_t parent, waiting;
18162306a36Sopenharmony_ci	int ret, status;
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci	parent = getpid();
18462306a36Sopenharmony_ci	set_tpidr2(parent);
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	ret = sys_clone(CLONE_SETTLS, 0, &parent_tid, 0, &child_tid);
18762306a36Sopenharmony_ci	if (ret == -1) {
18862306a36Sopenharmony_ci		putstr("# clone() failed\n");
18962306a36Sopenharmony_ci		putnum(errno);
19062306a36Sopenharmony_ci		putstr("\n");
19162306a36Sopenharmony_ci		return 0;
19262306a36Sopenharmony_ci	}
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_ci	if (ret == 0) {
19562306a36Sopenharmony_ci		/* In child */
19662306a36Sopenharmony_ci		if (get_tpidr2() != 0) {
19762306a36Sopenharmony_ci			putstr("# TPIDR2 non-zero in child: ");
19862306a36Sopenharmony_ci			putnum(get_tpidr2());
19962306a36Sopenharmony_ci			putstr("\n");
20062306a36Sopenharmony_ci			exit(0);
20162306a36Sopenharmony_ci		}
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_ci		if (gettid() == 0)
20462306a36Sopenharmony_ci			putstr("# Child TID==0\n");
20562306a36Sopenharmony_ci		set_tpidr2(gettid());
20662306a36Sopenharmony_ci		if (get_tpidr2() == gettid()) {
20762306a36Sopenharmony_ci			exit(1);
20862306a36Sopenharmony_ci		} else {
20962306a36Sopenharmony_ci			putstr("# Failed to set TPIDR2 in child\n");
21062306a36Sopenharmony_ci			exit(0);
21162306a36Sopenharmony_ci		}
21262306a36Sopenharmony_ci	}
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ci	for (;;) {
21562306a36Sopenharmony_ci		waiting = wait4(ret, &status, __WCLONE, NULL);
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_ci		if (waiting < 0) {
21862306a36Sopenharmony_ci			if (errno == EINTR)
21962306a36Sopenharmony_ci				continue;
22062306a36Sopenharmony_ci			putstr("# wait4() failed: ");
22162306a36Sopenharmony_ci			putnum(errno);
22262306a36Sopenharmony_ci			putstr("\n");
22362306a36Sopenharmony_ci			return 0;
22462306a36Sopenharmony_ci		}
22562306a36Sopenharmony_ci		if (waiting != ret) {
22662306a36Sopenharmony_ci			putstr("# wait4() returned wrong PID ");
22762306a36Sopenharmony_ci			putnum(waiting);
22862306a36Sopenharmony_ci			putstr("\n");
22962306a36Sopenharmony_ci			return 0;
23062306a36Sopenharmony_ci		}
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_ci		if (!WIFEXITED(status)) {
23362306a36Sopenharmony_ci			putstr("# child did not exit\n");
23462306a36Sopenharmony_ci			return 0;
23562306a36Sopenharmony_ci		}
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_ci		if (parent != get_tpidr2()) {
23862306a36Sopenharmony_ci			putstr("# TPIDR2 corrupted in parent\n");
23962306a36Sopenharmony_ci			return 0;
24062306a36Sopenharmony_ci		}
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ci		return WEXITSTATUS(status);
24362306a36Sopenharmony_ci	}
24462306a36Sopenharmony_ci}
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_ci#define run_test(name)			     \
24762306a36Sopenharmony_ci	if (name()) {			     \
24862306a36Sopenharmony_ci		tests_passed++;		     \
24962306a36Sopenharmony_ci	} else {			     \
25062306a36Sopenharmony_ci		tests_failed++;		     \
25162306a36Sopenharmony_ci		putstr("not ");		     \
25262306a36Sopenharmony_ci	}				     \
25362306a36Sopenharmony_ci	putstr("ok ");			     \
25462306a36Sopenharmony_ci	putnum(++tests_run);		     \
25562306a36Sopenharmony_ci	putstr(" " #name "\n");
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_ciint main(int argc, char **argv)
25862306a36Sopenharmony_ci{
25962306a36Sopenharmony_ci	int ret, i;
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_ci	putstr("TAP version 13\n");
26262306a36Sopenharmony_ci	putstr("1..");
26362306a36Sopenharmony_ci	putnum(EXPECTED_TESTS);
26462306a36Sopenharmony_ci	putstr("\n");
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_ci	putstr("# PID: ");
26762306a36Sopenharmony_ci	putnum(getpid());
26862306a36Sopenharmony_ci	putstr("\n");
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_ci	/*
27162306a36Sopenharmony_ci	 * This test is run with nolibc which doesn't support hwcap and
27262306a36Sopenharmony_ci	 * it's probably disproportionate to implement so instead check
27362306a36Sopenharmony_ci	 * for the default vector length configuration in /proc.
27462306a36Sopenharmony_ci	 */
27562306a36Sopenharmony_ci	ret = open("/proc/sys/abi/sme_default_vector_length", O_RDONLY, 0);
27662306a36Sopenharmony_ci	if (ret >= 0) {
27762306a36Sopenharmony_ci		run_test(default_value);
27862306a36Sopenharmony_ci		run_test(write_read);
27962306a36Sopenharmony_ci		run_test(write_sleep_read);
28062306a36Sopenharmony_ci		run_test(write_fork_read);
28162306a36Sopenharmony_ci		run_test(write_clone_read);
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_ci	} else {
28462306a36Sopenharmony_ci		putstr("# SME support not present\n");
28562306a36Sopenharmony_ci
28662306a36Sopenharmony_ci		for (i = 0; i < EXPECTED_TESTS; i++) {
28762306a36Sopenharmony_ci			putstr("ok ");
28862306a36Sopenharmony_ci			putnum(i);
28962306a36Sopenharmony_ci			putstr(" skipped, TPIDR2 not supported\n");
29062306a36Sopenharmony_ci		}
29162306a36Sopenharmony_ci
29262306a36Sopenharmony_ci		tests_skipped += EXPECTED_TESTS;
29362306a36Sopenharmony_ci	}
29462306a36Sopenharmony_ci
29562306a36Sopenharmony_ci	print_summary();
29662306a36Sopenharmony_ci
29762306a36Sopenharmony_ci	return 0;
29862306a36Sopenharmony_ci}
299