1// SPDX-License-Identifier: GPL-2.0-only
2
3#include <linux/sched.h>
4#include <linux/wait.h>
5
6#define SYS_TPIDR2 "S3_3_C13_C0_5"
7
8#define EXPECTED_TESTS 5
9
10static void putstr(const char *str)
11{
12	write(1, str, strlen(str));
13}
14
15static void putnum(unsigned int num)
16{
17	char c;
18
19	if (num / 10)
20		putnum(num / 10);
21
22	c = '0' + (num % 10);
23	write(1, &c, 1);
24}
25
26static int tests_run;
27static int tests_passed;
28static int tests_failed;
29static int tests_skipped;
30
31static void set_tpidr2(uint64_t val)
32{
33	asm volatile (
34		"msr	" SYS_TPIDR2 ", %0\n"
35		:
36		: "r"(val)
37		: "cc");
38}
39
40static uint64_t get_tpidr2(void)
41{
42	uint64_t val;
43
44	asm volatile (
45		"mrs	%0, " SYS_TPIDR2 "\n"
46		: "=r"(val)
47		:
48		: "cc");
49
50	return val;
51}
52
53static void print_summary(void)
54{
55	if (tests_passed + tests_failed + tests_skipped != EXPECTED_TESTS)
56		putstr("# UNEXPECTED TEST COUNT: ");
57
58	putstr("# Totals: pass:");
59	putnum(tests_passed);
60	putstr(" fail:");
61	putnum(tests_failed);
62	putstr(" xfail:0 xpass:0 skip:");
63	putnum(tests_skipped);
64	putstr(" error:0\n");
65}
66
67/* Processes should start with TPIDR2 == 0 */
68static int default_value(void)
69{
70	return get_tpidr2() == 0;
71}
72
73/* If we set TPIDR2 we should read that value */
74static int write_read(void)
75{
76	set_tpidr2(getpid());
77
78	return getpid() == get_tpidr2();
79}
80
81/* If we set a value we should read the same value after scheduling out */
82static int write_sleep_read(void)
83{
84	set_tpidr2(getpid());
85
86	msleep(100);
87
88	return getpid() == get_tpidr2();
89}
90
91/*
92 * If we fork the value in the parent should be unchanged and the
93 * child should start with the same value and be able to set its own
94 * value.
95 */
96static int write_fork_read(void)
97{
98	pid_t newpid, waiting, oldpid;
99	int status;
100
101	set_tpidr2(getpid());
102
103	oldpid = getpid();
104	newpid = fork();
105	if (newpid == 0) {
106		/* In child */
107		if (get_tpidr2() != oldpid) {
108			putstr("# TPIDR2 changed in child: ");
109			putnum(get_tpidr2());
110			putstr("\n");
111			exit(0);
112		}
113
114		set_tpidr2(getpid());
115		if (get_tpidr2() == getpid()) {
116			exit(1);
117		} else {
118			putstr("# Failed to set TPIDR2 in child\n");
119			exit(0);
120		}
121	}
122	if (newpid < 0) {
123		putstr("# fork() failed: -");
124		putnum(-newpid);
125		putstr("\n");
126		return 0;
127	}
128
129	for (;;) {
130		waiting = waitpid(newpid, &status, 0);
131
132		if (waiting < 0) {
133			if (errno == EINTR)
134				continue;
135			putstr("# waitpid() failed: ");
136			putnum(errno);
137			putstr("\n");
138			return 0;
139		}
140		if (waiting != newpid) {
141			putstr("# waitpid() returned wrong PID\n");
142			return 0;
143		}
144
145		if (!WIFEXITED(status)) {
146			putstr("# child did not exit\n");
147			return 0;
148		}
149
150		if (getpid() != get_tpidr2()) {
151			putstr("# TPIDR2 corrupted in parent\n");
152			return 0;
153		}
154
155		return WEXITSTATUS(status);
156	}
157}
158
159/*
160 * sys_clone() has a lot of per architecture variation so just define
161 * it here rather than adding it to nolibc, plus the raw API is a
162 * little more convenient for this test.
163 */
164static int sys_clone(unsigned long clone_flags, unsigned long newsp,
165		     int *parent_tidptr, unsigned long tls,
166		     int *child_tidptr)
167{
168	return my_syscall5(__NR_clone, clone_flags, newsp, parent_tidptr, tls,
169			   child_tidptr);
170}
171
172/*
173 * If we clone with CLONE_SETTLS then the value in the parent should
174 * be unchanged and the child should start with zero and be able to
175 * set its own value.
176 */
177static int write_clone_read(void)
178{
179	int parent_tid, child_tid;
180	pid_t parent, waiting;
181	int ret, status;
182
183	parent = getpid();
184	set_tpidr2(parent);
185
186	ret = sys_clone(CLONE_SETTLS, 0, &parent_tid, 0, &child_tid);
187	if (ret == -1) {
188		putstr("# clone() failed\n");
189		putnum(errno);
190		putstr("\n");
191		return 0;
192	}
193
194	if (ret == 0) {
195		/* In child */
196		if (get_tpidr2() != 0) {
197			putstr("# TPIDR2 non-zero in child: ");
198			putnum(get_tpidr2());
199			putstr("\n");
200			exit(0);
201		}
202
203		if (gettid() == 0)
204			putstr("# Child TID==0\n");
205		set_tpidr2(gettid());
206		if (get_tpidr2() == gettid()) {
207			exit(1);
208		} else {
209			putstr("# Failed to set TPIDR2 in child\n");
210			exit(0);
211		}
212	}
213
214	for (;;) {
215		waiting = wait4(ret, &status, __WCLONE, NULL);
216
217		if (waiting < 0) {
218			if (errno == EINTR)
219				continue;
220			putstr("# wait4() failed: ");
221			putnum(errno);
222			putstr("\n");
223			return 0;
224		}
225		if (waiting != ret) {
226			putstr("# wait4() returned wrong PID ");
227			putnum(waiting);
228			putstr("\n");
229			return 0;
230		}
231
232		if (!WIFEXITED(status)) {
233			putstr("# child did not exit\n");
234			return 0;
235		}
236
237		if (parent != get_tpidr2()) {
238			putstr("# TPIDR2 corrupted in parent\n");
239			return 0;
240		}
241
242		return WEXITSTATUS(status);
243	}
244}
245
246#define run_test(name)			     \
247	if (name()) {			     \
248		tests_passed++;		     \
249	} else {			     \
250		tests_failed++;		     \
251		putstr("not ");		     \
252	}				     \
253	putstr("ok ");			     \
254	putnum(++tests_run);		     \
255	putstr(" " #name "\n");
256
257#define skip_test(name)			     \
258	tests_skipped++;		     \
259	putstr("ok ");			     \
260	putnum(++tests_run);		     \
261	putstr(" # SKIP " #name "\n");
262
263int main(int argc, char **argv)
264{
265	int ret, i;
266
267	putstr("TAP version 13\n");
268	putstr("1..");
269	putnum(EXPECTED_TESTS);
270	putstr("\n");
271
272	putstr("# PID: ");
273	putnum(getpid());
274	putstr("\n");
275
276	/*
277	 * This test is run with nolibc which doesn't support hwcap and
278	 * it's probably disproportionate to implement so instead check
279	 * for the default vector length configuration in /proc.
280	 */
281	ret = open("/proc/sys/abi/sme_default_vector_length", O_RDONLY, 0);
282	if (ret >= 0) {
283		run_test(default_value);
284		run_test(write_read);
285		run_test(write_sleep_read);
286		run_test(write_fork_read);
287		run_test(write_clone_read);
288
289	} else {
290		putstr("# SME support not present\n");
291
292		skip_test(default_value);
293		skip_test(write_read);
294		skip_test(write_sleep_read);
295		skip_test(write_fork_read);
296		skip_test(write_clone_read);
297	}
298
299	print_summary();
300
301	return 0;
302}
303