1 /* SPDX-License-Identifier: LGPL-2.1 OR MIT */
2 
3 /*
4  * RSEQ_SIG uses the trap4 instruction. As Linux does not make use of the
5  * access-register mode nor the linkage stack this instruction will always
6  * cause a special-operation exception (the trap-enabled bit in the DUCT
7  * is and will stay 0). The instruction pattern is
8  *	b2 ff 0f ff	trap4	4095(%r0)
9  */
10 #define RSEQ_SIG	0xB2FF0FFF
11 
12 #define rseq_smp_mb()	__asm__ __volatile__ ("bcr 15,0" ::: "memory")
13 #define rseq_smp_rmb()	rseq_smp_mb()
14 #define rseq_smp_wmb()	rseq_smp_mb()
15 
16 #define rseq_smp_load_acquire(p)					\
17 __extension__ ({							\
18 	__typeof(*p) ____p1 = RSEQ_READ_ONCE(*p);			\
19 	rseq_barrier();							\
20 	____p1;								\
21 })
22 
23 #define rseq_smp_acquire__after_ctrl_dep()	rseq_smp_rmb()
24 
25 #define rseq_smp_store_release(p, v)					\
26 do {									\
27 	rseq_barrier();							\
28 	RSEQ_WRITE_ONCE(*p, v);						\
29 } while (0)
30 
31 #ifdef RSEQ_SKIP_FASTPATH
32 #include "rseq-skip.h"
33 #else /* !RSEQ_SKIP_FASTPATH */
34 
35 #ifdef __s390x__
36 
37 #define LONG_L			"lg"
38 #define LONG_S			"stg"
39 #define LONG_LT_R		"ltgr"
40 #define LONG_CMP		"cg"
41 #define LONG_CMP_R		"cgr"
42 #define LONG_ADDI		"aghi"
43 #define LONG_ADD_R		"agr"
44 
45 #define __RSEQ_ASM_DEFINE_TABLE(label, version, flags,			\
46 				start_ip, post_commit_offset, abort_ip)	\
47 		".pushsection __rseq_table, \"aw\"\n\t"			\
48 		".balign 32\n\t"					\
49 		__rseq_str(label) ":\n\t"				\
50 		".long " __rseq_str(version) ", " __rseq_str(flags) "\n\t" \
51 		".quad " __rseq_str(start_ip) ", " __rseq_str(post_commit_offset) ", " __rseq_str(abort_ip) "\n\t" \
52 		".popsection\n\t"
53 
54 #elif __s390__
55 
56 #define __RSEQ_ASM_DEFINE_TABLE(label, version, flags,			\
57 				start_ip, post_commit_offset, abort_ip)	\
58 		".pushsection __rseq_table, \"aw\"\n\t"			\
59 		".balign 32\n\t"					\
60 		__rseq_str(label) ":\n\t"				\
61 		".long " __rseq_str(version) ", " __rseq_str(flags) "\n\t" \
62 		".long 0x0, " __rseq_str(start_ip) ", 0x0, " __rseq_str(post_commit_offset) ", 0x0, " __rseq_str(abort_ip) "\n\t" \
63 		".popsection\n\t"
64 
65 #define LONG_L			"l"
66 #define LONG_S			"st"
67 #define LONG_LT_R		"ltr"
68 #define LONG_CMP		"c"
69 #define LONG_CMP_R		"cr"
70 #define LONG_ADDI		"ahi"
71 #define LONG_ADD_R		"ar"
72 
73 #endif
74 
75 #define RSEQ_ASM_DEFINE_TABLE(label, start_ip, post_commit_ip, abort_ip) \
76 	__RSEQ_ASM_DEFINE_TABLE(label, 0x0, 0x0, start_ip,		\
77 				(post_commit_ip - start_ip), abort_ip)
78 
79 #define RSEQ_ASM_STORE_RSEQ_CS(label, cs_label, rseq_cs)		\
80 		RSEQ_INJECT_ASM(1)					\
81 		"larl %%r0, " __rseq_str(cs_label) "\n\t"		\
82 		LONG_S " %%r0, %[" __rseq_str(rseq_cs) "]\n\t"		\
83 		__rseq_str(label) ":\n\t"
84 
85 #define RSEQ_ASM_CMP_CPU_ID(cpu_id, current_cpu_id, label)		\
86 		RSEQ_INJECT_ASM(2)					\
87 		"c %[" __rseq_str(cpu_id) "], %[" __rseq_str(current_cpu_id) "]\n\t" \
88 		"jnz " __rseq_str(label) "\n\t"
89 
90 #define RSEQ_ASM_DEFINE_ABORT(label, teardown, abort_label)		\
91 		".pushsection __rseq_failure, \"ax\"\n\t"		\
92 		".long " __rseq_str(RSEQ_SIG) "\n\t"			\
93 		__rseq_str(label) ":\n\t"				\
94 		teardown						\
95 		"j %l[" __rseq_str(abort_label) "]\n\t"			\
96 		".popsection\n\t"
97 
98 #define RSEQ_ASM_DEFINE_CMPFAIL(label, teardown, cmpfail_label)		\
99 		".pushsection __rseq_failure, \"ax\"\n\t"		\
100 		__rseq_str(label) ":\n\t"				\
101 		teardown						\
102 		"j %l[" __rseq_str(cmpfail_label) "]\n\t"		\
103 		".popsection\n\t"
104 
105 static inline __attribute__((always_inline))
106 int rseq_cmpeqv_storev(intptr_t *v, intptr_t expect, intptr_t newv, int cpu)
107 {
108 	RSEQ_INJECT_C(9)
109 
110 	__asm__ __volatile__ goto (
111 		RSEQ_ASM_DEFINE_TABLE(3, 1f, 2f, 4f) /* start, commit, abort */
112 		/* Start rseq by storing table entry pointer into rseq_cs. */
113 		RSEQ_ASM_STORE_RSEQ_CS(1, 3b, rseq_cs)
114 		RSEQ_ASM_CMP_CPU_ID(cpu_id, current_cpu_id, 4f)
115 		RSEQ_INJECT_ASM(3)
116 		LONG_CMP " %[expect], %[v]\n\t"
117 		"jnz %l[cmpfail]\n\t"
118 		RSEQ_INJECT_ASM(4)
119 #ifdef RSEQ_COMPARE_TWICE
120 		RSEQ_ASM_CMP_CPU_ID(cpu_id, current_cpu_id, %l[error1])
121 		LONG_CMP " %[expect], %[v]\n\t"
122 		"jnz %l[error2]\n\t"
123 #endif
124 		/* final store */
125 		LONG_S " %[newv], %[v]\n\t"
126 		"2:\n\t"
127 		RSEQ_INJECT_ASM(5)
128 		RSEQ_ASM_DEFINE_ABORT(4, "", abort)
129 		: /* gcc asm goto does not allow outputs */
130 		: [cpu_id]		"r" (cpu),
131 		  [current_cpu_id]	"m" (__rseq_abi.cpu_id),
132 		  [rseq_cs]		"m" (__rseq_abi.rseq_cs),
133 		  [v]			"m" (*v),
134 		  [expect]		"r" (expect),
135 		  [newv]		"r" (newv)
136 		  RSEQ_INJECT_INPUT
137 		: "memory", "cc", "r0"
138 		  RSEQ_INJECT_CLOBBER
139 		: abort, cmpfail
140 #ifdef RSEQ_COMPARE_TWICE
141 		  , error1, error2
142 #endif
143 	);
144 	return 0;
145 abort:
146 	RSEQ_INJECT_FAILED
147 	return -1;
148 cmpfail:
149 	return 1;
150 #ifdef RSEQ_COMPARE_TWICE
151 error1:
152 	rseq_bug("cpu_id comparison failed");
153 error2:
154 	rseq_bug("expected value comparison failed");
155 #endif
156 }
157 
158 /*
159  * Compare @v against @expectnot. When it does _not_ match, load @v
160  * into @load, and store the content of *@v + voffp into @v.
161  */
162 static inline __attribute__((always_inline))
163 int rseq_cmpnev_storeoffp_load(intptr_t *v, intptr_t expectnot,
164 			       off_t voffp, intptr_t *load, int cpu)
165 {
166 	RSEQ_INJECT_C(9)
167 
168 	__asm__ __volatile__ goto (
169 		RSEQ_ASM_DEFINE_TABLE(3, 1f, 2f, 4f) /* start, commit, abort */
170 		/* Start rseq by storing table entry pointer into rseq_cs. */
171 		RSEQ_ASM_STORE_RSEQ_CS(1, 3b, rseq_cs)
172 		RSEQ_ASM_CMP_CPU_ID(cpu_id, current_cpu_id, 4f)
173 		RSEQ_INJECT_ASM(3)
174 		LONG_L " %%r1, %[v]\n\t"
175 		LONG_CMP_R " %%r1, %[expectnot]\n\t"
176 		"je %l[cmpfail]\n\t"
177 		RSEQ_INJECT_ASM(4)
178 #ifdef RSEQ_COMPARE_TWICE
179 		RSEQ_ASM_CMP_CPU_ID(cpu_id, current_cpu_id, %l[error1])
180 		LONG_L " %%r1, %[v]\n\t"
181 		LONG_CMP_R " %%r1, %[expectnot]\n\t"
182 		"je %l[error2]\n\t"
183 #endif
184 		LONG_S " %%r1, %[load]\n\t"
185 		LONG_ADD_R " %%r1, %[voffp]\n\t"
186 		LONG_L " %%r1, 0(%%r1)\n\t"
187 		/* final store */
188 		LONG_S " %%r1, %[v]\n\t"
189 		"2:\n\t"
190 		RSEQ_INJECT_ASM(5)
191 		RSEQ_ASM_DEFINE_ABORT(4, "", abort)
192 		: /* gcc asm goto does not allow outputs */
193 		: [cpu_id]		"r" (cpu),
194 		  [current_cpu_id]	"m" (__rseq_abi.cpu_id),
195 		  [rseq_cs]		"m" (__rseq_abi.rseq_cs),
196 		  /* final store input */
197 		  [v]			"m" (*v),
198 		  [expectnot]		"r" (expectnot),
199 		  [voffp]		"r" (voffp),
200 		  [load]		"m" (*load)
201 		  RSEQ_INJECT_INPUT
202 		: "memory", "cc", "r0", "r1"
203 		  RSEQ_INJECT_CLOBBER
204 		: abort, cmpfail
205 #ifdef RSEQ_COMPARE_TWICE
206 		  , error1, error2
207 #endif
208 	);
209 	return 0;
210 abort:
211 	RSEQ_INJECT_FAILED
212 	return -1;
213 cmpfail:
214 	return 1;
215 #ifdef RSEQ_COMPARE_TWICE
216 error1:
217 	rseq_bug("cpu_id comparison failed");
218 error2:
219 	rseq_bug("expected value comparison failed");
220 #endif
221 }
222 
223 static inline __attribute__((always_inline))
224 int rseq_addv(intptr_t *v, intptr_t count, int cpu)
225 {
226 	RSEQ_INJECT_C(9)
227 
228 	__asm__ __volatile__ goto (
229 		RSEQ_ASM_DEFINE_TABLE(3, 1f, 2f, 4f) /* start, commit, abort */
230 		/* Start rseq by storing table entry pointer into rseq_cs. */
231 		RSEQ_ASM_STORE_RSEQ_CS(1, 3b, rseq_cs)
232 		RSEQ_ASM_CMP_CPU_ID(cpu_id, current_cpu_id, 4f)
233 		RSEQ_INJECT_ASM(3)
234 #ifdef RSEQ_COMPARE_TWICE
235 		RSEQ_ASM_CMP_CPU_ID(cpu_id, current_cpu_id, %l[error1])
236 #endif
237 		LONG_L " %%r0, %[v]\n\t"
238 		LONG_ADD_R " %%r0, %[count]\n\t"
239 		/* final store */
240 		LONG_S " %%r0, %[v]\n\t"
241 		"2:\n\t"
242 		RSEQ_INJECT_ASM(4)
243 		RSEQ_ASM_DEFINE_ABORT(4, "", abort)
244 		: /* gcc asm goto does not allow outputs */
245 		: [cpu_id]		"r" (cpu),
246 		  [current_cpu_id]	"m" (__rseq_abi.cpu_id),
247 		  [rseq_cs]		"m" (__rseq_abi.rseq_cs),
248 		  /* final store input */
249 		  [v]			"m" (*v),
250 		  [count]		"r" (count)
251 		  RSEQ_INJECT_INPUT
252 		: "memory", "cc", "r0"
253 		  RSEQ_INJECT_CLOBBER
254 		: abort
255 #ifdef RSEQ_COMPARE_TWICE
256 		  , error1
257 #endif
258 	);
259 	return 0;
260 abort:
261 	RSEQ_INJECT_FAILED
262 	return -1;
263 #ifdef RSEQ_COMPARE_TWICE
264 error1:
265 	rseq_bug("cpu_id comparison failed");
266 #endif
267 }
268 
269 static inline __attribute__((always_inline))
270 int rseq_cmpeqv_trystorev_storev(intptr_t *v, intptr_t expect,
271 				 intptr_t *v2, intptr_t newv2,
272 				 intptr_t newv, int cpu)
273 {
274 	RSEQ_INJECT_C(9)
275 
276 	__asm__ __volatile__ goto (
277 		RSEQ_ASM_DEFINE_TABLE(3, 1f, 2f, 4f) /* start, commit, abort */
278 		/* Start rseq by storing table entry pointer into rseq_cs. */
279 		RSEQ_ASM_STORE_RSEQ_CS(1, 3b, rseq_cs)
280 		RSEQ_ASM_CMP_CPU_ID(cpu_id, current_cpu_id, 4f)
281 		RSEQ_INJECT_ASM(3)
282 		LONG_CMP " %[expect], %[v]\n\t"
283 		"jnz %l[cmpfail]\n\t"
284 		RSEQ_INJECT_ASM(4)
285 #ifdef RSEQ_COMPARE_TWICE
286 		RSEQ_ASM_CMP_CPU_ID(cpu_id, current_cpu_id, %l[error1])
287 		LONG_CMP " %[expect], %[v]\n\t"
288 		"jnz %l[error2]\n\t"
289 #endif
290 		/* try store */
291 		LONG_S " %[newv2], %[v2]\n\t"
292 		RSEQ_INJECT_ASM(5)
293 		/* final store */
294 		LONG_S " %[newv], %[v]\n\t"
295 		"2:\n\t"
296 		RSEQ_INJECT_ASM(6)
297 		RSEQ_ASM_DEFINE_ABORT(4, "", abort)
298 		: /* gcc asm goto does not allow outputs */
299 		: [cpu_id]		"r" (cpu),
300 		  [current_cpu_id]	"m" (__rseq_abi.cpu_id),
301 		  [rseq_cs]		"m" (__rseq_abi.rseq_cs),
302 		  /* try store input */
303 		  [v2]			"m" (*v2),
304 		  [newv2]		"r" (newv2),
305 		  /* final store input */
306 		  [v]			"m" (*v),
307 		  [expect]		"r" (expect),
308 		  [newv]		"r" (newv)
309 		  RSEQ_INJECT_INPUT
310 		: "memory", "cc", "r0"
311 		  RSEQ_INJECT_CLOBBER
312 		: abort, cmpfail
313 #ifdef RSEQ_COMPARE_TWICE
314 		  , error1, error2
315 #endif
316 	);
317 	return 0;
318 abort:
319 	RSEQ_INJECT_FAILED
320 	return -1;
321 cmpfail:
322 	return 1;
323 #ifdef RSEQ_COMPARE_TWICE
324 error1:
325 	rseq_bug("cpu_id comparison failed");
326 error2:
327 	rseq_bug("expected value comparison failed");
328 #endif
329 }
330 
331 /* s390 is TSO. */
332 static inline __attribute__((always_inline))
333 int rseq_cmpeqv_trystorev_storev_release(intptr_t *v, intptr_t expect,
334 					 intptr_t *v2, intptr_t newv2,
335 					 intptr_t newv, int cpu)
336 {
337 	return rseq_cmpeqv_trystorev_storev(v, expect, v2, newv2, newv, cpu);
338 }
339 
340 static inline __attribute__((always_inline))
341 int rseq_cmpeqv_cmpeqv_storev(intptr_t *v, intptr_t expect,
342 			      intptr_t *v2, intptr_t expect2,
343 			      intptr_t newv, int cpu)
344 {
345 	RSEQ_INJECT_C(9)
346 
347 	__asm__ __volatile__ goto (
348 		RSEQ_ASM_DEFINE_TABLE(3, 1f, 2f, 4f) /* start, commit, abort */
349 		/* Start rseq by storing table entry pointer into rseq_cs. */
350 		RSEQ_ASM_STORE_RSEQ_CS(1, 3b, rseq_cs)
351 		RSEQ_ASM_CMP_CPU_ID(cpu_id, current_cpu_id, 4f)
352 		RSEQ_INJECT_ASM(3)
353 		LONG_CMP " %[expect], %[v]\n\t"
354 		"jnz %l[cmpfail]\n\t"
355 		RSEQ_INJECT_ASM(4)
356 		LONG_CMP " %[expect2], %[v2]\n\t"
357 		"jnz %l[cmpfail]\n\t"
358 		RSEQ_INJECT_ASM(5)
359 #ifdef RSEQ_COMPARE_TWICE
360 		RSEQ_ASM_CMP_CPU_ID(cpu_id, current_cpu_id, %l[error1])
361 		LONG_CMP " %[expect], %[v]\n\t"
362 		"jnz %l[error2]\n\t"
363 		LONG_CMP " %[expect2], %[v2]\n\t"
364 		"jnz %l[error3]\n\t"
365 #endif
366 		/* final store */
367 		LONG_S " %[newv], %[v]\n\t"
368 		"2:\n\t"
369 		RSEQ_INJECT_ASM(6)
370 		RSEQ_ASM_DEFINE_ABORT(4, "", abort)
371 		: /* gcc asm goto does not allow outputs */
372 		: [cpu_id]		"r" (cpu),
373 		  [current_cpu_id]	"m" (__rseq_abi.cpu_id),
374 		  [rseq_cs]		"m" (__rseq_abi.rseq_cs),
375 		  /* cmp2 input */
376 		  [v2]			"m" (*v2),
377 		  [expect2]		"r" (expect2),
378 		  /* final store input */
379 		  [v]			"m" (*v),
380 		  [expect]		"r" (expect),
381 		  [newv]		"r" (newv)
382 		  RSEQ_INJECT_INPUT
383 		: "memory", "cc", "r0"
384 		  RSEQ_INJECT_CLOBBER
385 		: abort, cmpfail
386 #ifdef RSEQ_COMPARE_TWICE
387 		  , error1, error2, error3
388 #endif
389 	);
390 	return 0;
391 abort:
392 	RSEQ_INJECT_FAILED
393 	return -1;
394 cmpfail:
395 	return 1;
396 #ifdef RSEQ_COMPARE_TWICE
397 error1:
398 	rseq_bug("cpu_id comparison failed");
399 error2:
400 	rseq_bug("1st expected value comparison failed");
401 error3:
402 	rseq_bug("2nd expected value comparison failed");
403 #endif
404 }
405 
406 static inline __attribute__((always_inline))
407 int rseq_cmpeqv_trymemcpy_storev(intptr_t *v, intptr_t expect,
408 				 void *dst, void *src, size_t len,
409 				 intptr_t newv, int cpu)
410 {
411 	uint64_t rseq_scratch[3];
412 
413 	RSEQ_INJECT_C(9)
414 
415 	__asm__ __volatile__ goto (
416 		RSEQ_ASM_DEFINE_TABLE(3, 1f, 2f, 4f) /* start, commit, abort */
417 		LONG_S " %[src], %[rseq_scratch0]\n\t"
418 		LONG_S " %[dst], %[rseq_scratch1]\n\t"
419 		LONG_S " %[len], %[rseq_scratch2]\n\t"
420 		/* Start rseq by storing table entry pointer into rseq_cs. */
421 		RSEQ_ASM_STORE_RSEQ_CS(1, 3b, rseq_cs)
422 		RSEQ_ASM_CMP_CPU_ID(cpu_id, current_cpu_id, 4f)
423 		RSEQ_INJECT_ASM(3)
424 		LONG_CMP " %[expect], %[v]\n\t"
425 		"jnz 5f\n\t"
426 		RSEQ_INJECT_ASM(4)
427 #ifdef RSEQ_COMPARE_TWICE
428 		RSEQ_ASM_CMP_CPU_ID(cpu_id, current_cpu_id, 6f)
429 		LONG_CMP " %[expect], %[v]\n\t"
430 		"jnz 7f\n\t"
431 #endif
432 		/* try memcpy */
433 		LONG_LT_R " %[len], %[len]\n\t"
434 		"jz 333f\n\t"
435 		"222:\n\t"
436 		"ic %%r0,0(%[src])\n\t"
437 		"stc %%r0,0(%[dst])\n\t"
438 		LONG_ADDI " %[src], 1\n\t"
439 		LONG_ADDI " %[dst], 1\n\t"
440 		LONG_ADDI " %[len], -1\n\t"
441 		"jnz 222b\n\t"
442 		"333:\n\t"
443 		RSEQ_INJECT_ASM(5)
444 		/* final store */
445 		LONG_S " %[newv], %[v]\n\t"
446 		"2:\n\t"
447 		RSEQ_INJECT_ASM(6)
448 		/* teardown */
449 		LONG_L " %[len], %[rseq_scratch2]\n\t"
450 		LONG_L " %[dst], %[rseq_scratch1]\n\t"
451 		LONG_L " %[src], %[rseq_scratch0]\n\t"
452 		RSEQ_ASM_DEFINE_ABORT(4,
453 			LONG_L " %[len], %[rseq_scratch2]\n\t"
454 			LONG_L " %[dst], %[rseq_scratch1]\n\t"
455 			LONG_L " %[src], %[rseq_scratch0]\n\t",
456 			abort)
457 		RSEQ_ASM_DEFINE_CMPFAIL(5,
458 			LONG_L " %[len], %[rseq_scratch2]\n\t"
459 			LONG_L " %[dst], %[rseq_scratch1]\n\t"
460 			LONG_L " %[src], %[rseq_scratch0]\n\t",
461 			cmpfail)
462 #ifdef RSEQ_COMPARE_TWICE
463 		RSEQ_ASM_DEFINE_CMPFAIL(6,
464 			LONG_L " %[len], %[rseq_scratch2]\n\t"
465 			LONG_L " %[dst], %[rseq_scratch1]\n\t"
466 			LONG_L " %[src], %[rseq_scratch0]\n\t",
467 			error1)
468 		RSEQ_ASM_DEFINE_CMPFAIL(7,
469 			LONG_L " %[len], %[rseq_scratch2]\n\t"
470 			LONG_L " %[dst], %[rseq_scratch1]\n\t"
471 			LONG_L " %[src], %[rseq_scratch0]\n\t",
472 			error2)
473 #endif
474 		: /* gcc asm goto does not allow outputs */
475 		: [cpu_id]		"r" (cpu),
476 		  [current_cpu_id]	"m" (__rseq_abi.cpu_id),
477 		  [rseq_cs]		"m" (__rseq_abi.rseq_cs),
478 		  /* final store input */
479 		  [v]			"m" (*v),
480 		  [expect]		"r" (expect),
481 		  [newv]		"r" (newv),
482 		  /* try memcpy input */
483 		  [dst]			"r" (dst),
484 		  [src]			"r" (src),
485 		  [len]			"r" (len),
486 		  [rseq_scratch0]	"m" (rseq_scratch[0]),
487 		  [rseq_scratch1]	"m" (rseq_scratch[1]),
488 		  [rseq_scratch2]	"m" (rseq_scratch[2])
489 		  RSEQ_INJECT_INPUT
490 		: "memory", "cc", "r0"
491 		  RSEQ_INJECT_CLOBBER
492 		: abort, cmpfail
493 #ifdef RSEQ_COMPARE_TWICE
494 		  , error1, error2
495 #endif
496 	);
497 	return 0;
498 abort:
499 	RSEQ_INJECT_FAILED
500 	return -1;
501 cmpfail:
502 	return 1;
503 #ifdef RSEQ_COMPARE_TWICE
504 error1:
505 	rseq_bug("cpu_id comparison failed");
506 error2:
507 	rseq_bug("expected value comparison failed");
508 #endif
509 }
510 
511 /* s390 is TSO. */
512 static inline __attribute__((always_inline))
513 int rseq_cmpeqv_trymemcpy_storev_release(intptr_t *v, intptr_t expect,
514 					 void *dst, void *src, size_t len,
515 					 intptr_t newv, int cpu)
516 {
517 	return rseq_cmpeqv_trymemcpy_storev(v, expect, dst, src, len,
518 					    newv, cpu);
519 }
520 #endif /* !RSEQ_SKIP_FASTPATH */
521