9#include "pedigree/kernel/Atomic.h"
10#include "pedigree/kernel/Log.h"
11#include "pedigree/kernel/core/SlamAllocator.h"
12#include "pedigree/kernel/machine/IrqHandler.h"
13#include "pedigree/kernel/machine/IrqHandlerRegistry.h"
14#include "pedigree/kernel/machine/Machine.h"
15#include "pedigree/kernel/machine/SplitIrqHandler.h"
16#include "pedigree/kernel/machine/ThreadedIrqDispatcher.h"
17#include "pedigree/kernel/machine/Timer.h"
18#include "pedigree/kernel/machine/TimerHandler.h"
19#include "pedigree/kernel/process/Scheduler.h"
20#include "pedigree/kernel/process/Semaphore.h"
21#include "pedigree/kernel/process/Thread.h"
22#include "pedigree/kernel/process/WaitQueue.h"
23#include "pedigree/kernel/processor/Processor.h"
24#include "pedigree/kernel/processor/ProcessorInformation.h"
25#include "pedigree/kernel/time/Time.h"
27#include "system/kernel/core/processor/DeviceHardIrqContext.h"
29extern bool runHostedInterruptManagerRegressions();
30extern bool runHostedSchedulerRegressions();
31extern void system_reset();
34constexpr Time::Timestamp TestTimeout = 2 * Time::Multiplier::Second;
36bool check(
bool condition,
const char* test,
const char* detail) {
41 ERROR(
"HOSTED-IRQ-CLOSURE: FAIL " << test <<
": " << detail);
45template <
class Predicate>
46bool waitUntil(Predicate predicate) {
47 const Time::Timestamp deadline = Time::getTicks() + TestTimeout;
48 while (!predicate() && Time::getTicks() < deadline) {
54struct DispatcherContext {
55 DispatcherContext() : calls(0), lastCookie(0), contextFailures(0), driver(nullptr) {}
63void dispatchThreaded(
void* opaque, uint8_t line,
size_t cookie) {
64 DispatcherContext* context =
reinterpret_cast<DispatcherContext*
>(opaque);
69 current->getHostedSignalDepth()) {
70 context->contextFailures += 1;
72 context->lastCookie = cookie;
76bool threadedDispatcherLifecycle() {
77 constexpr const char* Test =
"threaded-dispatcher-lifecycle";
78 DispatcherContext context;
81 dispatchThreaded, &context);
83 bool passed = check(dispatcher.initialise(), Test,
"initialise failed");
84 passed &= check(!dispatcher.publishFromInterrupt(1, 1) && !dispatcher.publishFromInterrupt(0, 0),
85 Test,
"invalid publications were accepted");
89 const bool firstPublished = dispatcher.publishFromInterrupt(0, 7);
90 const bool newerPublished = dispatcher.publishFromInterrupt(0, 9);
93 passed &= check(firstPublished && newerPublished &&
94 waitUntil([&dispatcher] {
return dispatcher.completedCookie(0) == 9; }),
95 Test,
"the worker did not consume the published high-water cookie");
97 dispatcher.rejectNextPublicationForTest();
99 const bool rejectionObserved = !dispatcher.publishFromInterrupt(0, 11);
100 const bool recoveryPublished = dispatcher.publishFromInterrupt(0, 12);
102 passed &= check(rejectionObserved && recoveryPublished &&
103 waitUntil([&dispatcher] {
return dispatcher.completedCookie(0) == 12; }),
104 Test,
"a rejected publication poisoned the next occurrence");
106 const size_t callsBeforeShutdown = context.calls.value();
107 const bool shutdown = dispatcher.shutdown();
108 passed &= check(shutdown && callsBeforeShutdown && !context.contextFailures &&
109 !dispatcher.isInitialised() && !dispatcher.publishFromInterrupt(0, 13),
110 Test,
"shutdown or callback context invariants failed");
113 NOTICE(
"HOSTED-IRQ-CLOSURE: PASS threaded-dispatcher-lifecycle");
118class CallbackIdProbe final :
public IrqHandler {
120 CallbackIdProbe() : seen(0) {}
123 return IrqDisposition::Handled;
128bool registryCallbackId() {
129 constexpr const char* Test =
"registry-callback-id";
130 constexpr uint8_t Key = 2;
131 constexpr irq_id_t PublicId = 35;
133 CallbackIdProbe probe;
135 "registration failed")) {
143 const bool admitted = published && registry.
dispatchThreaded(Key, 1, result,
nullptr, PublicId);
146 registry.
unregisterHandler(Key, &probe) == IrqHandlerRegistry::UnregisterResult::Completed;
148 check(admitted && result.handled && result.allowRearm && probe.seen == PublicId && retired,
149 Test,
"callback did not receive the public IRQ ID");
151 NOTICE(
"HOSTED-IRQ-CLOSURE: PASS " << Test);
158 explicit SplitLifecycleProbe(
Thread* driver)
164 quiesceContextFailures(0),
165 rearmContextFailures(0),
170 ~SplitLifecycleProbe()
override {
181 bool publish(
size_t work) {
182 return publishWorkForTest(work);
201 using SplitIrqHandler::completedBatchesForTest;
202 using SplitIrqHandler::deferredIrqsForTest;
203 using SplitIrqHandler::pendingWorkForTest;
204 using SplitIrqHandler::publicationFailuresForTest;
205 using SplitIrqHandler::rejectNextPublicationForTest;
208 HardStageDisposition
hardIrq(irq_id_t, InterruptState&,
size_t&)
override {
209 return HardStageDisposition::NotHandled;
216 current->getHostedSignalDepth()) {
217 contextFailures += 1;
219 if (current != m_Driver) {
222 threadedWork |= work;
228 quiesceContextFailures += 1;
237 rearmContextFailures += 1;
246bool splitHandlerLifecycle() {
247 constexpr const char* Test =
"split-handler-lifecycle";
249 if (!check(probe.start(), Test,
"initialise failed")) {
256 bool firstPublished =
false;
257 bool secondPublished =
false;
259 size_t previousDepth = 0;
260 bool restorationArmed =
false;
262 firstPublished = probe.publish(1);
263 secondPublished = probe.publish(2);
267 passed &= check(firstPublished && secondPublished && waitUntil([&probe] {
268 return probe.completedBatchesForTest() && !probe.pendingWorkForTest();
270 probe.threadedWork.value() == 3 && probe.rearmedWork.value() == 3 &&
271 probe.workerCalls.value() && probe.deferredIrqsForTest() == 2 &&
272 !probe.publicationFailuresForTest(),
273 Test,
"accepted hard work did not drain and rearm on the worker");
275 probe.rejectNextPublicationForTest();
277 bool rejectionObserved =
false;
279 size_t previousDepth = 0;
280 bool restorationArmed =
false;
282 rejectionObserved = !probe.publish(4);
286 const size_t rearmsBeforeShutdown = probe.rearmedWork.value();
287 const bool orphanRecorded = probe.pendingWorkForTest() == 4;
288 const bool stopped = probe.stop();
289 passed &= check(rejectionObserved && orphanRecorded && probe.publicationFailuresForTest() == 1,
290 Test,
"the forced rejection did not leave one orphan batch");
291 passed &= check(stopped, Test,
"shutdown rejected an ordinary caller");
292 passed &= check(!probe.pendingWorkForTest() && probe.threadedWork.value() == 7, Test,
293 "shutdown did not drain the orphan batch");
294 passed &= check(probe.rearmedWork.value() == rearmsBeforeShutdown, Test,
295 "shutdown rearmed a quiesced source");
296 passed &= check(probe.quiesceCalls.value() == 2, Test,
297 "shutdown did not reassert source quiescence after draining");
298 passed &= check(!probe.contextFailures, Test,
299 "the threaded callback ran outside ordinary waitable context");
300 passed &= check(!probe.quiesceContextFailures, Test,
301 "source quiescence ran outside ordinary waitable context");
303 check(!probe.rearmContextFailures, Test,
"source rearm did not run with interrupts disabled");
306 NOTICE(
"HOSTED-IRQ-CLOSURE: PASS split-handler-lifecycle");
313 explicit TimerContextProbe(
Thread* driver)
314 : calls(0), elapsed(0), contextFailures(0), m_Driver(driver) {}
316 void timer(uint64_t delta)
override {
321 current->getHostedSignalDepth()) {
322 contextFailures += 1;
336bool hostedTimerSplitDelivery() {
337 constexpr const char* Test =
"hosted-timer-split-delivery";
341 check(timer && timer->registerHandler(&probe), Test,
"timer handler registration failed");
342 passed &= check(waitUntil([&probe] {
return probe.calls.value() >= 3; }), Test,
343 "the hard timer source did not reach its threaded callback");
345 const bool removed = timer && timer->unregisterHandler(&probe);
346 const size_t callsAfterRemoval = probe.calls.value();
347 const Time::Timestamp drainDeadline = Time::getTicks() + (10 * Time::Multiplier::Millisecond);
348 while (Time::getTicks() < drainDeadline) {
351 passed &= check(removed && callsAfterRemoval >= 3 && probe.calls.value() == callsAfterRemoval &&
352 probe.elapsed.value() && !probe.contextFailures,
353 Test,
"threaded delivery context or unregister drain failed");
356 NOTICE(
"HOSTED-IRQ-CLOSURE: PASS hosted-timer-split-delivery");
361constexpr size_t ExpectedGuardDenials = 7;
364 GuardContext() : calls(0), overflow(0), operations() {}
371GuardContext* g_GuardContext =
nullptr;
374 GuardContext* context = __atomic_load_n(&g_GuardContext, __ATOMIC_ACQUIRE);
378 const size_t call = __atomic_fetch_add(&context->calls,
static_cast<size_t>(1), __ATOMIC_RELAXED);
379 if (call < ExpectedGuardDenials) {
380 context->operations[call] = operation;
382 context->overflow = 1;
387bool hardIrqOperationGuards() {
388 constexpr const char* Test =
"hard-irq-operation-guards";
389 GuardContext context;
392 __atomic_store_n(&g_GuardContext, &context, __ATOMIC_RELEASE);
393 Processor::setDeviceHardIrqOperationHookForTest(denyHardIrqOperation);
394 const size_t denialsBefore = Processor::deviceHardIrqOperationDenialsForTest();
397 bool hardContext =
false;
398 bool scheduleReturned =
false;
399 bool semaphoreAcquireDenied =
false;
400 bool semaphoreReleaseDenied =
false;
401 bool waitDenied =
false;
402 bool wakeDenied =
false;
403 bool allocateDenied =
false;
407 size_t previousDepth = 0;
408 bool restorationArmed =
false;
414 scheduleReturned =
true;
416 Semaphore::SemaphoreError error = Semaphore::NoError;
417 semaphoreAcquireDenied =
418 !semaphore.acquireWithError(1, 0, 0, error) && error == Semaphore::Interrupted;
419 semaphore.release(1);
420 semaphoreReleaseDenied = semaphore.getValue() == 0;
423 auto guard = waitQueue.acquire();
424 waitDenied = guard.
wait() == WaitQueue::WakeReason::Spurious;
426 wakeDenied = waitQueue.wakeAll(WaitQueue::WakeReason::Signalled) == 0;
427 allocateDenied = SlamAllocator::guardedAllocateForTest(64) == 0;
428 SlamAllocator::guardedFreeForTest(0);
432 Processor::setDeviceHardIrqOperationHookForTest(
nullptr);
433 __atomic_store_n(&g_GuardContext,
static_cast<GuardContext*
>(
nullptr), __ATOMIC_RELEASE);
434 const size_t denialsAfter = Processor::deviceHardIrqOperationDenialsForTest();
437 DeviceHardIrqOperation::Schedule, DeviceHardIrqOperation::SemaphoreAcquire,
438 DeviceHardIrqOperation::SemaphoreRelease, DeviceHardIrqOperation::WaitQueueAccess,
439 DeviceHardIrqOperation::WaitQueueAccess, DeviceHardIrqOperation::HeapAllocate,
440 DeviceHardIrqOperation::HeapFree,
442 bool operationsMatch = context.calls == ExpectedGuardDenials && !context.overflow;
443 for (
size_t i = 0; i < ExpectedGuardDenials && operationsMatch; ++i) {
444 operationsMatch = context.operations[i] == expected[i];
447 const bool passed = check(
448 marked && hardContext && scheduleReturned && semaphoreAcquireDenied &&
449 semaphoreReleaseDenied && waitDenied && wakeDenied && allocateDenied && operationsMatch &&
452 waitQueue.waiterCount() == 0 && semaphore.getValue() == 0,
453 Test,
"a forbidden operation escaped or the hard context leaked");
455 NOTICE(
"HOSTED-IRQ-CLOSURE: PASS hard-irq-operation-guards");
461bool runHostedIrqClosureRegressions() {
462 NOTICE(
"HOSTED-IRQ-CLOSURE: BEGIN");
463 bool passed = hardIrqOperationGuards();
464 passed &= threadedDispatcherLifecycle();
465 passed &= registryCallbackId();
466 passed &= splitHandlerLifecycle();
467 passed &= hostedTimerSplitDelivery();
468 passed &= runHostedInterruptManagerRegressions();
469 passed &= runHostedSchedulerRegressions();
471 NOTICE(
"HOSTED-IRQ-CLOSURE: PASS all");
473 ERROR(
"HOSTED-IRQ-CLOSURE: FAIL suite");
bool publishThreadedDispatch(uint8_t irq, size_t dispatchGeneration)
bool registerThreadedHandler(uint8_t irq, IrqHandler *handler)
UnregisterResult unregisterHandler(uint8_t irq, IrqHandlerBase *handler)
void invalidateThreadedLine(uint8_t irq, size_t throughGeneration)
bool dispatchThreaded(uint8_t irq, size_t dispatchGeneration, ThreadedDispatchResult &result, IrqHandler *onlyHandler=nullptr, irq_id_t callbackId=0)
virtual IrqDisposition irq(irq_id_t number)=0
virtual Timer * getTimer()=0
static bool getInterrupts()
static ProcessorInformation & information()
static bool inDeviceHardIrq()
static ExecutionContext executionContext()
static void setInterrupts(bool bEnable)
static Scheduler & instance()
virtual void rearmIrqSources(size_t work)=0
virtual bool quiesceIrqSources()=0
virtual HardStageDisposition hardIrq(irq_id_t number, InterruptState &state, size_t &work)=0
virtual void threadedIrq(size_t work)=0
bool initialiseSplitIrq()
virtual void timer(uint64_t delta)=0
MUST_USE_RESULT WakeReason wait(const Channel &channel=Channel(), size_t debugState=0, uintptr_t debugAddress=0, StackDiscardCleanup onStackDiscard=nullptr, void *stackDiscardContext=nullptr)