Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions src/PreciseDelay.h
Original file line number Diff line number Diff line change
@@ -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 <algorithm>
#include <atomic>
#include <chrono>
#include <cstdint>
#include <thread>

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<uint32_t> &sleep_bound_us() {
static std::atomic<uint32_t> 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<microseconds>(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<uint32_t>(std::min<int64_t>(
std::chrono::duration_cast<microseconds>(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
5 changes: 2 additions & 3 deletions src/kestrel/HalKestrel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) */

Expand All @@ -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 */

Expand Down
7 changes: 2 additions & 5 deletions src/kestrel/KestrelFw.cpp
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
#include "KestrelFw.h"

#include <chrono>
#include <cstring>
#include <thread>
#include <utility>

#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"
Expand All @@ -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
Expand Down
Loading