Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

selftests: vDSO: fix the way vDSO functions are called for powerpc

vdso_test_correctness test fails on powerpc:

~ # ./vdso_test_correctness
...
[RUN] Testing clock_gettime for clock CLOCK_REALTIME_ALARM (8)...
[FAIL] No such clock, but __vdso_clock_gettime returned 22
[RUN] Testing clock_gettime for clock CLOCK_BOOTTIME_ALARM (9)...
[FAIL] No such clock, but __vdso_clock_gettime returned 22
[RUN] Testing clock_gettime for clock CLOCK_SGI_CYCLE (10)...
[FAIL] No such clock, but __vdso_clock_gettime returned 22
...
[RUN] Testing clock_gettime for clock invalid (-1)...
[FAIL] No such clock, but __vdso_clock_gettime returned 22
[RUN] Testing clock_gettime for clock invalid (-2147483648)...
[FAIL] No such clock, but __vdso_clock_gettime returned 22
[RUN] Testing clock_gettime for clock invalid (2147483647)...
[FAIL] No such clock, but __vdso_clock_gettime returned 22

On powerpc, a call to a VDSO function is not an ordinary C function
call. Unlike several architectures which returns a negative error code
in case of an error, powerpc sets CR[SO] and returns the error code
as a positive value.

Define and use a macro called VDSO_CALL() which takes a pointer
to the function to call, the number of arguments and the arguments.

Also update ABI vdso documentation to reflect this subtlety.

Provide a specific version of VDSO_CALL() for powerpc that negates
the error code on return when CR[SO] is set.

Fixes: c7e5789b24d3 ("kselftest: Move test_vdso to the vDSO test suite")
Fixes: 2e9a97256616 ("selftests: vdso: Add a selftest for vDSO getcpu()")
Fixes: 693f5ca08ca0 ("kselftest: Extend vDSO selftest")
Fixes: b2f1c3db2887 ("kselftest: Extend vdso correctness test to clock_gettime64")
Fixes: 4920a2590e91 ("selftests/vDSO: add tests for vgetrandom")
Signed-off-by: Christophe Leroy <christophe.leroy@csgroup.eu>
Acked-by: Shuah Khan <skhan@linuxfoundation.org>
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>

authored by

Christophe Leroy and committed by
Jason A. Donenfeld
6eda706a ba83b323

