diff --git a/src/PreciseDelay.h b/src/PreciseDelay.h new file mode 100644 index 00000000..503727ff --- /dev/null +++ b/src/PreciseDelay.h @@ -0,0 +1,70 @@ +#pragma once + +/* Microsecond delays that mean what they say on every platform. + * + * std::this_thread::sleep_for is fine for short waits on Linux, but on + * Windows no sleep ends before the next scheduler tick: 15.6 ms by default, + * 1 ms at best with timeBeginPeriod(1). The Kestrel bring-up and the vendored + * halbb/halrf calibrations wait tens of microseconds thousands of times, so a + * 50 us wait turning into 15.6 ms stretches a seconds-long bring-up into many + * minutes. + * + * On Windows the wait spins on steady_clock (QPC-backed) and only sleeps + * while more time is left than a sleep can take. The tick is process-global + * state that any library may change, so that bound is learned rather than + * assumed: it starts at one default tick, follows shorter sleeps down slowly + * (e.g. once timeBeginPeriod(1) is active) and jumps back up at once when a + * sleep runs long. A wait shorter than the current tick (the 5 ms EDCCA settle + * under the default 15.6 ms tick) therefore never sleeps. Other platforms keep + * sleep_for unchanged. */ + +#include +#include +#include +#include +#include + +namespace devourer { + +#if defined(_WIN32) +namespace detail { +/* Longest a 1 ms sleep_for is expected to take right now, in microseconds. + * Starts at (and is capped to) the default 15.625 ms tick plus slack. */ +inline constexpr uint32_t kMaxSleepUs = 16000; +inline std::atomic &sleep_bound_us() { + static std::atomic bound{kMaxSleepUs}; + return bound; +} +} // namespace detail +#endif + +inline void precise_delay_us(uint32_t us) { +#if defined(_WIN32) + using clock = std::chrono::steady_clock; + using std::chrono::microseconds; + /* Kept free after the last sleep for wakeup latency on a busy system. */ + constexpr int64_t kMarginUs = 500; + const auto deadline = clock::now() + microseconds(us); + auto &bound = detail::sleep_bound_us(); + for (;;) { + const auto start = clock::now(); + const int64_t left = + std::chrono::duration_cast(deadline - start).count(); + const uint32_t b = bound.load(std::memory_order_relaxed); + if (left <= int64_t(b) + kMarginUs) + break; + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + const auto took = static_cast(std::min( + std::chrono::duration_cast(clock::now() - start).count(), + detail::kMaxSleepUs)); + bound.store(took >= b ? took : b - (b - took) / 8, + std::memory_order_relaxed); + } + while (clock::now() < deadline) + std::this_thread::yield(); +#else + std::this_thread::sleep_for(std::chrono::microseconds(us)); +#endif +} + +} // namespace devourer diff --git a/src/kestrel/HalKestrel.cpp b/src/kestrel/HalKestrel.cpp index 208a8f4d..6b315351 100644 --- a/src/kestrel/HalKestrel.cpp +++ b/src/kestrel/HalKestrel.cpp @@ -8,6 +8,7 @@ #include "KestrelFw.h" #include "MacRegAx.h" +#include "PreciseDelay.h" #include "kestrel_halbb_glue.h" /* vendored halbb-G6 BB bring-up (C) */ #include "kestrel_halrf_glue.h" /* vendored halrf-G6 RF cals (C) */ @@ -16,9 +17,7 @@ namespace kestrel { namespace { namespace r = kestrel::reg; -void delay_us(uint32_t us) { - std::this_thread::sleep_for(std::chrono::microseconds(us)); -} +void delay_us(uint32_t us) { devourer::precise_delay_us(us); } } /* namespace */ diff --git a/src/kestrel/KestrelFw.cpp b/src/kestrel/KestrelFw.cpp index 2a122cc3..9da7abb3 100644 --- a/src/kestrel/KestrelFw.cpp +++ b/src/kestrel/KestrelFw.cpp @@ -1,12 +1,11 @@ #include "KestrelFw.h" -#include #include -#include #include #include "KestrelLe.h" #include "MacRegAx.h" +#include "PreciseDelay.h" #include "SignalStop.h" /* g_devourer_should_stop — set by demo signal handlers */ #if defined(DEVOURER_HAVE_KESTREL_8852B) #include "hal8852b_fw.h" @@ -20,9 +19,7 @@ namespace kestrel { namespace { namespace r = kestrel::reg; -void delay_us(uint32_t us) { - std::this_thread::sleep_for(std::chrono::microseconds(us)); -} +void delay_us(uint32_t us) { devourer::precise_delay_us(us); } /* le32 / put_le32 now live in KestrelLe.h (shared with KestrelFwSched.cpp); * pull them into this anonymous namespace so existing unqualified uses below