8#include "pedigree/kernel/Atomic.h"
9#include "pedigree/kernel/Log.h"
10#include "pedigree/kernel/process/Scheduler.h"
11#include "pedigree/kernel/process/Semaphore.h"
12#include "pedigree/kernel/process/Thread.h"
13#include "pedigree/kernel/time/Time.h"
15#include "modules/drivers/x86/ps2mouse/Ps2MouseCallbackRegistry.h"
18bool check(
bool condition,
const char* detail) {
23 ERROR(
"HOSTED-WAIT-TEST: FAIL ps2mouse-callback-lifetime: " << detail);
27struct CallbackLifetimeContext {
28 CallbackLifetimeContext()
35 callbacksAfterReturn(0),
36 unregisterReturned(0),
50CallbackLifetimeContext* g_CallbackLifetimeContext =
nullptr;
52void lifetimeCallback(
void* parameter,
const void*,
size_t) {
53 CallbackLifetimeContext* context =
reinterpret_cast<CallbackLifetimeContext*
>(parameter);
54 context->callbackCalls += 1;
55 if (context->unregisterReturned) {
56 context->callbacksAfterReturn += 1;
60void callbackPinHook(Ps2MouseCallbackRegistry::Handler handler,
void* parameter) {
61 CallbackLifetimeContext* context = g_CallbackLifetimeContext;
62 if (!context || handler != lifetimeCallback || parameter != context ||
63 !context->phase.compareAndSwap(0, 1)) {
67 context->hookCalls += 1;
68 for (
size_t attempt = 0; attempt < 10000; ++attempt) {
70 uintptr_t debugAddress = 0;
71 if (context->phase ==
static_cast<size_t>(2) && context->remover->getWaitDebugInfo(info) &&
72 info.queue && info.channelOwner && info.queued &&
73 context->remover->getDebugState(debugAddress) == Thread::CallbackDrain &&
74 debugAddress ==
reinterpret_cast<uintptr_t
>(context)) {
81 uintptr_t debugAddress = 0;
82 if (context->phase ==
static_cast<size_t>(2) && !context->unregisterReturned &&
83 context->remover->getWaitDebugInfo(info) && info.queue && info.channelOwner && info.queued &&
84 context->remover->getDebugState(debugAddress) == Thread::CallbackDrain &&
85 debugAddress ==
reinterpret_cast<uintptr_t
>(context)) {
86 context->hookObservedDrain += 1;
88 context->failures += 1;
93int unregisterPinnedCallback(
void* parameter) {
94 CallbackLifetimeContext* context =
reinterpret_cast<CallbackLifetimeContext*
>(parameter);
95 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
96 while (context->phase !=
static_cast<size_t>(1) && Time::getTicks() < deadline) {
100 if (context->phase !=
static_cast<size_t>(1)) {
101 context->failures += 1;
106 if (!context->registration.reset()) {
107 context->failures += 1;
109 context->unregisterReturned += 1;
114struct SelfRemovalContext {
115 SelfRemovalContext() : registration(), calls(0) {}
121void selfRemovingCallback(
void* parameter,
const void*,
size_t) {
122 SelfRemovalContext* context =
reinterpret_cast<SelfRemovalContext*
>(parameter);
124 context->registration.reset();
127bool callbackLifetimeBarrier() {
129 CallbackLifetimeContext context;
131 &context,
nullptr,
false,
true);
132 context.remover->setName(
"hosted PS/2 callback remover");
134 g_CallbackLifetimeContext = &context;
135 registry.setCallbackPinHook(callbackPinHook);
136 const bool registered = registry.subscribe(lifetimeCallback, &context, context.registration);
137 const bool duplicateRejected =
138 !registry.subscribe(lifetimeCallback, &context, context.registration);
139 const uint8_t
byte = 0x7F;
140 registry.dispatch(&
byte, 1);
141 const bool joined = context.remover->join();
142 registry.setCallbackPinHook(
nullptr);
143 g_CallbackLifetimeContext =
nullptr;
145 const size_t callsAtUnregisterReturn = context.callbackCalls;
146 registry.dispatch(&
byte, 1);
147 const bool lateDispatchRejected =
148 context.callbackCalls == callsAtUnregisterReturn && context.callbacksAfterReturn == 0;
150 const bool reregistered = registry.subscribe(lifetimeCallback, &context, context.registration);
152 registry.dispatch(&
byte, 1);
153 context.registration.reset();
156 SelfRemovalContext selfRemoval;
157 const bool selfRegistered =
158 registry.subscribe(selfRemovingCallback, &selfRemoval, selfRemoval.registration);
159 if (selfRegistered) {
160 registry.dispatch(&
byte, 1);
161 registry.dispatch(&
byte, 1);
163 const bool selfReregistered =
164 registry.subscribe(selfRemovingCallback, &selfRemoval, selfRemoval.registration);
165 selfRemoval.registration.reset();
168 passed &= check(registered && duplicateRejected,
"registration or duplicate rejection failed");
169 passed &= check(joined && context.failures == 0,
170 "the concurrent callback remover did not finish cleanly");
172 context.hookCalls == 1 && context.hookObservedDrain == 1 && context.unregisterReturned == 1,
173 "unregister returned instead of waiting for the admitted callback");
174 passed &= check(context.callbackCalls >= 1 && lateDispatchRejected,
175 "a callback began after its registration was reset");
176 passed &= check(reregistered && context.callbackCalls == callsAtUnregisterReturn + 1,
177 "the drained callback slot could not be reused");
179 selfRegistered && selfRemoval.calls == 1 && !selfRemoval.registration && selfReregistered,
180 "self-removal did not retire the callback after it returned");
183 NOTICE(
"HOSTED-WAIT-TEST: PASS ps2mouse-callback-lifetime");
188struct ReciprocalRemovalContext {
189 ReciprocalRemovalContext()
200 invocationsFinished(0),
218 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
219 while (value != expected && Time::getTicks() < deadline) {
222 return value == expected;
225void firstReciprocalCallback(
void* parameter,
const void*,
size_t) {
226 ReciprocalRemovalContext* context =
reinterpret_cast<ReciprocalRemovalContext*
>(parameter);
227 context->firstCalls += 1;
228 if (!context->firstParticipant.compareAndSwap(0, 1)) {
232 context->callbacksEntered += 1;
233 if (!context->beginReset.acquireForCompletion()) {
234 context->failures += 1;
237 if (!context->second.reset() && context->second) {
238 context->resetRejections += 1;
240 context->failures += 1;
242 context->resetsFinished += 1;
243 if (!waitForValue(context->resetsFinished, 2)) {
244 context->failures += 1;
248void secondReciprocalCallback(
void* parameter,
const void*,
size_t) {
249 ReciprocalRemovalContext* context =
reinterpret_cast<ReciprocalRemovalContext*
>(parameter);
250 context->secondCalls += 1;
251 context->callbacksEntered += 1;
252 if (!context->beginReset.acquireForCompletion()) {
253 context->failures += 1;
256 if (!context->first.reset() && context->first) {
257 context->resetRejections += 1;
259 context->failures += 1;
261 context->resetsFinished += 1;
262 if (!waitForValue(context->resetsFinished, 2)) {
263 context->failures += 1;
267int dispatchReciprocalCallbacks(
void* parameter) {
268 ReciprocalRemovalContext* context =
reinterpret_cast<ReciprocalRemovalContext*
>(parameter);
269 const uint8_t
byte = 0x5A;
270 context->registry->dispatch(&
byte, 1);
271 context->invocationsFinished += 1;
275bool reciprocalRemovalIsRetryable() {
277 ReciprocalRemovalContext context;
278 context.registry = ®istry;
280 const bool firstRegistered = registry.subscribe(firstReciprocalCallback, &context, context.first);
281 const bool secondRegistered =
282 registry.subscribe(secondReciprocalCallback, &context, context.second);
285 Thread* firstInvoker =
nullptr;
286 Thread* secondInvoker =
nullptr;
287 bool firstEntered =
false;
288 bool bothEntered =
false;
289 if (firstRegistered && secondRegistered) {
290 firstInvoker =
new Thread(process, dispatchReciprocalCallbacks, &context,
nullptr,
false,
true);
291 firstInvoker->setName(
"hosted PS/2 reciprocal callback A");
292 firstEntered = waitForValue(context.callbacksEntered, 1);
296 new Thread(process, dispatchReciprocalCallbacks, &context,
nullptr,
false,
true);
297 secondInvoker->setName(
"hosted PS/2 reciprocal callback B");
298 bothEntered = waitForValue(context.callbacksEntered, 2);
302 context.beginReset.release(2);
305 const bool tokensPreserved = context.first && context.second;
307 const size_t firstCalls = context.firstCalls;
308 const size_t secondCalls = context.secondCalls;
309 const uint8_t
byte = 0xA5;
310 registry.dispatch(&
byte, 1);
311 const bool admissionClosed =
312 context.firstCalls == firstCalls && context.secondCalls == secondCalls;
314 const bool firstRetired = context.first && context.first.reset();
315 const bool secondRetired = context.second && context.second.reset();
316 const bool firstReused = registry.subscribe(firstReciprocalCallback, &context, context.first);
317 const bool secondReused = registry.subscribe(secondReciprocalCallback, &context, context.second);
318 const bool firstReuseRetired = context.first.reset();
319 const bool secondReuseRetired = context.second.reset();
321 const bool passed = check(
322 firstRegistered && secondRegistered && firstEntered && bothEntered && firstJoined &&
323 secondJoined && tokensPreserved && admissionClosed && firstRetired && secondRetired &&
324 firstReused && secondReused && firstReuseRetired && secondReuseRetired &&
325 !context.first && !context.second && context.firstCalls ==
static_cast<size_t>(2) &&
326 context.secondCalls ==
static_cast<size_t>(1) &&
327 context.resetRejections ==
static_cast<size_t>(2) &&
328 context.resetsFinished ==
static_cast<size_t>(2) &&
329 context.invocationsFinished ==
static_cast<size_t>(2) && !context.failures,
330 "reciprocal callbacks did not preserve retryable registration ownership");
332 NOTICE(
"HOSTED-WAIT-TEST: PASS ps2mouse-reciprocal-removal");
338bool runHostedPs2MouseRegressions() {
339 return callbackLifetimeBarrier() && reciprocalRemovalIsRetryable();
static Scheduler & instance()