+95 -18
+5 -3
Documentation/ABI/stable/vdso
··· 9 9 the vDSO and it often contains useful and highly-optimized alternatives 10 10 to real syscalls. 11 11 12 - These functions are called just like ordinary C function according to 13 - your platform's ABI. Call them from a sensible context. (For example, 14 - if you set CS on x86 to something strange, the vDSO functions are 12 + These functions are called according to your platform's ABI. On many 13 + platforms they are called just like ordinary C function. On other platforms 14 + (ex: powerpc) they are called with the same convention as system calls which 15 + is different from ordinary C functions. Call them from a sensible context. 16 + (For example, if you set CS on x86 to something strange, the vDSO functions are 15 17 within their rights to crash.) In addition, if you pass a bad 16 18 pointer to a vDSO function, you might get SIGSEGV instead of -EFAULT. 17 19
+70
tools/testing/selftests/vDSO/vdso_call.h
··· 1 + /* SPDX-License-Identifier: GPL-2.0 */ 2 + /* 3 + * Macro to call vDSO functions 4 + * 5 + * Copyright (C) 2024 Christophe Leroy <christophe.leroy@csgroup.eu>, CS GROUP France 6 + */ 7 + #ifndef __VDSO_CALL_H__ 8 + #define __VDSO_CALL_H__ 9 + 10 + #ifdef __powerpc__ 11 + 12 + #define LOADARGS_1(fn, __arg1) do { \ 13 + _r0 = fn; \ 14 + _r3 = (long)__arg1; \ 15 + } while (0) 16 + 17 + #define LOADARGS_2(fn, __arg1, __arg2) do { \ 18 + _r0 = fn; \ 19 + _r3 = (long)__arg1; \ 20 + _r4 = (long)__arg2; \ 21 + } while (0) 22 + 23 + #define LOADARGS_3(fn, __arg1, __arg2, __arg3) do { \ 24 + _r0 = fn; \ 25 + _r3 = (long)__arg1; \ 26 + _r4 = (long)__arg2; \ 27 + _r5 = (long)__arg3; \ 28 + } while (0) 29 + 30 + #define LOADARGS_5(fn, __arg1, __arg2, __arg3, __arg4, __arg5) do { \ 31 + _r0 = fn; \ 32 + _r3 = (long)__arg1; \ 33 + _r4 = (long)__arg2; \ 34 + _r5 = (long)__arg3; \ 35 + _r6 = (long)__arg4; \ 36 + _r7 = (long)__arg5; \ 37 + } while (0) 38 + 39 + #define VDSO_CALL(fn, nr, args...) ({ \ 40 + register void *_r0 asm ("r0"); \ 41 + register long _r3 asm ("r3"); \ 42 + register long _r4 asm ("r4"); \ 43 + register long _r5 asm ("r5"); \ 44 + register long _r6 asm ("r6"); \ 45 + register long _r7 asm ("r7"); \ 46 + register long _r8 asm ("r8"); \ 47 + register long _rval asm ("r3"); \ 48 + \ 49 + LOADARGS_##nr(fn, args); \ 50 + \ 51 + asm volatile( \ 52 + " mtctr %0\n" \ 53 + " bctrl\n" \ 54 + " bns+ 1f\n" \ 55 + " neg 3, 3\n" \ 56 + "1:" \ 57 + : "+r" (_r0), "=r" (_r3), "+r" (_r4), "+r" (_r5), \ 58 + "+r" (_r6), "+r" (_r7), "+r" (_r8) \ 59 + : "r" (_rval) \ 60 + : "r9", "r10", "r11", "r12", "cr0", "cr1", "cr5", \ 61 + "cr6", "cr7", "xer", "lr", "ctr", "memory" \ 62 + ); \ 63 + _rval; \ 64 + }) 65 + 66 + #else 67 + #define VDSO_CALL(fn, nr, args...) fn(args) 68 + #endif 69 + 70 + #endif
+5 -4
tools/testing/selftests/vDSO/vdso_test_abi.c
··· 20 20 21 21 #include "../kselftest.h" 22 22 #include "vdso_config.h" 23 + #include "vdso_call.h" 23 24 24 25 extern void *vdso_sym(const char *version, const char *name); 25 26 extern void vdso_init_from_sysinfo_ehdr(uintptr_t base); ··· 62 61 } 63 62 64 63 struct timeval tv; 65 - long ret = vdso_gettimeofday(&tv, 0); 64 + long ret = VDSO_CALL(vdso_gettimeofday, 2, &tv, 0); 66 65 67 66 if (ret == 0) { 68 67 ksft_print_msg("The time is %lld.%06lld\n", ··· 87 86 } 88 87 89 88 struct timespec ts; 90 - long ret = vdso_clock_gettime(clk_id, &ts); 89 + long ret = VDSO_CALL(vdso_clock_gettime, 2, clk_id, &ts); 91 90 92 91 if (ret == 0) { 93 92 ksft_print_msg("The time is %lld.%06lld\n", ··· 112 111 return; 113 112 } 114 113 115 - long ret = vdso_time(NULL); 114 + long ret = VDSO_CALL(vdso_time, 1, NULL); 116 115 117 116 if (ret > 0) { 118 117 ksft_print_msg("The time in hours since January 1, 1970 is %lld\n", ··· 139 138 } 140 139 141 140 struct timespec ts, sys_ts; 142 - long ret = vdso_clock_getres(clk_id, &ts); 141 + long ret = VDSO_CALL(vdso_clock_getres, 2, clk_id, &ts); 143 142 144 143 if (ret == 0) { 145 144 ksft_print_msg("The vdso resolution is %lld %lld\n",
+8 -7
tools/testing/selftests/vDSO/vdso_test_correctness.c
··· 20 20 #include <limits.h> 21 21 22 22 #include "vdso_config.h" 23 + #include "vdso_call.h" 23 24 #include "../kselftest.h" 24 25 25 26 static const char **name; ··· 187 186 188 187 ret_sys = sys_getcpu(&cpu_sys, &node_sys, 0); 189 188 if (vdso_getcpu) 190 - ret_vdso = vdso_getcpu(&cpu_vdso, &node_vdso, 0); 189 + ret_vdso = VDSO_CALL(vdso_getcpu, 3, &cpu_vdso, &node_vdso, 0); 191 190 if (vgetcpu) 192 191 ret_vsys = vgetcpu(&cpu_vsys, &node_vsys, 0); 193 192 ··· 270 269 271 270 if (sys_clock_gettime(clock, &start) < 0) { 272 271 if (errno == EINVAL) { 273 - vdso_ret = vdso_clock_gettime(clock, &vdso); 272 + vdso_ret = VDSO_CALL(vdso_clock_gettime, 2, clock, &vdso); 274 273 if (vdso_ret == -EINVAL) { 275 274 printf("[OK]\tNo such clock.\n"); 276 275 } else { ··· 283 282 return; 284 283 } 285 284 286 - vdso_ret = vdso_clock_gettime(clock, &vdso); 285 + vdso_ret = VDSO_CALL(vdso_clock_gettime, 2, clock, &vdso); 287 286 end_ret = sys_clock_gettime(clock, &end); 288 287 289 288 if (vdso_ret != 0 || end_ret != 0) { ··· 332 331 333 332 if (sys_clock_gettime64(clock, &start) < 0) { 334 333 if (errno == EINVAL) { 335 - vdso_ret = vdso_clock_gettime64(clock, &vdso); 334 + vdso_ret = VDSO_CALL(vdso_clock_gettime64, 2, clock, &vdso); 336 335 if (vdso_ret == -EINVAL) { 337 336 printf("[OK]\tNo such clock.\n"); 338 337 } else { ··· 345 344 return; 346 345 } 347 346 348 - vdso_ret = vdso_clock_gettime64(clock, &vdso); 347 + vdso_ret = VDSO_CALL(vdso_clock_gettime64, 2, clock, &vdso); 349 348 end_ret = sys_clock_gettime64(clock, &end); 350 349 351 350 if (vdso_ret != 0 || end_ret != 0) { ··· 402 401 return; 403 402 } 404 403 405 - vdso_ret = vdso_gettimeofday(&vdso, &vdso_tz); 404 + vdso_ret = VDSO_CALL(vdso_gettimeofday, 2, &vdso, &vdso_tz); 406 405 end_ret = sys_gettimeofday(&end, NULL); 407 406 408 407 if (vdso_ret != 0 || end_ret != 0) { ··· 432 431 } 433 432 434 433 /* And make sure that passing NULL for tz doesn't crash. */ 435 - vdso_gettimeofday(&vdso, NULL); 434 + VDSO_CALL(vdso_gettimeofday, 2, &vdso, NULL); 436 435 } 437 436 438 437 int main(int argc, char **argv)
+2 -1
tools/testing/selftests/vDSO/vdso_test_getcpu.c
··· 14 14 #include "../kselftest.h" 15 15 #include "parse_vdso.h" 16 16 #include "vdso_config.h" 17 + #include "vdso_call.h" 17 18 18 19 struct getcpu_cache; 19 20 typedef long (*getcpu_t)(unsigned int *, unsigned int *, ··· 43 42 return KSFT_SKIP; 44 43 } 45 44 46 - ret = get_cpu(&cpu, &node, 0); 45 + ret = VDSO_CALL(get_cpu, 3, &cpu, &node, 0); 47 46 if (ret == 0) { 48 47 printf("Running on CPU %u node %u\n", cpu, node); 49 48 } else {
+3 -2
tools/testing/selftests/vDSO/vdso_test_getrandom.c
··· 22 22 #include "../kselftest.h" 23 23 #include "parse_vdso.h" 24 24 #include "vdso_config.h" 25 + #include "vdso_call.h" 25 26 26 27 #ifndef timespecsub 27 28 #define timespecsub(tsp, usp, vsp) \ ··· 117 116 printf("%s is missing!\n", name); 118 117 exit(KSFT_FAIL); 119 118 } 120 - ret = vgrnd.fn(NULL, 0, 0, &vgrnd.params, ~0UL); 119 + ret = VDSO_CALL(vgrnd.fn, 5, NULL, 0, 0, &vgrnd.params, ~0UL); 121 120 if (ret == -ENOSYS) { 122 121 printf("unsupported architecture\n"); 123 122 exit(KSFT_SKIP); ··· 138 137 exit(KSFT_FAIL); 139 138 } 140 139 } 141 - return vgrnd.fn(buf, len, flags, state, vgrnd.params.size_of_opaque_state); 140 + return VDSO_CALL(vgrnd.fn, 5, buf, len, flags, state, vgrnd.params.size_of_opaque_state); 142 141 } 143 142 144 143 enum { TRIALS = 25000000, THREADS = 256 };
+2 -1
tools/testing/selftests/vDSO/vdso_test_gettimeofday.c
··· 19 19 #include "../kselftest.h" 20 20 #include "parse_vdso.h" 21 21 #include "vdso_config.h" 22 + #include "vdso_call.h" 22 23 23 24 int main(int argc, char **argv) 24 25 { ··· 44 43 } 45 44 46 45 struct timeval tv; 47 - long ret = gtod(&tv, 0); 46 + long ret = VDSO_CALL(gtod, 2, &tv, 0); 48 47 49 48 if (ret == 0) { 50 49 printf("The time is %lld.%06lld\n",