8#include "pedigree/kernel/Atomic.h"
9#include "pedigree/kernel/Log.h"
10#include "pedigree/kernel/process/PerProcessorScheduler.h"
11#include "pedigree/kernel/process/Scheduler.h"
12#include "pedigree/kernel/process/TerminationDeferral.h"
13#include "pedigree/kernel/process/Thread.h"
14#include "pedigree/kernel/process/Uninterruptible.h"
15#include "pedigree/kernel/processor/Processor.h"
16#include "pedigree/kernel/processor/SyscallHandler.h"
17#include "pedigree/kernel/processor/SyscallManager.h"
18#include "pedigree/kernel/processor/state.h"
19#include "pedigree/kernel/time/Time.h"
20#include "pedigree/kernel/utilities/utility.h"
23bool check(
bool condition,
const char* test,
const char* detail) {
28 ERROR(
"HOSTED-WAIT-TEST: FAIL " << test <<
": " << detail);
32struct HandlerLifetimeContext;
33HandlerLifetimeContext* g_HandlerLifetimeContext =
nullptr;
37 explicit LifetimeHandler(HandlerLifetimeContext& context) : m_Context(context) {}
39 uintptr_t
syscall(SyscallState&)
override;
42 HandlerLifetimeContext& m_Context;
45struct HandlerLifetimeContext {
46 HandlerLifetimeContext()
53 callbacksAfterReturn(0),
54 unregisterReturned(0),
55 unregisterSucceeded(0),
58 LifetimeHandler handler;
71uintptr_t LifetimeHandler::syscall(SyscallState&) {
72 m_Context.handlerCalls += 1;
73 if (m_Context.unregisterReturned) {
74 m_Context.callbacksAfterReturn += 1;
82 : m_Registration(registration), calls(0), rejectionSeen(0) {}
84 uintptr_t
syscall(SyscallState&)
override {
86 if (!m_Registration.reset() && m_Registration) {
97struct ReciprocalUnregisterContext;
101 ReciprocalUnregisterHandler(ReciprocalUnregisterContext& context,
bool first)
102 : m_Context(context), m_First(first) {}
104 uintptr_t
syscall(SyscallState&)
override;
107 ReciprocalUnregisterContext& m_Context;
111struct ReciprocalUnregisterContext {
112 ReciprocalUnregisterContext()
113 : first(*this, true),
114 second(*this, false),
121 ReciprocalUnregisterHandler first;
122 ReciprocalUnregisterHandler second;
132uintptr_t ReciprocalUnregisterHandler::syscall(SyscallState&) {
133 m_Context.entered += 1;
134 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
135 while (m_Context.entered !=
static_cast<size_t>(2) && Time::getTicks() < deadline) {
139 if (m_Context.entered !=
static_cast<size_t>(2)) {
140 m_Context.failures += 1;
144 m_Context.attempts += 1;
146 m_First ? m_Context.secondRegistration : m_Context.firstRegistration;
147 if (!peer.reset() && peer) {
148 m_Context.rejections += 1;
150 m_Context.failures += 1;
152 m_Context.resetReturns += 1;
153 const Time::Timestamp returnDeadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
154 while (m_Context.resetReturns !=
static_cast<size_t>(2) && Time::getTicks() < returnDeadline) {
157 if (m_Context.resetReturns !=
static_cast<size_t>(2)) {
158 m_Context.failures += 1;
160 return m_First ? 0x53 : 0x54;
163struct ReciprocalSyscallInvocation {
164 ReciprocalUnregisterContext* context;
169int invokeReciprocalSyscall(
void* parameter) {
170 ReciprocalSyscallInvocation* invocation =
171 reinterpret_cast<ReciprocalSyscallInvocation*
>(parameter);
176bool reciprocalUnregisterRejected() {
178 ReciprocalUnregisterContext context;
179 const bool registered =
183 if (context.firstRegistration) {
184 context.firstRegistration.reset();
186 return check(
false,
"syscall-reciprocal-unregister",
187 "the reciprocal handlers could not register");
190 ReciprocalSyscallInvocation first = {&context, TUI, 0};
191 ReciprocalSyscallInvocation second = {&context, native, 0};
193 invokeReciprocalSyscall, &first,
nullptr,
false,
true);
195 invokeReciprocalSyscall, &second,
nullptr,
false,
true);
196 firstThread->setName(
"hosted syscall reciprocal first");
197 secondThread->setName(
"hosted syscall reciprocal second");
199 const bool firstJoined = firstThread->
join();
200 const bool secondJoined = secondThread->
join();
201 const bool registrationsPreserved = context.firstRegistration && context.secondRegistration;
202 const bool firstRetired = context.firstRegistration.reset();
203 const bool secondRetired = context.secondRegistration.reset();
205 check(firstJoined && secondJoined && first.result == 0x53 && second.result == 0x54 &&
206 context.entered == 2 && context.attempts == 2 && context.rejections == 2 &&
207 context.resetReturns == 2 && context.failures == 0,
208 "syscall-reciprocal-unregister",
"reciprocal callbacks did not finish cleanly") &&
209 check(registrationsPreserved && firstRetired && secondRetired,
210 "syscall-reciprocal-unregister",
211 "callback-context rejection lost external cleanup ownership");
213 NOTICE(
"HOSTED-WAIT-TEST: PASS syscall-reciprocal-unregister");
219 HandlerLifetimeContext* context = g_HandlerLifetimeContext;
220 if (!context || service != TUI || handler != &context->handler ||
221 !context->phase.compareAndSwap(0, 1)) {
225 context->hookCalls += 1;
226 for (
size_t attempt = 0; attempt < 10000; ++attempt) {
228 uintptr_t debugAddress = 0;
229 if (context->phase ==
static_cast<size_t>(2) && context->remover->getWaitDebugInfo(info) &&
230 info.queue && info.channelOwner && info.queued &&
231 context->remover->getDebugState(debugAddress) == Thread::CallbackDrain &&
232 debugAddress ==
reinterpret_cast<uintptr_t
>(&context->handler)) {
233 context->hookObservedDrain += 1;
240 context->failures += 1;
244int unregisterPinnedHandler(
void* parameter) {
245 HandlerLifetimeContext* context =
reinterpret_cast<HandlerLifetimeContext*
>(parameter);
246 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
247 while (context->phase !=
static_cast<size_t>(1) && Time::getTicks() < deadline) {
251 if (context->phase !=
static_cast<size_t>(1)) {
252 context->failures += 1;
257 if (context->registration.reset()) {
258 context->unregisterSucceeded += 1;
260 context->unregisterReturned += 1;
264bool handlerLifetimeBarrier() {
266 HandlerLifetimeContext context;
268 &context,
nullptr,
false,
true);
269 context.remover->setName(
"hosted syscall-handler remover");
271 g_HandlerLifetimeContext = &context;
272 manager.setHandlerPinHook(handlerPinHook);
273 const bool registered =
276 const bool duplicateRejected =
278 const uintptr_t result = manager.
syscall(TUI, 0);
279 const bool joined = context.remover->join();
280 manager.setHandlerPinHook(
nullptr);
281 g_HandlerLifetimeContext =
nullptr;
283 const size_t callsAtUnregisterReturn = context.handlerCalls;
284 const uintptr_t lateResult = manager.
syscall(TUI, 0);
285 const bool lateDispatchRejected = lateResult == 0 &&
286 context.handlerCalls == callsAtUnregisterReturn &&
287 context.callbacksAfterReturn == 0;
291 const uintptr_t redispatchResult = reregistered ? manager.
syscall(TUI, 0) : 0;
293 const bool movePreservedOwnership = !reusable && moved && moved.reset();
296 SelfUnregisteringHandler selfUnregistering(selfRegistration);
297 const bool selfRegistered =
299 const uintptr_t selfResult = selfRegistered ? manager.
syscall(TUI, 0) : 0;
300 const bool selfCleanup = selfRegistered && selfRegistration.reset();
303 check(registered && duplicateRejected && result == 0x51,
"syscall-handler-lifetime",
304 "registration, token ownership, or dispatch failed") &&
305 check(joined && context.failures == 0,
"syscall-handler-lifetime",
306 "the unregister worker did not finish cleanly") &&
307 check(context.hookCalls == 1 && context.hookObservedDrain == 1 &&
308 context.unregisterSucceeded == 1 && context.unregisterReturned == 1,
309 "syscall-handler-lifetime",
"unregister returned before the admitted handler") &&
310 check(lateDispatchRejected,
"syscall-handler-lifetime",
311 "a handler ran after token reset returned") &&
312 check(reregistered && redispatchResult == 0x51 && movePreservedOwnership,
313 "syscall-handler-lifetime",
"generation-bearing token move or slot reuse failed") &&
314 check(selfRegistered && selfResult == 0x52 && selfUnregistering.calls == 1 &&
315 selfUnregistering.rejectionSeen == 1 && selfCleanup,
316 "syscall-handler-lifetime",
"self-reset changed ownership or reported safe teardown");
319 NOTICE(
"HOSTED-WAIT-TEST: PASS syscall-handler-lifetime");
326 PostActionHandler() : requested(
SyscallManager::NoPostSyscallAction), calls(0), failures(0) {}
328 uintptr_t
syscall(SyscallState& syscallState)
override {
330 if (syscallState.getSyscallNumber() == 1) {
335 void* dispatchContext = thread->getSyscallDispatchContext();
338 case SyscallManager::TerminateCurrentThread:
341 case SyscallManager::ExitCurrentProcess:
344 case SyscallManager::ReturnFromEvent:
347 case SyscallManager::PopEventState:
350 case SyscallManager::RestoreProcessorState: {
351 ProcessorState state;
353 state.state = 0x12345678;
355 volatile uint64_t* source = &state.state;
356 *source = 0x87654321;
359 case SyscallManager::JumpToUserspace:
362 case SyscallManager::RebootSystem:
365 case SyscallManager::NoPostSyscallAction:
373 if (requested != SyscallManager::NoPostSyscallAction) {
374 ProcessorState rejectedState;
375 rejectedState.state = 0xabcdef;
383 thread->getSyscallDispatchContext() != dispatchContext) {
389 SyscallManager::PostSyscallActionKind requested;
394struct PostActionContext {
395 SyscallManager::PostSyscallActionKind expected = SyscallManager::NoPostSyscallAction;
396 intptr_t expectedValue = 0;
397 void* previousContext =
nullptr;
404PostActionContext* g_PostActionContext =
nullptr;
406bool postActionHook(SyscallManager::PostSyscallActionKind kind, intptr_t value,
407 const ProcessorState* state) {
408 PostActionContext* context = g_PostActionContext;
415 if (kind != context->expected || value != context->expectedValue || !thread ||
417 thread->getSyscallDispatchContext() != context->previousContext) {
418 context->failures += 1;
420 const bool needsState =
421 kind == SyscallManager::RestoreProcessorState || kind == SyscallManager::JumpToUserspace;
422 if (needsState != (state !=
nullptr) ||
423 (state && state->state != (kind == SyscallManager::RestoreProcessorState ? 0x12345678 : 0))) {
424 context->failures += 1;
426 if (context->registration && context->registration->reset()) {
427 context->retired += 1;
429 context->failures += 1;
434bool postSyscallActions() {
435 static const SyscallManager::PostSyscallActionKind actions[] = {
436 SyscallManager::TerminateCurrentThread, SyscallManager::ExitCurrentProcess,
437 SyscallManager::ReturnFromEvent, SyscallManager::PopEventState,
438 SyscallManager::RestoreProcessorState, SyscallManager::NoPostSyscallAction,
439 SyscallManager::JumpToUserspace, SyscallManager::RebootSystem};
440 constexpr size_t ActionCount =
sizeof(actions) /
sizeof(actions[0]);
443 PostActionHandler handler;
444 PostActionContext context;
446 context.previousContext = thread->getSyscallDispatchContext();
447 g_PostActionContext = &context;
448 manager.setPostSyscallHook(postActionHook);
450 bool loopPassed =
true;
451 for (
size_t i = 0; i < ActionCount; ++i) {
452 const bool needsEventState = actions[i] == SyscallManager::ReturnFromEvent ||
453 actions[i] == SyscallManager::PopEventState;
454 const bool stateReady = !needsEventState || (thread && thread->
pushState() !=
nullptr);
456 context.expected = actions[i];
457 context.expectedValue = actions[i] == SyscallManager::ExitCurrentProcess ? 37 : 0;
458 context.registration = ®istration;
459 handler.requested = actions[i];
460 const bool noAction = actions[i] == SyscallManager::NoPostSyscallAction;
461 const size_t hookCalls = context.calls;
462 const bool dispatched = stateReady &&
464 manager.
syscall(TUI, 0) == 0x61;
465 if (!dispatched || thread->getSyscallDispatchContext() != context.previousContext ||
466 (noAction ? !registration || context.calls != hookCalls || !registration.reset()
467 :
static_cast<bool>(registration))) {
470 registration.reset();
473 if (needsEventState && stateReady) {
478 manager.setPostSyscallHook(
nullptr);
479 g_PostActionContext =
nullptr;
481 check(loopPassed && handler.calls == 2 * ActionCount && handler.failures == 0 &&
482 context.calls == ActionCount - 1 && context.retired == ActionCount - 1 &&
483 context.failures == 0,
484 "syscall-post-actions",
"post-action state, nesting, rejection, or retirement failed");
486 NOTICE(
"HOSTED-WAIT-TEST: PASS syscall-post-actions");
493 InvalidEventActionHandler()
494 : requested(
SyscallManager::NoPostSyscallAction), calls(0), rejections(0) {}
496 uintptr_t
syscall(SyscallState&)
override {
497 bool accepted =
false;
498 if (requested == SyscallManager::ReturnFromEvent) {
500 }
else if (requested == SyscallManager::PopEventState) {
511 SyscallManager::PostSyscallActionKind requested;
516bool baseStateEventActionsRejected() {
519 InvalidEventActionHandler handler;
521 const bool registered = thread && !thread->
getStateLevel() &&
524 handler.requested = SyscallManager::ReturnFromEvent;
525 const uintptr_t returnResult = registered ? manager.
syscall(TUI, 0) : 0;
526 handler.requested = SyscallManager::PopEventState;
527 const uintptr_t popResult = registered ? manager.
syscall(TUI, 0) : 0;
528 const bool retired = registered && registration.reset();
531 check(registered && returnResult == 0x62 && popResult == 0x62 && handler.calls == 2 &&
532 handler.rejections == 2 && retired && !thread->
getStateLevel(),
533 "syscall-event-action-boundary",
"a base-state event return or pop was accepted");
536 "HOSTED-WAIT-TEST: PASS "
537 "syscall-event-action-boundary");
542struct AbandonedStackContext;
546 explicit AbandoningHandler(AbandonedStackContext& context) : m_Context(context) {}
548 uintptr_t
syscall(SyscallState&)
override;
551 AbandonedStackContext& m_Context;
554struct AbandonedStackContext {
555 AbandonedStackContext() : handler(*this), entered(0), returned(0), destructed(0) {}
557 AbandoningHandler handler;
563class AbandonedStackCanary {
565 explicit AbandonedStackCanary(
Atomic<size_t>* destructed) : m_Destructed(destructed) {}
567 ~AbandonedStackCanary() {
575uintptr_t AbandoningHandler::syscall(SyscallState&) {
583 AbandonedStackCanary stackCanary(&m_Context.destructed);
584 m_Context.entered += 1;
586 PerProcessorScheduler::StackDiscardReason::HostedRegression);
589int invokeAbandoningSyscall(
void* parameter) {
590 AbandonedStackContext* context =
reinterpret_cast<AbandonedStackContext*
>(parameter);
592 context->returned += 1;
596bool abandonedSyscallStack() {
598 AbandonedStackContext context;
601 return check(
false,
"syscall-abandoned-stack",
"the abandoning handler could not register");
605 PerProcessorScheduler::StackDiscardReason::HostedRegression);
607 &context,
nullptr,
false,
true);
608 thread->setName(
"hosted abandoning syscall");
609 const bool joined = thread->
join();
610 const bool retired = registration.reset();
612 const bool passed = check(
613 joined && retired && context.entered == 1 && context.returned == 0 &&
614 context.destructed == 0 &&
616 PerProcessorScheduler::StackDiscardReason::HostedRegression) == discardsBefore + 1,
617 "syscall-abandoned-stack",
618 "forced teardown did not count exactly one intentionally leaked stack");
620 NOTICE(
"HOSTED-WAIT-TEST: PASS syscall-abandoned-stack");
626bool runHostedPostSyscallRegressions() {
627 return postSyscallActions() && baseStateEventActionsRejected();
630bool runHostedSyscallRegressions() {
631 return handlerLifetimeBarrier() && reciprocalUnregisterRejected() && postSyscallActions() &&
632 baseStateEventActionsRejected() && abandonedSyscallStack();
void abandonCurrentThreadStack(StackDiscardReason reason, Spinlock *pLock=0) NORETURN
static size_t stackDiscardCount()
static ProcessorInformation & information()
static Scheduler & instance()
virtual uintptr_t syscall(SyscallState &State)=0
virtual uintptr_t syscall(Service_t service, uintptr_t function, uintptr_t p1=0, uintptr_t p2=0, uintptr_t p3=0, uintptr_t p4=0, uintptr_t p5=0)=0
virtual bool registerSyscallHandler(Service_t Service, SyscallHandler *pHandler, Registration ®istration, FastEntry entry=nullptr)=0
static EXPORTED_PUBLIC SyscallManager & instance()
EXPORTED_PUBLIC bool requestThreadExit()
void popState(bool clean=true)
class PerProcessorScheduler * getScheduler() const
bool isTerminationDeferred() const
SchedulerState * pushState()
size_t getStateLevel() const