1 /*
2 * Copyright (c) 2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include <time.h>
17 #include <errno.h>
18 #include <stdint.h>
19 #include "syscall.h"
20 #include "atomic.h"
21
22 #ifdef VDSO_CGT_SYM
23
24 static void *volatile vdso_func;
25
26 #ifdef VDSO_CGT32_SYM
27 static void *volatile vdso_func_32;
cgt_time32_wrap(clockid_t clk,struct timespec * ts)28 static int cgt_time32_wrap(clockid_t clk, struct timespec *ts)
29 {
30 long ts32[2];
31 int (*f)(clockid_t, long[2]) =
32 (int (*)(clockid_t, long[2]))vdso_func_32;
33 int r = f(clk, ts32);
34 if (!r) {
35 /* Fallback to syscalls if time32 overflowed. Maybe
36 * we lucked out and somehow migrated to a kernel with
37 * time64 syscalls available. */
38 if (ts32[0] < 0) {
39 a_cas_p(&vdso_func, (void *)cgt_time32_wrap, 0);
40 return -ENOSYS;
41 }
42 ts->tv_sec = ts32[0];
43 ts->tv_nsec = ts32[1];
44 }
45 return r;
46 }
47 #endif
48
cgt_init(clockid_t clk,struct timespec * ts)49 static int cgt_init(clockid_t clk, struct timespec *ts)
50 {
51 __get_vdso_info();
52 void *p = __get_vdso_addr(VDSO_CGT_VER, VDSO_CGT_SYM);
53 #ifdef VDSO_CGT32_SYM
54 if (!p) {
55 void *q = __get_vdso_addr(VDSO_CGT32_VER, VDSO_CGT32_SYM);
56 if (q) {
57 a_cas_p(&vdso_func_32, 0, q);
58 p = cgt_time32_wrap;
59 }
60 }
61 #endif
62 int (*f)(clockid_t, struct timespec *) =
63 (int (*)(clockid_t, struct timespec *))p;
64 a_cas_p(&vdso_func, (void *)cgt_init, p);
65 return f ? f(clk, ts) : -ENOSYS;
66 }
67
68 static void *volatile vdso_func = (void *)cgt_init;
69
70 #endif
71
__clock_gettime(clockid_t clk,struct timespec * ts)72 int __clock_gettime(clockid_t clk, struct timespec *ts)
73 {
74 int r;
75
76 #ifdef VDSO_CGT_SYM
77 int (*f)(clockid_t, struct timespec *) =
78 (int (*)(clockid_t, struct timespec *))vdso_func;
79 if (f) {
80 r = f(clk, ts);
81 if (!r) return r;
82 if (r == -EINVAL) return __syscall_ret(r);
83 /* Fall through on errors other than EINVAL. Some buggy
84 * vdso implementations return ENOSYS for clocks they
85 * can't handle, rather than making the syscall. This
86 * also handles the case where cgt_init fails to find
87 * a vdso function to use. */
88 }
89 #endif
90
91 #ifdef SYS_clock_gettime64
92 r = -ENOSYS;
93 if (sizeof(time_t) > 4)
94 r = __syscall(SYS_clock_gettime64, clk, ts);
95 if (SYS_clock_gettime == SYS_clock_gettime64 || r!=-ENOSYS)
96 return __syscall_ret(r);
97 long ts32[2];
98 r = __syscall(SYS_clock_gettime, clk, ts32);
99 if (r==-ENOSYS && clk==CLOCK_REALTIME) {
100 r = __syscall(SYS_gettimeofday, ts32, 0);
101 ts32[1] *= 1000;
102 }
103 if (!r) {
104 ts->tv_sec = ts32[0];
105 ts->tv_nsec = ts32[1];
106 return r;
107 }
108 return __syscall_ret(r);
109 #else
110 r = __syscall(SYS_clock_gettime, clk, ts);
111 if (r == -ENOSYS) {
112 if (clk == CLOCK_REALTIME) {
113 __syscall(SYS_gettimeofday, ts, 0);
114 ts->tv_nsec = (int)ts->tv_nsec * 1000;
115 return 0;
116 }
117 r = -EINVAL;
118 }
119 return __syscall_ret(r);
120 #endif
121 }
122
123 weak_alias(__clock_gettime, clock_gettime);
124