4#if PEDIGREE_AFFINITY_TESTS && THREADS
5#include "pedigree/kernel/Atomic.h"
6#include "pedigree/kernel/LockGuard.h"
7#include "pedigree/kernel/Log.h"
8#include "pedigree/kernel/process/Process.h"
9#include "pedigree/kernel/process/Scheduler.h"
10#include "pedigree/kernel/process/Semaphore.h"
11#include "pedigree/kernel/process/Thread.h"
12#include "pedigree/kernel/process/WaitQueue.h"
13#include "pedigree/kernel/processor/Processor.h"
14#include "pedigree/kernel/processor/ProcessorInformation.h"
15#include "pedigree/kernel/time/Time.h"
18bool check(
bool condition,
const char* detail) {
20 ERROR(
"AFFINITY-CORE: FAIL " << detail);
25 for (
size_t i = 0; i < CpuAffinityMask::WordCount; ++i) {
26 if (left.words[i] != right.words[i])
34 Semaphore entered{0}, firstDone{0}, secondDone{0};
39 Atomic<size_t> firstReason{
static_cast<size_t>(WaitQueue::WakeReason::Waiting)};
40 Atomic<size_t> secondReason{
static_cast<size_t>(WaitQueue::WakeReason::Waiting)};
45int waitEntry(
void* parameter) {
46 auto& state = *
static_cast<WaitState*
>(parameter);
47 state.entered.release();
48 WaitQueue::WakeReason reason;
50 auto guard = state.queue.acquire();
53 state.firstReason =
static_cast<size_t>(reason);
55 if (reason == WaitQueue::WakeReason::Signalled) {
64 state.firstDone.release();
65 if (!state.repeat || reason != WaitQueue::WakeReason::Signalled || !state.gatePassed)
68 auto guard = state.queue.acquire();
71 state.secondReason =
static_cast<size_t>(reason);
74 state.secondDone.release();
78Thread* startWaiter(WaitState& state,
size_t cpu) {
80 placement.allowed.set(cpu);
81 placement.migratable =
true;
84 new Thread(current->
getParent(), waitEntry, &state,
nullptr,
false,
false,
true, &placement);
87 peer->setPriority(current->getPriority());
90 if (!peer->joinForCompletion())
91 FATAL(
"AFFINITY-CORE: failed startup could not be joined");
97bool waitForSleeping(
Thread& peer, WaitState& state) {
98 const Time::Timestamp start = Time::getTicks();
99 while (Time::getTicks() - start < 5 * Time::Multiplier::Second) {
104 sleeping = peer.
getStatus() == Thread::Sleeping;
106 if (sleeping && state.queue.waiterCount() == 1)
113void retireWaiter(
Thread* peer, WaitState& state,
bool completed) {
116 state.queue.wakeAll();
119 FATAL(
"AFFINITY-CORE: owned waiter could not be joined");
124 WaitState* state =
nullptr;
132ReadyContext* g_ReadyContext =
nullptr;
134void beforeReadyPublication(
Thread* peer) {
135 auto* context = __atomic_load_n(&g_ReadyContext, __ATOMIC_ACQUIRE);
136 if (!context || peer != context->target)
139 if (context->calls != 1) {
140 context->passed =
false;
145 if (!context->passed)
148 uint64_t generation = 0;
149 const AffinityResult requested = peer->
requestAffinity(context->destination, generation);
151 check(requested == AffinityResult::Success,
"request while ready publication is held");
152 if (requested == AffinityResult::Success) {
153 context->passed &= check(peer->
waitAffinity(generation) == AffinityResult::Success,
154 "affinity completion while wake is held");
157 peer->snapshotPlacement(placement);
163 context->passed &= check(correctOwner && sameMask(placement.allowed, context->destination),
164 "mask acknowledged without moving the blocked kernel continuation");
169 for (
size_t i = 0; i < 8; ++i)
171 context->passed &= check(context->state->returns == 0 && context->state->queue.waiterCount() == 1,
172 "waiter executed or was reused before wake publication retired");
175bool heldWake(
size_t sourceCpu,
size_t destinationCpu) {
178 Thread* peer = startWaiter(state, sourceCpu);
179 if (!check(peer !=
nullptr,
"wake waiter allocation/start"))
181 bool passed = check(state.entered.acquireForCompletion(1, 5) && waitForSleeping(*peer, state),
182 "first wait enrollment");
183 ReadyContext context;
184 context.target = peer;
185 context.state = &state;
186 context.destination.set(destinationCpu);
187 context.moving = sourceCpu != destinationCpu;
193 __atomic_store_n(&g_ReadyContext, &context, __ATOMIC_RELEASE);
194 WaitQueue::setReadyPublicationHookForTest(peer, beforeReadyPublication);
197 WaitQueue::setReadyPublicationHookForTest(
nullptr,
nullptr);
198 __atomic_store_n(&g_ReadyContext,
static_cast<ReadyContext*
>(
nullptr), __ATOMIC_RELEASE);
199 passed &= check(woke && context.calls == 1 && context.passed,
"held wake admission");
201 bool completed =
false;
204 check(state.firstDone.acquireForCompletion(1, 5) && state.returns == 1 &&
205 state.beforeGateCpu == sourceCpu && state.gatePassed &&
206 state.firstCpu == destinationCpu &&
207 state.firstReason ==
static_cast<size_t>(WaitQueue::WakeReason::Signalled) &&
208 waitForSleeping(*peer, state),
209 "source wait return, clean gate migration and second wait enrollment");
213 completed = woke && state.secondDone.acquireForCompletion(1, 5);
215 check(completed && state.returns == 2 && state.secondCpu == destinationCpu &&
216 state.secondReason ==
static_cast<size_t>(WaitQueue::WakeReason::Signalled) &&
217 state.queue.waiterCount() == 0,
218 "waiter reuse, single execution and final wake reason");
221 retireWaiter(peer, state, completed);
223 NOTICE(
"AFFINITY-CORE: PASS extracted wake " << (context.moving ?
"migration" :
"same CPU"));
227struct CancelContext {
233CancelContext* g_CancelContext =
nullptr;
235void cancelBeforeCommit(
Thread* peer) {
236 auto* context = __atomic_load_n(&g_CancelContext, __ATOMIC_ACQUIRE);
237 if (!context || peer != context->target)
244bool cancelPending(
size_t sourceCpu,
size_t destinationCpu) {
246 Thread* peer = startWaiter(state, sourceCpu);
247 if (!check(peer !=
nullptr,
"cancel waiter allocation/start"))
249 bool passed = check(state.entered.acquireForCompletion(1, 5) && waitForSleeping(*peer, state),
250 "cancel wait enrollment");
251 CancelContext context;
252 context.target = peer;
254 destination.set(destinationCpu);
256 __atomic_store_n(&g_CancelContext, &context, __ATOMIC_RELEASE);
257 Thread::setAffinityCommitHookForTest(peer, cancelBeforeCommit);
258 uint64_t generation = 0;
259 const AffinityResult requested = peer->
requestAffinity(destination, generation);
260 passed &= check(requested == AffinityResult::Success,
"pending request admission");
261 if (requested == AffinityResult::Success) {
262 passed &= check(peer->
waitAffinity(generation) == AffinityResult::Terminal,
263 "pending generation terminal completion");
265 Thread::setAffinityCommitHookForTest(
nullptr,
nullptr);
266 __atomic_store_n(&g_CancelContext,
static_cast<CancelContext*
>(
nullptr), __ATOMIC_RELEASE);
267 passed &= check(context.calls == 1 && context.safeContext,
"terminal precommit hook");
269 retireWaiter(peer, state,
false);
271 NOTICE(
"AFFINITY-CORE: PASS pending terminal cancellation");
281PinContext* g_PinContext =
nullptr;
283void pinBeforeCommit(
Thread* peer) {
284 auto* context = __atomic_load_n(&g_PinContext, __ATOMIC_ACQUIRE);
285 if (!context || peer != context->target)
289 context->rejected = !pinned;
291 peer->unpinLegacyUserCallbacks();
294bool legacyPins(
size_t sourceCpu,
size_t destinationCpu) {
296 Thread* peer = startWaiter(state, sourceCpu);
297 if (!check(peer !=
nullptr,
"legacy pin waiter allocation/start"))
299 bool passed = check(state.entered.acquireForCompletion(1, 5) && waitForSleeping(*peer, state),
300 "legacy pin wait enrollment");
302 source.set(sourceCpu);
303 destination.set(destinationCpu);
305 for (
size_t i = 0; passed && i < 2; ++i) {
308 passed &= check(pinned,
"counted legacy pin admission");
310 const bool moving = sourceCpu != destinationCpu;
311 auto rejectsExclusion = [&] {
312 uint64_t generation = ~uint64_t(0);
314 if (result == AffinityResult::Success || result == AffinityResult::Busy)
317 peer->snapshotPlacement(placement);
318 return check(result == AffinityResult::Unsupported && !generation && placement.migratable &&
319 sameMask(placement.allowed, source),
320 "legacy pin rejected exclusion without changing policy");
322 if (passed && moving)
323 passed &= rejectsExclusion();
325 uint64_t generation = 0;
327 passed &= check(result == AffinityResult::Success,
"legacy pin compatible mask admission");
328 if (result == AffinityResult::Success)
329 passed &= check(peer->
waitAffinity(generation) == AffinityResult::Success,
330 "legacy pin compatible mask acknowledgement");
333 peer->unpinLegacyUserCallbacks();
336 if (passed && moving)
337 passed &= rejectsExclusion();
339 peer->unpinLegacyUserCallbacks();
343 context.target = peer;
346 __atomic_store_n(&g_PinContext, &context, __ATOMIC_RELEASE);
347 Thread::setAffinityCommitHookForTest(peer, pinBeforeCommit);
349 uint64_t generation = 0;
351 passed &= check(result == AffinityResult::Success,
"last legacy pin release permits policy");
352 if (result == AffinityResult::Success)
353 passed &= check(peer->
waitAffinity(generation) == AffinityResult::Success,
354 "unpinned policy acknowledgement");
356 Thread::setAffinityCommitHookForTest(
nullptr,
nullptr);
357 __atomic_store_n(&g_PinContext,
static_cast<PinContext*
>(
nullptr), __ATOMIC_RELEASE);
358 passed &= check(context.calls == 1 && context.rejected,
359 "accepted excluding request rejects legacy pin admission");
361 passed &= check(!pinned,
"committed excluding mask rejects legacy pin admission");
363 peer->unpinLegacyUserCallbacks();
366 peer->snapshotPlacement(placement);
370 sourceOwned = peer->
getScheduler() == Scheduler::schedulerForCpu(sourceCpu);
372 passed &= check(sourceOwned && state.returns == 0 && sameMask(placement.allowed, destination),
373 "sleeping target acknowledges policy on original owner");
375 bool completed =
false;
378 state.queue.wakeOne(WaitQueue::WakeReason::Signalled,
WaitQueue::Channel(&state, 1)) &&
379 state.firstDone.acquireForCompletion(1, 5);
380 passed &= check(completed && state.beforeGateCpu == sourceCpu && state.gatePassed &&
381 state.firstCpu == destinationCpu && state.returns == 1,
382 "unpinned target moves only at clean gate");
384 retireWaiter(peer, state, completed);
386 NOTICE(
"AFFINITY-CORE: PASS legacy callback pins " << (moving ?
"migration" :
"same CPU"));
390int forbiddenEntry(
void* parameter) {
391 *
static_cast<bool*
>(parameter) =
true;
395bool pinnedAdmission() {
396 bool entered =
false;
399 new Thread(current->
getParent(), forbiddenEntry, &entered,
nullptr,
false,
true,
true);
400 if (!check(peer !=
nullptr,
"pinned waiter allocation"))
403 peer->snapshotPlacement(before);
404 uint64_t generation = 0;
405 const AffinityResult result = peer->
requestAffinity(before.allowed, generation);
406 peer->snapshotPlacement(after);
407 bool passed = check(!before.migratable && result == AffinityResult::Pinned && !after.migratable &&
408 sameMask(before.allowed, after.allowed),
409 "fixed placement admission changed state");
412 FATAL(
"AFFINITY-CORE: unstarted pinned waiter could not be joined");
413 passed &= check(!entered,
"unstarted pinned waiter executed");
415 NOTICE(
"AFFINITY-CORE: PASS pinned admission");
420EXPORTED_PUBLIC
bool runAffinityRegressions() {
421 NOTICE(
"AFFINITY-CORE: BEGIN");
424 "fixture entry context"))
428 if (!check(online.contains(source),
"source CPU online"))
430 size_t destination = source;
431 for (
size_t cpu = 0; cpu < CpuAffinityMask::MaximumCpus; ++cpu) {
432 if (cpu != source && online.contains(cpu)) {
437 bool passed = pinnedAdmission() && legacyPins(source, source) && heldWake(source, source) &&
438 cancelPending(source, source);
439 if (destination != source) {
440 passed = passed && legacyPins(source, destination) && heldWake(source, destination) &&
441 cancelPending(source, destination);
443 NOTICE(
"AFFINITY-CORE: SKIP cross-CPU migration (one online CPU)");
446 NOTICE(
"AFFINITY-CORE: END PASS");
static bool getInterrupts()
static ProcessorInformation & information()
static bool inDeviceHardIrq()
static void setInterrupts(bool bEnable)
static Scheduler & instance()
void setUnwindState(UnwindType ut)
@ TerminateThread
Exit only this thread during Process exit.
bool tryPinLegacyUserCallbacks()
AffinityResult waitAffinity(uint64_t generation)
Process * getParent() const
class PerProcessorScheduler * getScheduler() const
AffinityResult requestAffinity(const CpuAffinityMask &mask, uint64_t &generation)