The Pedigree Project 0.1
TickSource.cc
1/*
2 * Copyright (c) 2008-2014, Pedigree Developers
3 *
4 * Please see the CONTRIB file in the root of the source tree for a full
5 * list of contributors.
6 *
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 */
19
20#include "TickSource.h"
21#include "pedigree/kernel/utilities/utility.h"
22
23#include <errno.h>
24#if defined(__linux__)
25#include <unistd.h>
26
27#include <sys/syscall.h>
28#endif
29
30namespace {
31static_assert(__atomic_always_lock_free(sizeof(size_t), nullptr),
32 "hosted tick expiration accounting must remain signal-safe");
33static_assert(__atomic_always_lock_free(sizeof(bool), nullptr),
34 "hosted tick state publication must remain signal-safe");
35
36timespec nanosecondsToTimespec(uint64_t nanoseconds) {
37 timespec result;
38 result.tv_sec = nanoseconds / 1000000000ULL;
39 result.tv_nsec = nanoseconds % 1000000000ULL;
40 return result;
41}
42
43#if !defined(__linux__)
44uint64_t monotonicNanoseconds() {
45 timespec now;
46 if (clock_gettime(CLOCK_MONOTONIC, &now) != 0) {
47 return 0;
48 }
49 return (static_cast<uint64_t>(now.tv_sec) * 1000000000ULL) + static_cast<uint64_t>(now.tv_nsec);
50}
51
52int waitFor(pthread_cond_t* condition, pthread_mutex_t* mutex, uint64_t nanoseconds) {
53 timespec delay = nanosecondsToTimespec(nanoseconds);
54#if defined(__APPLE__)
55 return pthread_cond_timedwait_relative_np(condition, mutex, &delay);
56#else
57 timespec deadline;
58 if (clock_gettime(CLOCK_REALTIME, &deadline) != 0) {
59 return errno;
60 }
61 deadline.tv_sec += delay.tv_sec;
62 deadline.tv_nsec += delay.tv_nsec;
63 if (deadline.tv_nsec >= 1000000000L) {
64 ++deadline.tv_sec;
65 deadline.tv_nsec -= 1000000000L;
66 }
67 return pthread_cond_timedwait(condition, mutex, &deadline);
68#endif
69}
70#endif
71} // namespace
72
73HostedTickSource::HostedTickSource()
74#if defined(__linux__)
75 : m_Timer(),
76#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
77 m_InjectedExpirations(0),
78#endif
79 m_Signal(0),
80 m_Owner(nullptr),
81 m_Prepared(false)
82#else
83 : m_TargetThread(),
84 m_HelperThread(),
85 m_Mutex(),
86 m_Condition(),
87 m_IntervalNanoseconds(0),
88 m_PendingExpirations(0),
89 m_Generation(0),
90 m_Armed(false),
91 m_Delivering(false),
92 m_Stop(false),
93 m_Failed(false),
94 m_HelperStarted(false),
95 m_Signal(0),
96 m_Owner(nullptr),
97 m_Prepared(false)
98#endif
99{
100}
101
102HostedTickSource::~HostedTickSource() {
103 destroy();
104}
105
106bool HostedTickSource::prepare(int signal, void* owner) {
107 if (__atomic_load_n(&m_Prepared, __ATOMIC_ACQUIRE) || signal <= 0 || !owner) {
108 return false;
109 }
110
111 m_Signal = signal;
112 m_Owner = owner;
113
114#if defined(__linux__)
115#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
116 __atomic_store_n(&m_InjectedExpirations, static_cast<size_t>(0), __ATOMIC_RELEASE);
117#endif
118 const long executionThreadId = ::syscall(SYS_gettid);
119 if (executionThreadId <= 0) {
120 return false;
121 }
122
123 sigevent event;
124 ByteSet(&event, 0, sizeof(event));
125 event.sigev_notify = SIGEV_THREAD_ID;
126 event.sigev_signo = signal;
127 event.sigev_value.sival_ptr = owner;
128 event._sigev_un._tid = static_cast<decltype(event._sigev_un._tid)>(executionThreadId);
129 if (timer_create(CLOCK_MONOTONIC, &event, &m_Timer) != 0) {
130 return false;
131 }
132#else
133 m_TargetThread = pthread_self();
134 m_IntervalNanoseconds = 0;
135 m_PendingExpirations = 0;
136 m_Generation = 0;
137 m_Armed = false;
138 m_Delivering = false;
139 m_Stop = false;
140 m_Failed = false;
141 if (pthread_mutex_init(&m_Mutex, nullptr) != 0) {
142 return false;
143 }
144 if (pthread_cond_init(&m_Condition, nullptr) != 0) {
145 pthread_mutex_destroy(&m_Mutex);
146 return false;
147 }
148 if (pthread_create(&m_HelperThread, nullptr, helperEntry, this) != 0) {
149 pthread_cond_destroy(&m_Condition);
150 pthread_mutex_destroy(&m_Mutex);
151 return false;
152 }
153 m_HelperStarted = true;
154#endif
155
156 __atomic_store_n(&m_Prepared, true, __ATOMIC_RELEASE);
157 return true;
158}
159
160bool HostedTickSource::arm(uint64_t intervalNanoseconds) {
161 if (!__atomic_load_n(&m_Prepared, __ATOMIC_ACQUIRE) || !intervalNanoseconds) {
162 return false;
163 }
164
165#if defined(__linux__)
166 itimerspec interval;
167 ByteSet(&interval, 0, sizeof(interval));
168 interval.it_interval = nanosecondsToTimespec(intervalNanoseconds);
169 interval.it_value = interval.it_interval;
170 return timer_settime(m_Timer, 0, &interval, nullptr) == 0;
171#else
172 pthread_mutex_lock(&m_Mutex);
173 if (__atomic_load_n(&m_Failed, __ATOMIC_ACQUIRE)) {
174 pthread_mutex_unlock(&m_Mutex);
175 return false;
176 }
177 m_IntervalNanoseconds = intervalNanoseconds;
178 m_Armed = true;
179 ++m_Generation;
180 pthread_cond_broadcast(&m_Condition);
181 pthread_mutex_unlock(&m_Mutex);
182 return true;
183#endif
184}
185
186bool HostedTickSource::disarm() {
187 if (!__atomic_load_n(&m_Prepared, __ATOMIC_ACQUIRE)) {
188 return true;
189 }
190
191#if defined(__linux__)
192#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
193 __atomic_store_n(&m_InjectedExpirations, static_cast<size_t>(0), __ATOMIC_RELEASE);
194#endif
195 itimerspec disarmed;
196 ByteSet(&disarmed, 0, sizeof(disarmed));
197 return timer_settime(m_Timer, 0, &disarmed, nullptr) == 0;
198#else
199 pthread_mutex_lock(&m_Mutex);
200 m_Armed = false;
201 ++m_Generation;
202 pthread_cond_broadcast(&m_Condition);
203 while (m_Delivering) {
204 pthread_cond_wait(&m_Condition, &m_Mutex);
205 }
206 __atomic_store_n(&m_PendingExpirations, static_cast<size_t>(0), __ATOMIC_RELEASE);
207 const bool success = !__atomic_load_n(&m_Failed, __ATOMIC_ACQUIRE);
208 pthread_mutex_unlock(&m_Mutex);
209 return success;
210#endif
211}
212
213void HostedTickSource::destroy() {
214 if (!__atomic_load_n(&m_Prepared, __ATOMIC_ACQUIRE)) {
215 return;
216 }
217
218#if defined(__linux__)
219 timer_delete(m_Timer);
220#else
221 disarm();
222 pthread_mutex_lock(&m_Mutex);
223 m_Stop = true;
224 pthread_cond_broadcast(&m_Condition);
225 pthread_mutex_unlock(&m_Mutex);
226 if (m_HelperStarted) {
227 pthread_join(m_HelperThread, nullptr);
228 m_HelperStarted = false;
229 }
230 pthread_cond_destroy(&m_Condition);
231 pthread_mutex_destroy(&m_Mutex);
232#endif
233
234 __atomic_store_n(&m_Prepared, false, __ATOMIC_RELEASE);
235 m_Signal = 0;
236 m_Owner = nullptr;
237}
238
239HostedTickSource::TakeResult HostedTickSource::takeExpirations(const siginfo_t* info,
240 size_t& expirations) {
241 expirations = 0;
242 if (!__atomic_load_n(&m_Prepared, __ATOMIC_ACQUIRE) || !info || info->si_signo != m_Signal) {
243 return TakeResult::NotSource;
244 }
245
246#if defined(__linux__)
247 if (info->si_code != SI_TIMER || info->si_value.sival_ptr != m_Owner) {
248#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
249 expirations =
250 __atomic_exchange_n(&m_InjectedExpirations, static_cast<size_t>(0), __ATOMIC_ACQ_REL);
251 if (expirations) {
252 return TakeResult::Expirations;
253 }
254#endif
255 return TakeResult::NotSource;
256 }
257 if (info->si_overrun < 0) {
258 return TakeResult::Invalid;
259 }
260 expirations = static_cast<size_t>(info->si_overrun) + 1;
261#else
262 if (__atomic_load_n(&m_Failed, __ATOMIC_ACQUIRE)) {
263 return TakeResult::Invalid;
264 }
265 expirations =
266 __atomic_exchange_n(&m_PendingExpirations, static_cast<size_t>(0), __ATOMIC_ACQ_REL);
267#endif
268 return TakeResult::Expirations;
269}
270
271#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
272bool HostedTickSource::queueExpirationForTest() {
273 if (!__atomic_load_n(&m_Prepared, __ATOMIC_ACQUIRE)) {
274 return false;
275 }
276
277#if defined(__linux__)
278 size_t pending = __atomic_load_n(&m_InjectedExpirations, __ATOMIC_ACQUIRE);
279 while (true) {
280 if (pending == ~static_cast<size_t>(0)) {
281 return false;
282 }
283 if (__atomic_compare_exchange_n(&m_InjectedExpirations, &pending, pending + 1, false,
284 __ATOMIC_RELEASE, __ATOMIC_ACQUIRE)) {
285 break;
286 }
287 }
288 if (pthread_kill(pthread_self(), m_Signal) == 0) {
289 return true;
290 }
291 __atomic_fetch_sub(&m_InjectedExpirations, 1, __ATOMIC_ACQ_REL);
292 return false;
293#else
294 if (!recordExpirations(1)) {
295 return false;
296 }
297 if (pthread_kill(m_TargetThread, m_Signal) == 0) {
298 return true;
299 }
300 __atomic_fetch_sub(&m_PendingExpirations, 1, __ATOMIC_ACQ_REL);
301 return false;
302#endif
303}
304#endif
305
306#if !defined(__linux__)
307void* HostedTickSource::helperEntry(void* source) {
308 static_cast<HostedTickSource*>(source)->helper();
309 return nullptr;
310}
311
312bool HostedTickSource::recordExpirations(size_t expirations) {
313 size_t pending = __atomic_load_n(&m_PendingExpirations, __ATOMIC_ACQUIRE);
314 while (true) {
315 if (expirations > (~static_cast<size_t>(0) - pending)) {
316 return false;
317 }
318 if (__atomic_compare_exchange_n(&m_PendingExpirations, &pending, pending + expirations, false,
319 __ATOMIC_RELEASE, __ATOMIC_ACQUIRE)) {
320 return true;
321 }
322 }
323}
324
325void HostedTickSource::helper() {
326 pthread_mutex_lock(&m_Mutex);
327 while (!m_Stop) {
328 while (!m_Stop && !m_Armed) {
329 pthread_cond_wait(&m_Condition, &m_Mutex);
330 }
331 if (m_Stop) {
332 break;
333 }
334
335 const size_t generation = m_Generation;
336 const uint64_t interval = m_IntervalNanoseconds;
337 uint64_t deadline = monotonicNanoseconds() + interval;
338 while (!m_Stop && m_Armed && generation == m_Generation) {
339 const uint64_t now = monotonicNanoseconds();
340 if (now < deadline) {
341 waitFor(&m_Condition, &m_Mutex, deadline - now);
342 continue;
343 }
344
345 const uint64_t elapsed = now - deadline;
346 const uint64_t count64 = 1 + (elapsed / interval);
347 if (count64 > static_cast<uint64_t>(~static_cast<size_t>(0))) {
348 __atomic_store_n(&m_Failed, true, __ATOMIC_RELEASE);
349 m_Armed = false;
350 break;
351 }
352 deadline += count64 * interval;
353 m_Delivering = true;
354 pthread_mutex_unlock(&m_Mutex);
355
356 const bool recorded = recordExpirations(static_cast<size_t>(count64));
357 const int delivered = recorded ? pthread_kill(m_TargetThread, m_Signal) : EOVERFLOW;
358
359 pthread_mutex_lock(&m_Mutex);
360 if (delivered != 0) {
361 __atomic_store_n(&m_Failed, true, __ATOMIC_RELEASE);
362 m_Armed = false;
363 }
364 m_Delivering = false;
365 pthread_cond_broadcast(&m_Condition);
366 }
367 }
368 pthread_mutex_unlock(&m_Mutex);
369}
370#endif