Commit 6eda706a authored by Christophe Leroy's avatar Christophe Leroy Committed by Jason A. Donenfeld

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: c7e5789b ("kselftest: Move test_vdso to the vDSO test suite")
Fixes: 2e9a9725 ("selftests: vdso: Add a selftest for vDSO getcpu()")
Fixes: 693f5ca0 ("kselftest: Extend vDSO selftest")
Fixes: b2f1c3db ("kselftest: Extend vdso correctness test to clock_gettime64")
Fixes: 4920a259 ("selftests/vDSO: add tests for vgetrandom")
Signed-off-by: default avatarChristophe Leroy <christophe.leroy@csgroup.eu>
Acked-by: default avatarShuah Khan <skhan@linuxfoundation.org>
Signed-off-by: default avatarJason A. Donenfeld <Jason@zx2c4.com>
parent ba83b323
......@@ -9,9 +9,11 @@ maps an ELF DSO into that program's address space. This DSO is called
the vDSO and it often contains useful and highly-optimized alternatives
to real syscalls.
These functions are called just like ordinary C function according to
your platform's ABI. Call them from a sensible context. (For example,
if you set CS on x86 to something strange, the vDSO functions are
These functions are called according to your platform's ABI. On many
platforms they are called just like ordinary C function. On other platforms
(ex: powerpc) they are called with the same convention as system calls which
is different from ordinary C functions. Call them from a sensible context.
(For example, if you set CS on x86 to something strange, the vDSO functions are
within their rights to crash.) In addition, if you pass a bad
pointer to a vDSO function, you might get SIGSEGV instead of -EFAULT.
......
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Macro to call vDSO functions
*
* Copyright (C) 2024 Christophe Leroy <christophe.leroy@csgroup.eu>, CS GROUP France
*/
#ifndef __VDSO_CALL_H__
#define __VDSO_CALL_H__
#ifdef __powerpc__
#define LOADARGS_1(fn, __arg1) do { \
_r0 = fn; \
_r3 = (long)__arg1; \
} while (0)
#define LOADARGS_2(fn, __arg1, __arg2) do { \
_r0 = fn; \
_r3 = (long)__arg1; \
_r4 = (long)__arg2; \
} while (0)
#define LOADARGS_3(fn, __arg1, __arg2, __arg3) do { \
_r0 = fn; \
_r3 = (long)__arg1; \
_r4 = (long)__arg2; \
_r5 = (long)__arg3; \
} while (0)
#define LOADARGS_5(fn, __arg1, __arg2, __arg3, __arg4, __arg5) do { \
_r0 = fn; \
_r3 = (long)__arg1; \
_r4 = (long)__arg2; \
_r5 = (long)__arg3; \
_r6 = (long)__arg4; \
_r7 = (long)__arg5; \
} while (0)
#define VDSO_CALL(fn, nr, args...) ({ \
register void *_r0 asm ("r0"); \
register long _r3 asm ("r3"); \
register long _r4 asm ("r4"); \
register long _r5 asm ("r5"); \
register long _r6 asm ("r6"); \
register long _r7 asm ("r7"); \
register long _r8 asm ("r8"); \
register long _rval asm ("r3"); \
\
LOADARGS_##nr(fn, args); \
\
asm volatile( \
" mtctr %0\n" \
" bctrl\n" \
" bns+ 1f\n" \
" neg 3, 3\n" \
"1:" \
: "+r" (_r0), "=r" (_r3), "+r" (_r4), "+r" (_r5), \
"+r" (_r6), "+r" (_r7), "+r" (_r8) \
: "r" (_rval) \
: "r9", "r10", "r11", "r12", "cr0", "cr1", "cr5", \
"cr6", "cr7", "xer", "lr", "ctr", "memory" \
); \
_rval; \
})
#else
#define VDSO_CALL(fn, nr, args...) fn(args)
#endif
#endif
......@@ -20,6 +20,7 @@
#include "../kselftest.h"
#include "vdso_config.h"
#include "vdso_call.h"
extern void *vdso_sym(const char *version, const char *name);
extern void vdso_init_from_sysinfo_ehdr(uintptr_t base);
......@@ -61,7 +62,7 @@ static void vdso_test_gettimeofday(void)
}
struct timeval tv;
long ret = vdso_gettimeofday(&tv, 0);
long ret = VDSO_CALL(vdso_gettimeofday, 2, &tv, 0);
if (ret == 0) {
ksft_print_msg("The time is %lld.%06lld\n",
......@@ -86,7 +87,7 @@ static void vdso_test_clock_gettime(clockid_t clk_id)
}
struct timespec ts;
long ret = vdso_clock_gettime(clk_id, &ts);
long ret = VDSO_CALL(vdso_clock_gettime, 2, clk_id, &ts);
if (ret == 0) {
ksft_print_msg("The time is %lld.%06lld\n",
......@@ -111,7 +112,7 @@ static void vdso_test_time(void)
return;
}
long ret = vdso_time(NULL);
long ret = VDSO_CALL(vdso_time, 1, NULL);
if (ret > 0) {
ksft_print_msg("The time in hours since January 1, 1970 is %lld\n",
......@@ -138,7 +139,7 @@ static void vdso_test_clock_getres(clockid_t clk_id)
}
struct timespec ts, sys_ts;
long ret = vdso_clock_getres(clk_id, &ts);
long ret = VDSO_CALL(vdso_clock_getres, 2, clk_id, &ts);
if (ret == 0) {
ksft_print_msg("The vdso resolution is %lld %lld\n",
......
......@@ -20,6 +20,7 @@
#include <limits.h>
#include "vdso_config.h"
#include "vdso_call.h"
#include "../kselftest.h"
static const char **name;
......@@ -186,7 +187,7 @@ static void test_getcpu(void)
ret_sys = sys_getcpu(&cpu_sys, &node_sys, 0);
if (vdso_getcpu)
ret_vdso = vdso_getcpu(&cpu_vdso, &node_vdso, 0);
ret_vdso = VDSO_CALL(vdso_getcpu, 3, &cpu_vdso, &node_vdso, 0);
if (vgetcpu)
ret_vsys = vgetcpu(&cpu_vsys, &node_vsys, 0);
......@@ -269,7 +270,7 @@ static void test_one_clock_gettime(int clock, const char *name)
if (sys_clock_gettime(clock, &start) < 0) {
if (errno == EINVAL) {
vdso_ret = vdso_clock_gettime(clock, &vdso);
vdso_ret = VDSO_CALL(vdso_clock_gettime, 2, clock, &vdso);
if (vdso_ret == -EINVAL) {
printf("[OK]\tNo such clock.\n");
} else {
......@@ -282,7 +283,7 @@ static void test_one_clock_gettime(int clock, const char *name)
return;
}
vdso_ret = vdso_clock_gettime(clock, &vdso);
vdso_ret = VDSO_CALL(vdso_clock_gettime, 2, clock, &vdso);
end_ret = sys_clock_gettime(clock, &end);
if (vdso_ret != 0 || end_ret != 0) {
......@@ -331,7 +332,7 @@ static void test_one_clock_gettime64(int clock, const char *name)
if (sys_clock_gettime64(clock, &start) < 0) {
if (errno == EINVAL) {
vdso_ret = vdso_clock_gettime64(clock, &vdso);
vdso_ret = VDSO_CALL(vdso_clock_gettime64, 2, clock, &vdso);
if (vdso_ret == -EINVAL) {
printf("[OK]\tNo such clock.\n");
} else {
......@@ -344,7 +345,7 @@ static void test_one_clock_gettime64(int clock, const char *name)
return;
}
vdso_ret = vdso_clock_gettime64(clock, &vdso);
vdso_ret = VDSO_CALL(vdso_clock_gettime64, 2, clock, &vdso);
end_ret = sys_clock_gettime64(clock, &end);
if (vdso_ret != 0 || end_ret != 0) {
......@@ -401,7 +402,7 @@ static void test_gettimeofday(void)
return;
}
vdso_ret = vdso_gettimeofday(&vdso, &vdso_tz);
vdso_ret = VDSO_CALL(vdso_gettimeofday, 2, &vdso, &vdso_tz);
end_ret = sys_gettimeofday(&end, NULL);
if (vdso_ret != 0 || end_ret != 0) {
......@@ -431,7 +432,7 @@ static void test_gettimeofday(void)
}
/* And make sure that passing NULL for tz doesn't crash. */
vdso_gettimeofday(&vdso, NULL);
VDSO_CALL(vdso_gettimeofday, 2, &vdso, NULL);
}
int main(int argc, char **argv)
......
......@@ -14,6 +14,7 @@
#include "../kselftest.h"
#include "parse_vdso.h"
#include "vdso_config.h"
#include "vdso_call.h"
struct getcpu_cache;
typedef long (*getcpu_t)(unsigned int *, unsigned int *,
......@@ -42,7 +43,7 @@ int main(int argc, char **argv)
return KSFT_SKIP;
}
ret = get_cpu(&cpu, &node, 0);
ret = VDSO_CALL(get_cpu, 3, &cpu, &node, 0);
if (ret == 0) {
printf("Running on CPU %u node %u\n", cpu, node);
} else {
......
......@@ -22,6 +22,7 @@
#include "../kselftest.h"
#include "parse_vdso.h"
#include "vdso_config.h"
#include "vdso_call.h"
#ifndef timespecsub
#define timespecsub(tsp, usp, vsp) \
......@@ -116,7 +117,7 @@ static void vgetrandom_init(void)
printf("%s is missing!\n", name);
exit(KSFT_FAIL);
}
ret = vgrnd.fn(NULL, 0, 0, &vgrnd.params, ~0UL);
ret = VDSO_CALL(vgrnd.fn, 5, NULL, 0, 0, &vgrnd.params, ~0UL);
if (ret == -ENOSYS) {
printf("unsupported architecture\n");
exit(KSFT_SKIP);
......@@ -137,7 +138,7 @@ static ssize_t vgetrandom(void *buf, size_t len, unsigned long flags)
exit(KSFT_FAIL);
}
}
return vgrnd.fn(buf, len, flags, state, vgrnd.params.size_of_opaque_state);
return VDSO_CALL(vgrnd.fn, 5, buf, len, flags, state, vgrnd.params.size_of_opaque_state);
}
enum { TRIALS = 25000000, THREADS = 256 };
......
......@@ -19,6 +19,7 @@
#include "../kselftest.h"
#include "parse_vdso.h"
#include "vdso_config.h"
#include "vdso_call.h"
int main(int argc, char **argv)
{
......@@ -43,7 +44,7 @@ int main(int argc, char **argv)
}
struct timeval tv;
long ret = gtod(&tv, 0);
long ret = VDSO_CALL(gtod, 2, &tv, 0);
if (ret == 0) {
printf("The time is %lld.%06lld\n",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment