The Pedigree Project 0.1
TscClock.h
1/*
2 * Copyright (c) 2026, Pedigree Developers
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted.
6 */
7
8#ifndef PEDIGREE_KERNEL_MACHINE_MACH_PC_TSCCLOCK_H
9#define PEDIGREE_KERNEL_MACHINE_MACH_PC_TSCCLOCK_H
10#include "pedigree/kernel/compiler.h"
11#include "pedigree/kernel/processor/types.h"
12
13#include <config.h>
14
15static_assert(__atomic_always_lock_free(sizeof(uint64_t), nullptr),
16 "the PC monotonic clock requires lock-free 64-bit publication");
17
18namespace PcTscClock {
19constexpr uint64_t MaximumTimestamp = ~static_cast<uint64_t>(0);
20
22 Calibration(uint64_t cycles = 1, uint64_t nanoseconds = 1)
23 : cycles(cycles), nanoseconds(nanoseconds), whole(0), multiplier(0), fractional(0) {
24 if (!cycles) {
25 return;
26 }
27
28 whole = nanoseconds / cycles;
29 fractional = nanoseconds % cycles;
30 if (!fractional) {
31 return;
32 }
33
34#if defined(__x86_64__)
35 uint64_t remainder;
36 // fractional < cycles, so this boot-time division cannot overflow. Avoid
37 // requiring a freestanding 128-bit division runtime for calibration.
38 asm("divq %4"
39 : "=a"(multiplier), "=d"(remainder)
40 : "0"(uint64_t(0)), "1"(fractional), "r"(cycles)
41 : "cc");
42#else
43 multiplier = static_cast<uint64_t>((static_cast<unsigned __int128>(fractional) << 64) / cycles);
44#endif
45 }
46
47 uint64_t cycles;
48 uint64_t nanoseconds;
49 uint64_t whole;
50 uint64_t multiplier;
51 uint64_t fractional;
52};
53
54ALWAYS_INLINE inline uint64_t scale(uint64_t cycles, const Calibration& calibration) {
55 const unsigned __int128 product = static_cast<unsigned __int128>(cycles) * calibration.multiplier;
56 uint64_t nanoseconds = static_cast<uint64_t>(product >> 64);
57 const uint64_t fraction = static_cast<uint64_t>(product);
58
59 // The rounded-down Q64 multiplier underestimates by less than cycles / 2^64.
60 // Without a carry from fraction + cycles, its integer part is already exact.
61 // Correct the remaining cases to retain floor(x * numerator / denominator)
62 // even at long uptimes, without a division or accumulated clock drift.
63 if (fraction > MaximumTimestamp - cycles) {
64 // The fractional quotient is below cycles, so adding one cannot overflow.
65 if (static_cast<unsigned __int128>(cycles) * calibration.fractional >=
66 static_cast<unsigned __int128>(nanoseconds + 1) * calibration.cycles) {
67 ++nanoseconds;
68 }
69 }
70
71 if (!calibration.whole) {
72 return nanoseconds;
73 }
74
75 const unsigned __int128 result =
76 static_cast<unsigned __int128>(cycles) * calibration.whole + nanoseconds;
77 return result > MaximumTimestamp ? MaximumTimestamp : static_cast<uint64_t>(result);
78}
79
80ALWAYS_INLINE inline uint64_t saturatingAdd(uint64_t first, uint64_t second) {
81 return first > (MaximumTimestamp - second) ? MaximumTimestamp : first + second;
82}
83
84ALWAYS_INLINE inline uint64_t fromAnchor(uint64_t currentTsc, uint64_t anchorTsc,
85 uint64_t anchorNanoseconds,
86 const Calibration& calibration) {
87 if (currentTsc < anchorTsc) {
88 return anchorNanoseconds;
89 }
90
91 return saturatingAdd(anchorNanoseconds, scale(currentTsc - anchorTsc, calibration));
92}
93
94constexpr uint64_t publicationResult(uint64_t candidate, uint64_t observed, bool exchanged) {
95 return exchanged ? candidate : observed;
96}
97
106 public:
107 explicit MonotonicPublication(uint64_t value = 0) : m_Value(value) {}
108
109 uint64_t publish(uint64_t candidate) {
110 uint64_t observed = __atomic_load_n(&m_Value, __ATOMIC_ACQUIRE);
111 if (candidate <= observed) {
112 return observed;
113 }
114
115 const bool exchanged = __atomic_compare_exchange_n(&m_Value, &observed, candidate, false,
116 __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE);
117 // On failure compare_exchange replaces observed with the winner.
118 return publicationResult(candidate, observed, exchanged);
119 }
120
121 uint64_t value() const {
122 return __atomic_load_n(&m_Value, __ATOMIC_ACQUIRE);
123 }
124
125 void reset(uint64_t value = 0) {
126 __atomic_store_n(&m_Value, value, __ATOMIC_RELEASE);
127 }
128
129 private:
130 uint64_t m_Value;
131
133 MonotonicPublication& operator=(const MonotonicPublication&) = delete;
134};
135} // namespace PcTscClock
136
137#endif