The Pedigree Project 0.1
Ps2MouseCallbackRegistry.cc
1/*
2 * Copyright (c) 2026, Pedigree Developers
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted.
6 */
7
8#include "Ps2MouseCallbackRegistry.h"
9#include "pedigree/kernel/Log.h"
10#include "pedigree/kernel/process/TerminationDeferral.h"
11#include "pedigree/kernel/process/Thread.h"
12#include "pedigree/kernel/processor/Processor.h"
13#include "pedigree/kernel/processor/ProcessorInformation.h"
14#include "pedigree/kernel/utilities/assert.h"
15
16Ps2MouseCallbackRegistry::CallbackSlot::CallbackSlot()
17 : handler(nullptr),
18 parameter(nullptr),
19 registration(nullptr),
20 generation(0),
21 inFlight(0),
22 enabled(false),
23 draining(false),
24 deferredRemoval(false),
25 dispatches(nullptr),
26 drainWaiters() {}
27
28Ps2MouseCallbackRegistry::Registration::Registration()
29 : m_pOwner(nullptr), m_pSlot(nullptr), m_Generation(0), m_Unregister(nullptr) {}
30
31Ps2MouseCallbackRegistry::Registration::~Registration() {
32 if (m_pSlot && !reset()) {
33 FATAL("Live PS/2 mouse callback registration could not be retired.");
34 }
35}
36
38 if (!m_pSlot) {
39 return true;
40 }
41
42 void* owner = m_pOwner;
43 void* slot = m_pSlot;
44 const size_t generation = m_Generation;
45 UnregisterThunk unregister = m_Unregister;
46 if (!unregister(owner, slot, generation, this)) {
47 return false;
48 }
49
50 releaseFromOwner(owner, slot, generation);
51 return true;
52}
53
54Ps2MouseCallbackRegistry::Ps2MouseCallbackRegistry()
55 : m_Callbacks(),
56 m_CallbackLock()
57#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
58 ,
59 m_CallbackPinHook(nullptr)
60#endif
61{
62}
63
64Ps2MouseCallbackRegistry::~Ps2MouseCallbackRegistry() {
65 m_CallbackLock.acquire();
66 const bool callbackContext = isCallbackContext(currentDispatchOwner());
67 m_CallbackLock.release();
68 if (callbackContext) {
69 FATAL("PS/2 mouse callback registry cannot be destroyed from callback context.");
70 }
71
72 for (size_t i = 0; i < MaxCallbacks; ++i) {
73 CallbackSlot& slot = m_Callbacks[i];
74 bool registered = false;
75 Registration* registration = nullptr;
76 size_t generation = 0;
77 {
78 m_CallbackLock.acquire();
79 registered = slot.handler != nullptr;
80 registration = slot.registration;
81 generation = slot.generation;
82 m_CallbackLock.release();
83 }
84
85 if (registered && !unregister(&slot, generation, registration)) {
86 FATAL("PS/2 mouse callback registry teardown did not complete.");
87 }
88 }
89}
90
91void* Ps2MouseCallbackRegistry::currentDispatchOwner() {
92 ProcessorInformation& information = Processor::information();
93 Thread* thread = information.getCurrentThread();
94 return thread ? static_cast<void*>(thread) : static_cast<void*>(&information);
95}
96
97bool Ps2MouseCallbackRegistry::unregisterThunk(void* owner, void* slot, size_t generation,
98 Registration* registration) {
99 return reinterpret_cast<Ps2MouseCallbackRegistry*>(owner)->unregister(
100 reinterpret_cast<CallbackSlot*>(slot), generation, registration);
101}
102
103void Ps2MouseCallbackRegistry::clearSlot(CallbackSlot& slot) {
104 assert(!slot.inFlight);
105 assert(!slot.dispatches);
106 if (slot.registration) {
107 slot.registration->releaseFromOwner(this, &slot, slot.generation);
108 }
109 slot.handler = nullptr;
110 slot.parameter = nullptr;
111 slot.registration = nullptr;
112 slot.enabled = false;
113 slot.draining = false;
114 slot.deferredRemoval = false;
115}
116
117bool Ps2MouseCallbackRegistry::subscribe(Handler handler, void* parameter,
118 Registration& registration) {
119 if (!handler || registration) {
120 return false;
121 }
122
123 m_CallbackLock.acquire();
124 for (size_t i = 0; i < MaxCallbacks; ++i) {
125 CallbackSlot& slot = m_Callbacks[i];
126 if (slot.handler) {
127 continue;
128 }
129
130 assert(!slot.inFlight);
131 assert(!slot.dispatches);
132 ++slot.generation;
133 if (!slot.generation) {
134 ++slot.generation;
135 }
136 slot.handler = handler;
137 slot.parameter = parameter;
138 slot.registration = &registration;
139 slot.enabled = true;
140 slot.draining = false;
141 slot.deferredRemoval = false;
142 registration.adopt(this, &slot, slot.generation, unregisterThunk);
143 m_CallbackLock.release();
144 return true;
145 }
146 m_CallbackLock.release();
147 return false;
148}
149
150bool Ps2MouseCallbackRegistry::isCallbackContext(void* owner) const {
151 for (size_t i = 0; i < MaxCallbacks; ++i) {
152 for (CallbackDispatch* dispatch = m_Callbacks[i].dispatches; dispatch;
153 dispatch = dispatch->next) {
154 if (dispatch->owner == owner) {
155 return true;
156 }
157 }
158 }
159 return false;
160}
161
162bool Ps2MouseCallbackRegistry::unregister(CallbackSlot* slot, size_t generation,
163 Registration* registration) {
164 Thread* current = Processor::information().getCurrentThread();
165 const bool canYield = current && Processor::getInterrupts();
166 TerminationDeferral terminationDeferral;
167 m_CallbackLock.acquire();
168 if (!slot || slot->generation != generation || !slot->handler) {
169 m_CallbackLock.release();
170 return true;
171 }
172 if (slot->registration && slot->registration != registration) {
173 m_CallbackLock.release();
174 return false;
175 }
176
177 slot->enabled = false;
178 if (!slot->inFlight) {
179 clearSlot(*slot);
180 m_CallbackLock.release();
181 return true;
182 }
183
184 void* owner = currentDispatchOwner();
185 bool ownsTarget = false;
186 bool targetOwnedByPeer = false;
187 for (CallbackDispatch* dispatch = slot->dispatches; dispatch; dispatch = dispatch->next) {
188 if (dispatch->owner == owner) {
189 ownsTarget = true;
190 } else {
191 targetOwnedByPeer = true;
192 }
193 }
194
195 if (isCallbackContext(owner)) {
196 if (ownsTarget && !targetOwnedByPeer) {
197 // The callback controls its own return path, so it can close admission
198 // and leave final slot retirement to releaseCallback(). Detach the token
199 // before returning so clearSlot() never touches a destroyed Registration.
200 slot->registration = nullptr;
201 slot->deferredRemoval = true;
202 m_CallbackLock.release();
203 return true;
204 }
205
206 // A live peer can be waiting for this callback in turn. Keep the token
207 // attached so a caller outside callback context can retry and drain it.
208 m_CallbackLock.release();
209 return false;
210 }
211
212 if (slot->inFlight && !canYield) {
213 m_CallbackLock.release();
214 return false;
215 }
216
217 slot->draining = true;
218 m_CallbackLock.release();
219
220 while (true) {
221 auto waitGuard = slot->drainWaiters.acquire();
222 m_CallbackLock.acquire();
223 if (slot->generation != generation || !slot->handler) {
224 m_CallbackLock.release();
225 return true;
226 }
227 if (!slot->inFlight) {
228 clearSlot(*slot);
229 m_CallbackLock.release();
230 return true;
231 }
232 m_CallbackLock.release();
233
234 const WaitQueue::WakeReason reason =
235 waitGuard.waitForCompletion(WaitQueue::Channel(slot), Thread::CallbackDrain,
236 reinterpret_cast<uintptr_t>(slot->parameter));
237 (void)reason;
238 }
239}
240
241void Ps2MouseCallbackRegistry::releaseCallback(CallbackSlot& slot, CallbackDispatch& dispatch) {
242 bool wakeDrainer = false;
243 m_CallbackLock.acquire();
244 CallbackDispatch** link = &slot.dispatches;
245 while (*link && *link != &dispatch) {
246 link = &(*link)->next;
247 }
248 assert(*link == &dispatch);
249 *link = dispatch.next;
250 assert(slot.inFlight);
251 --slot.inFlight;
252 wakeDrainer = !slot.inFlight && slot.draining;
253 if (!slot.inFlight && slot.deferredRemoval) {
254 clearSlot(slot);
255 }
256 m_CallbackLock.release();
257
258 if (wakeDrainer) {
259 slot.drainWaiters.wakeAll(WaitQueue::WakeReason::Signalled, WaitQueue::Channel(&slot));
260 }
261}
262
263void Ps2MouseCallbackRegistry::dispatch(const void* buffer, size_t length) {
264 TerminationDeferral callbackDeferral;
265 for (size_t i = 0; i < MaxCallbacks; ++i) {
266 CallbackDispatch dispatch = {currentDispatchOwner(), nullptr};
267 Handler handler = nullptr;
268 void* parameter = nullptr;
269
270 m_CallbackLock.acquire();
271 CallbackSlot& slot = m_Callbacks[i];
272 if (slot.handler && slot.enabled) {
273 handler = slot.handler;
274 parameter = slot.parameter;
275 ++slot.inFlight;
276 dispatch.next = slot.dispatches;
277 slot.dispatches = &dispatch;
278 }
279 m_CallbackLock.release();
280
281 if (!handler) {
282 continue;
283 }
284
285#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
286 CallbackPinHook hook = __atomic_load_n(&m_CallbackPinHook, __ATOMIC_ACQUIRE);
287 if (hook) {
288 hook(handler, parameter);
289 }
290#endif
291
292 handler(parameter, buffer, length);
293 releaseCallback(slot, dispatch);
294 }
295}
296
297#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
298void Ps2MouseCallbackRegistry::setCallbackPinHook(CallbackPinHook hook) {
299 __atomic_store_n(&m_CallbackPinHook, hook, __ATOMIC_RELEASE);
300}
301#endif
static bool getInterrupts()
static ProcessorInformation & information()
void release()
Definition Spinlock.cc:161
bool acquire(bool recurse=false, bool safe=true)
Definition Spinlock.cc:35