-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Expand file tree
/
Copy pathUptimeClock.cpp
More file actions
98 lines (87 loc) · 3.28 KB
/
Copy pathUptimeClock.cpp
File metadata and controls
98 lines (87 loc) · 3.28 KB
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
// See UptimeClock.h for the full contract.
#include "UptimeClock.h"
#include <Arduino.h>
#include <atomic>
uint32_t Time::getMillis()
{
#ifdef PIO_UNIT_TESTING
if (Time::useTestClock.load(std::memory_order_relaxed))
return Time::testNowMs.load(std::memory_order_relaxed);
#endif
return millis();
}
namespace
{
struct PublishedSnapshot {
std::atomic<uint32_t> high{0};
std::atomic<uint32_t> low{0};
};
// The constexpr atomic initializers make both snapshots available before firmware startup.
PublishedSnapshot published[2];
std::atomic<uint32_t> publishedGeneration{0};
#ifdef PIO_UNIT_TESTING
std::atomic<Time::MonotonicPublishHook> monotonicPublishHook{nullptr};
#endif
// Extend a published (high, low) snapshot to `now`; unsigned subtraction is exact across the wrap
// for any gap under 49.7 days. One copy, because reader and writer must agree on it exactly.
uint64_t extendPublished(uint32_t high, uint32_t low, uint32_t now)
{
return ((((uint64_t)high << 32) | low) + (uint32_t)(now - low));
}
// A generation change means the writer completed a publish while this copy was being read. A
// paused publish leaves the generation unchanged and writes only the inactive snapshot.
void readPublished(uint32_t &high, uint32_t &low)
{
for (;;) {
const uint32_t before = publishedGeneration.load(std::memory_order_acquire);
PublishedSnapshot &snapshot = published[before & 1u];
high = snapshot.high.load(std::memory_order_relaxed);
low = snapshot.low.load(std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_acquire);
if (publishedGeneration.load(std::memory_order_relaxed) == before)
return;
}
}
} // namespace
uint64_t Time::getMillisMonotonic()
{
uint32_t high, low;
readPublished(high, low);
// The reader writes nothing back; it just extends the last published carry to now.
return extendPublished(high, low, getMillis());
}
uint32_t Time::getUptimeSecs()
{
return (uint32_t)(getMillisMonotonic() / 1000);
}
void Time::serviceMonotonic()
{
const uint32_t generation = publishedGeneration.load(std::memory_order_relaxed);
PublishedSnapshot &active = published[generation & 1u];
const uint32_t low = active.low.load(std::memory_order_relaxed);
const uint32_t high = active.high.load(std::memory_order_relaxed);
const uint64_t next = extendPublished(high, low, getMillis());
PublishedSnapshot &inactive = published[(generation + 1u) & 1u];
inactive.high.store((uint32_t)(next >> 32), std::memory_order_relaxed);
inactive.low.store((uint32_t)next, std::memory_order_relaxed);
#ifdef PIO_UNIT_TESTING
if (const auto hook = monotonicPublishHook.load(std::memory_order_relaxed))
hook();
#endif
publishedGeneration.store(generation + 1u, std::memory_order_release);
}
#ifdef PIO_UNIT_TESTING
void Time::resetMonotonicForTests()
{
publishedGeneration.store(0, std::memory_order_relaxed);
for (auto &snapshot : published) {
snapshot.high.store(0, std::memory_order_relaxed);
snapshot.low.store(0, std::memory_order_relaxed);
}
monotonicPublishHook.store(nullptr, std::memory_order_relaxed);
}
void Time::setMonotonicPublishHookForTests(MonotonicPublishHook hook)
{
monotonicPublishHook.store(hook, std::memory_order_relaxed);
}
#endif