summaryrefslogtreecommitdiffhomepage
path: root/vdso/vdso_time.cc
blob: 1bb4bb86b63f175c9dd86c4a070b3b045c1cebbc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
// Copyright 2018 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "vdso/vdso_time.h"

#include <stdint.h>
#include <sys/time.h>
#include <time.h>

#include "vdso/cycle_clock.h"
#include "vdso/seqlock.h"
#include "vdso/syscalls.h"

// struct params defines the layout of the parameter page maintained by the
// kernel (i.e., sentry).
//
// This is similar to the VVAR page maintained by the normal Linux kernel for
// its VDSO, but it has a different layout.
//
// It must be kept in sync with VDSOParamPage in pkg/sentry/kernel/vdso.go.
struct params {
  uint64_t seq_count;

  uint64_t monotonic_ready;
  int64_t monotonic_base_cycles;
  int64_t monotonic_base_ref;
  uint64_t monotonic_frequency;

  uint64_t realtime_ready;
  int64_t realtime_base_cycles;
  int64_t realtime_base_ref;
  uint64_t realtime_frequency;
};

// Returns a pointer to the global parameter page.
//
// This page lives in the page just before the VDSO binary itself. The linker
// defines _params as the page before the VDSO.
//
// Ideally, we'd simply declare _params as an extern struct params.
// Unfortunately various combinations of old/new versions of gcc/clang and
// gold/bfd struggle to generate references to such a global without generating
// relocations.
//
// So instead, we use inline assembly with a construct that seems to have wide
// compatibility across many toolchains.
#if __x86_64__

inline struct params* get_params() {
  struct params* p = nullptr;
  asm("leaq _params(%%rip), %0" : "=r"(p) : :);
  return p;
}

#elif __aarch64__

inline struct params* get_params() {
  struct params* p = nullptr;
  asm("adr %0, _params" : "=r"(p) : :);
  return p;
}

#else
#error "unsupported architecture"
#endif

namespace vdso {

const uint64_t kNsecsPerSec = 1000000000UL;

inline struct timespec ns_to_timespec(uint64_t ns) {
  struct timespec ts;
  ts.tv_sec = ns / kNsecsPerSec;
  ts.tv_nsec = ns % kNsecsPerSec;
  return ts;
}

inline uint64_t cycles_to_ns(uint64_t frequency, uint64_t cycles) {
  uint64_t mult = (kNsecsPerSec << 32) / frequency;
  return ((unsigned __int128)cycles * mult) >> 32;
}

// ClockRealtime() is the VDSO implementation of clock_gettime(CLOCK_REALTIME).
int ClockRealtime(struct timespec* ts) {
  struct params* params = get_params();
  uint64_t seq;
  uint64_t ready;
  int64_t base_ref;
  int64_t base_cycles;
  uint64_t frequency;
  int64_t now_cycles;

  do {
    seq = read_seqcount_begin(&params->seq_count);
    ready = params->realtime_ready;
    base_ref = params->realtime_base_ref;
    base_cycles = params->realtime_base_cycles;
    frequency = params->realtime_frequency;
    now_cycles = cycle_clock();
  } while (read_seqcount_retry(&params->seq_count, seq));

  if (!ready) {
    // The sandbox kernel ensures that we won't compute a time later than this
    // once the params are ready.
    return sys_clock_gettime(CLOCK_REALTIME, ts);
  }

  int64_t delta_cycles =
      (now_cycles < base_cycles) ? 0 : now_cycles - base_cycles;
  int64_t now_ns = base_ref + cycles_to_ns(frequency, delta_cycles);
  *ts = ns_to_timespec(now_ns);
  return 0;
}

// ClockMonotonic() is the VDSO implementation of
// clock_gettime(CLOCK_MONOTONIC).
int ClockMonotonic(struct timespec* ts) {
  struct params* params = get_params();
  uint64_t seq;
  uint64_t ready;
  int64_t base_ref;
  int64_t base_cycles;
  uint64_t frequency;
  int64_t now_cycles;

  do {
    seq = read_seqcount_begin(&params->seq_count);
    ready = params->monotonic_ready;
    base_ref = params->monotonic_base_ref;
    base_cycles = params->monotonic_base_cycles;
    frequency = params->monotonic_frequency;
    now_cycles = cycle_clock();
  } while (read_seqcount_retry(&params->seq_count, seq));

  if (!ready) {
    // The sandbox kernel ensures that we won't compute a time later than this
    // once the params are ready.
    return sys_clock_gettime(CLOCK_MONOTONIC, ts);
  }

  int64_t delta_cycles =
      (now_cycles < base_cycles) ? 0 : now_cycles - base_cycles;
  int64_t now_ns = base_ref + cycles_to_ns(frequency, delta_cycles);
  *ts = ns_to_timespec(now_ns);
  return 0;
}

}  // namespace vdso