The Pedigree Project 0.1
PageFaultHandler.cc
1/*
2 * Copyright (c) 2008-2014, Pedigree Developers
3 *
4 * Please see the CONTRIB file in the root of the source tree for a full
5 * list of contributors.
6 *
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 */
19
20#include "pedigree/kernel/LockGuard.h"
21#include "pedigree/kernel/Log.h"
22#include "pedigree/kernel/process/TerminationDeferral.h"
23#include "pedigree/kernel/process/Thread.h"
24#include "pedigree/kernel/processor/PageFaultHandler.h"
25#include "pedigree/kernel/processor/Processor.h"
26#include "pedigree/kernel/processor/ProcessorInformation.h"
27#include "pedigree/kernel/processor/state.h"
28
29MemoryTrapHandler::~MemoryTrapHandler() = default;
30
31#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
32PageFaultHandler::HandlerPinHook PageFaultHandler::m_HandlerPinHook = nullptr;
33PageFaultHandler::HandlerPrePinHook PageFaultHandler::m_HandlerPrePinHook = nullptr;
34PageFaultHandler::AtomicDrainHook PageFaultHandler::m_AtomicDrainHook = nullptr;
35#endif
36
38 : m_Handlers(), m_ActiveDispatches(), m_DispatchWaiters(), m_HandlerLock(false) {}
39
40size_t PageFaultHandler::makePublication(size_t generation, SlotMode mode) {
41 return (generation << GenerationShift) | static_cast<size_t>(mode);
42}
43
44size_t PageFaultHandler::generationOf(size_t publication) {
45 return publication >> GenerationShift;
46}
47
48PageFaultHandler::SlotMode PageFaultHandler::modeOf(size_t publication) {
49 return static_cast<SlotMode>(publication & ModeMask);
50}
51
52bool PageFaultHandler::retireSlot(HandlerSlot& slot, size_t expectedPublication,
53 MemoryTrapHandler* expectedHandler) {
54 const size_t retiringPublication =
55 makePublication(generationOf(expectedPublication), SlotMode::Retiring);
56 if (!__atomic_compare_exchange_n(&slot.publication, &expectedPublication, retiringPublication,
57 false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)) {
58 return false;
59 }
60
61 MemoryTrapHandler* handler = __atomic_load_n(&slot.handler, __ATOMIC_ACQUIRE);
62 if (handler != expectedHandler) {
63 size_t expectedRetiringPublication = retiringPublication;
64 __atomic_compare_exchange_n(&slot.publication, &expectedRetiringPublication,
65 expectedPublication, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST);
66 return false;
67 }
68
69 __atomic_store_n(&slot.handler, nullptr, __ATOMIC_RELEASE);
70 __atomic_store_n(&slot.publication,
71 makePublication(generationOf(retiringPublication), SlotMode::Empty),
72 __ATOMIC_SEQ_CST);
73 return true;
74}
75
76bool PageFaultHandler::publishDispatch(HandlerSlot& slot, void* owner, DispatchCleanup& cleanup) {
77 void* token = &cleanup;
78 for (size_t i = 0; i < MaxActiveDispatches; ++i) {
79 ActiveDispatch& dispatch = m_ActiveDispatches[i];
80 // Select before claiming so cleanup can release even a partial publication.
81 __atomic_store_n(&cleanup.activeDispatch, &dispatch, __ATOMIC_RELAXED);
82 void* expectedToken = nullptr;
83 if (__atomic_compare_exchange_n(&dispatch.token, &expectedToken, token, false, __ATOMIC_SEQ_CST,
84 __ATOMIC_SEQ_CST)) {
85 __atomic_add_fetch(&dispatch.generation, static_cast<size_t>(1), __ATOMIC_ACQ_REL);
86 __atomic_store_n(&dispatch.owner, owner, __ATOMIC_RELAXED);
87 // This final store commits the callback hazard. Admission close
88 // either observes it or wins the total order and is then observed
89 // by dispatch revalidation.
90 __atomic_store_n(&dispatch.slot, &slot, __ATOMIC_SEQ_CST);
91 return true;
92 }
93 }
94
95 return false;
96}
97
98void PageFaultHandler::unpublishDispatch(DispatchCleanup& cleanup) {
99 ActiveDispatch* dispatch = __atomic_load_n(&cleanup.activeDispatch, __ATOMIC_RELAXED);
100 void* token = &cleanup;
101 // Cleanup can run before the selected entry was claimed.
102 if (!dispatch || __atomic_load_n(&dispatch->token, __ATOMIC_ACQUIRE) != token) {
103 return;
104 }
105
106 HandlerSlot* releasedSlot = __atomic_load_n(&dispatch->slot, __ATOMIC_SEQ_CST);
107 __atomic_store_n(&dispatch->slot, nullptr, __ATOMIC_SEQ_CST);
108 __atomic_store_n(&dispatch->owner, nullptr, __ATOMIC_RELAXED);
109 __atomic_store_n(&dispatch->token, nullptr, __ATOMIC_RELEASE);
110 if (!releasedSlot) {
111 return;
112 }
113
114 const size_t publication = __atomic_load_n(&releasedSlot->publication, __ATOMIC_SEQ_CST);
115 const SlotMode mode = modeOf(publication);
116 if (mode != SlotMode::Draining && mode != SlotMode::Deferred) {
117 return;
118 }
119 // The hazard is already clear. A later remover will observe its release;
120 // only a removal already in progress needs a scan and waiter notification.
121 if (hasActiveDispatch(*releasedSlot)) {
122 return;
123 }
124
125 const size_t drainGeneration = generationOf(publication);
126 auto guard = m_DispatchWaiters.acquire();
127 const size_t finalPublication = __atomic_load_n(&releasedSlot->publication, __ATOMIC_SEQ_CST);
128 if (generationOf(finalPublication) == drainGeneration &&
129 modeOf(finalPublication) == SlotMode::Deferred && !hasActiveDispatch(*releasedSlot)) {
130 MemoryTrapHandler* handler = __atomic_load_n(&releasedSlot->handler, __ATOMIC_ACQUIRE);
131 if (handler) {
132 retireSlot(*releasedSlot, finalPublication, handler);
133 }
134 }
135
136 guard.wakeAll(WaitQueue::WakeReason::Signalled,
137 WaitQueue::Channel(releasedSlot, drainGeneration));
138}
139
140void PageFaultHandler::abandonedHandlerCleanup(void* context) {
141 DispatchCleanup* dispatch = reinterpret_cast<DispatchCleanup*>(context);
142 if (dispatch && dispatch->registry) {
143 dispatch->registry->unpublishDispatch(*dispatch);
144 }
145}
146
147bool PageFaultHandler::hasActiveDispatch(HandlerSlot& target) const {
148 for (size_t i = 0; i < MaxActiveDispatches; ++i) {
149 const ActiveDispatch& dispatch = m_ActiveDispatches[i];
150 void* token = __atomic_load_n(&dispatch.token, __ATOMIC_ACQUIRE);
151 if (!token) {
152 continue;
153 }
154
155 const size_t generation = __atomic_load_n(&dispatch.generation, __ATOMIC_ACQUIRE);
156 HandlerSlot* slot = __atomic_load_n(&dispatch.slot, __ATOMIC_SEQ_CST);
157 if (slot == &target && __atomic_load_n(&dispatch.token, __ATOMIC_ACQUIRE) == token &&
158 __atomic_load_n(&dispatch.generation, __ATOMIC_ACQUIRE) == generation) {
159 return true;
160 }
161 }
162 return false;
163}
164
165bool PageFaultHandler::findCurrentDispatch(void* owner, HandlerSlot* target,
166 bool& callbackContext) const {
167 callbackContext = false;
168 bool foundTarget = false;
169 for (size_t i = 0; i < MaxActiveDispatches; ++i) {
170 const ActiveDispatch& dispatch = m_ActiveDispatches[i];
171 void* token = __atomic_load_n(&dispatch.token, __ATOMIC_ACQUIRE);
172 if (!token) {
173 continue;
174 }
175
176 const size_t generation = __atomic_load_n(&dispatch.generation, __ATOMIC_ACQUIRE);
177 void* dispatchOwner = __atomic_load_n(&dispatch.owner, __ATOMIC_RELAXED);
178 HandlerSlot* slot = __atomic_load_n(&dispatch.slot, __ATOMIC_SEQ_CST);
179 if (__atomic_load_n(&dispatch.token, __ATOMIC_ACQUIRE) != token ||
180 __atomic_load_n(&dispatch.generation, __ATOMIC_ACQUIRE) != generation) {
181 continue;
182 }
183
184 if (dispatchOwner == owner && slot) {
185 callbackContext = true;
186 foundTarget |= slot == target;
187 }
188 }
189 return foundTarget;
190}
191
192void* PageFaultHandler::currentDispatchOwner() {
193 ProcessorInformation& information = Processor::information();
194 Thread* thread = information.getCurrentThread();
195 return thread ? static_cast<void*>(thread) : static_cast<void*>(&information);
196}
197
199 if (!pHandler) {
200 return false;
201 }
202
203 bool callbackContext = false;
204 findCurrentDispatch(currentDispatchOwner(), nullptr, callbackContext);
205 if (callbackContext) {
206 return false;
207 }
208
209 LockGuard<Spinlock> guard(m_HandlerLock);
210 for (size_t i = 0; i < MaxMemoryTrapHandlers; ++i) {
211 HandlerSlot& slot = m_Handlers[i];
212 const size_t publication = __atomic_load_n(&slot.publication, __ATOMIC_SEQ_CST);
213 if (__atomic_load_n(&slot.handler, __ATOMIC_ACQUIRE) == pHandler &&
214 modeOf(publication) != SlotMode::Empty) {
215 return false;
216 }
217 }
218
219 for (size_t i = 0; i < MaxMemoryTrapHandlers; ++i) {
220 HandlerSlot& slot = m_Handlers[i];
221 const size_t publication = __atomic_load_n(&slot.publication, __ATOMIC_SEQ_CST);
222 if (modeOf(publication) == SlotMode::Empty &&
223 !__atomic_load_n(&slot.handler, __ATOMIC_ACQUIRE)) {
224 const size_t generation = generationOf(publication) + 1;
225 if (!generation) {
226 return false;
227 }
228 __atomic_store_n(&slot.handler, pHandler, __ATOMIC_RELEASE);
229 __atomic_store_n(&slot.publication, makePublication(generation, SlotMode::Enabled),
230 __ATOMIC_SEQ_CST);
231 return true;
232 }
233 }
234
235 return false;
236}
237
239 if (!pHandler) {
240 return false;
241 }
242
243 void* owner = currentDispatchOwner();
244 Thread* current = Processor::information().getCurrentThread();
245 const bool canYield = current && Processor::getInterrupts();
246 bool callbackContext = false;
247 findCurrentDispatch(owner, nullptr, callbackContext);
248
249 // Exception and callback contexts cannot wait on a writer or on another
250 // callback. Self-removal closes admission and lets the final callback
251 // hazard retire the slot.
252 if (!canYield || callbackContext) {
253 for (size_t i = 0; i < MaxMemoryTrapHandlers; ++i) {
254 HandlerSlot& slot = m_Handlers[i];
255 size_t publication = __atomic_load_n(&slot.publication, __ATOMIC_SEQ_CST);
256 if (modeOf(publication) == SlotMode::Empty ||
257 __atomic_load_n(&slot.handler, __ATOMIC_ACQUIRE) != pHandler) {
258 continue;
259 }
260
261 bool currentTargetDispatch = false;
262 const bool selfUnregister = findCurrentDispatch(owner, &slot, currentTargetDispatch);
263 if (selfUnregister) {
264 while (true) {
265 const SlotMode mode = modeOf(publication);
266 if (mode == SlotMode::Deferred || mode == SlotMode::Empty || mode == SlotMode::Retiring) {
267 return false;
268 }
269 if (mode != SlotMode::Enabled && mode != SlotMode::Draining) {
270 return false;
271 }
272
273 const size_t deferredPublication =
274 makePublication(generationOf(publication), SlotMode::Deferred);
275 if (__atomic_compare_exchange_n(&slot.publication, &publication, deferredPublication,
276 false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)) {
277 return false;
278 }
279 if (__atomic_load_n(&slot.handler, __ATOMIC_ACQUIRE) != pHandler) {
280 return false;
281 }
282 }
283 }
284
285 if (callbackContext || modeOf(publication) != SlotMode::Enabled) {
286 return false;
287 }
288
289 const size_t drainingPublication =
290 makePublication(generationOf(publication), SlotMode::Draining);
291 if (!__atomic_compare_exchange_n(&slot.publication, &publication, drainingPublication, false,
292 __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)) {
293 return false;
294 }
295
296#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
297 AtomicDrainHook drainHook = __atomic_load_n(&m_AtomicDrainHook, __ATOMIC_ACQUIRE);
298 if (drainHook) {
299 drainHook(pHandler);
300 }
301#endif
302
303 if (hasActiveDispatch(slot)) {
304 size_t expectedPublication = drainingPublication;
305 __atomic_compare_exchange_n(
306 &slot.publication, &expectedPublication,
307 makePublication(generationOf(drainingPublication), SlotMode::Enabled), false,
308 __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST);
309 return false;
310 }
311
312 return retireSlot(slot, drainingPublication, pHandler);
313 }
314 return false;
315 }
316
317 // Keep this ordinary unregister stack alive while admitted callbacks
318 // drain. Dispatch itself never touches ordinary deferral state.
319 TerminationDeferral terminationDeferral;
320 m_HandlerLock.acquire();
321
322 HandlerSlot* slot = nullptr;
323 size_t publication = 0;
324 for (size_t i = 0; i < MaxMemoryTrapHandlers; ++i) {
325 const size_t candidatePublication =
326 __atomic_load_n(&m_Handlers[i].publication, __ATOMIC_SEQ_CST);
327 if (modeOf(candidatePublication) != SlotMode::Empty &&
328 __atomic_load_n(&m_Handlers[i].handler, __ATOMIC_ACQUIRE) == pHandler) {
329 slot = &m_Handlers[i];
330 publication = candidatePublication;
331 break;
332 }
333 }
334
335 if (!slot || modeOf(publication) != SlotMode::Enabled) {
336 m_HandlerLock.release();
337 return false;
338 }
339
340 size_t expectedPublication = publication;
341 const size_t drainingPublication = makePublication(generationOf(publication), SlotMode::Draining);
342 if (!__atomic_compare_exchange_n(&slot->publication, &expectedPublication, drainingPublication,
343 false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)) {
344 m_HandlerLock.release();
345 return false;
346 }
347
348 m_HandlerLock.release();
349
350 const size_t drainGeneration = generationOf(drainingPublication);
351 while (true) {
352 auto guard = m_DispatchWaiters.acquire();
353 const size_t finalPublication = __atomic_load_n(&slot->publication, __ATOMIC_SEQ_CST);
354 MemoryTrapHandler* finalHandler = __atomic_load_n(&slot->handler, __ATOMIC_ACQUIRE);
355 if (generationOf(finalPublication) != drainGeneration) {
356 return true;
357 }
358
359 const SlotMode finalMode = modeOf(finalPublication);
360 if (finalMode == SlotMode::Empty) {
361 return finalHandler != pHandler;
362 }
363 if (finalHandler != pHandler ||
364 (finalMode != SlotMode::Draining && finalMode != SlotMode::Deferred)) {
365 return false;
366 }
367 if (!hasActiveDispatch(*slot)) {
368 if (retireSlot(*slot, finalPublication, pHandler)) {
369 return true;
370 }
371 continue;
372 }
373
374 const WaitQueue::WakeReason reason =
375 guard.waitForCompletion(WaitQueue::Channel(slot, drainGeneration), Thread::CallbackDrain,
376 reinterpret_cast<uintptr_t>(pHandler));
377 (void)reason;
378 }
379}
380
381bool PageFaultHandler::dispatchHandlers(InterruptState& state, uintptr_t address, bool bIsWrite,
382 bool bWasPresent, MemoryTrapHandler* pOnlyHandler) {
383 for (size_t i = 0; i < MaxMemoryTrapHandlers; ++i) {
384 HandlerSlot& slot = m_Handlers[i];
385 const size_t publication = __atomic_load_n(&slot.publication, __ATOMIC_SEQ_CST);
386 if (modeOf(publication) != SlotMode::Enabled) {
387 continue;
388 }
389
390 MemoryTrapHandler* handler = __atomic_load_n(&slot.handler, __ATOMIC_ACQUIRE);
391 if (!handler || (pOnlyHandler && handler != pOnlyHandler)) {
392 continue;
393 }
394
395#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
396 HandlerPrePinHook prePinHook = __atomic_load_n(&m_HandlerPrePinHook, __ATOMIC_ACQUIRE);
397 if (prePinHook) {
398 prePinHook(handler);
399 }
400#endif
401
402 Thread* thread = Processor::information().getCurrentThread();
403 DispatchCleanup dispatchCleanup(this);
404 if (thread) {
405 // Cleanup is visible before callback admission commits. Every
406 // later stack-abandonment point can therefore release the hazard.
407 thread->armAtomicStateCleanup(dispatchCleanup.cleanup, abandonedHandlerCleanup,
408 &dispatchCleanup);
409 }
410
411 if (!publishDispatch(slot, currentDispatchOwner(), dispatchCleanup)) {
412 if (thread) {
413 thread->disarmAtomicStateCleanup(dispatchCleanup.cleanup);
414 }
415 FATAL_NOLOCK("Page-fault callback hazard table exhausted.");
416 return false;
417 }
418
419 if (__atomic_load_n(&slot.publication, __ATOMIC_SEQ_CST) != publication ||
420 __atomic_load_n(&slot.handler, __ATOMIC_ACQUIRE) != handler) {
421 unpublishDispatch(dispatchCleanup);
422 if (thread) {
423 thread->disarmAtomicStateCleanup(dispatchCleanup.cleanup);
424 }
425 continue;
426 }
427
428#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
429 HandlerPinHook hook = __atomic_load_n(&m_HandlerPinHook, __ATOMIC_ACQUIRE);
430 if (hook) {
431 hook(handler);
432 }
433#endif
434
435 const bool handled = handler->trap(state, address, bIsWrite, bWasPresent);
436 unpublishDispatch(dispatchCleanup);
437 if (thread) {
438 thread->disarmAtomicStateCleanup(dispatchCleanup.cleanup);
439 }
440
441 if (handled) {
442 return true;
443 }
444 }
445
446 return false;
447}
448
449#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
450void PageFaultHandler::setHandlerPinHook(HandlerPinHook hook) {
451 __atomic_store_n(&m_HandlerPinHook, hook, __ATOMIC_RELEASE);
452}
453
454void PageFaultHandler::setHandlerPrePinHook(HandlerPrePinHook hook) {
455 __atomic_store_n(&m_HandlerPrePinHook, hook, __ATOMIC_RELEASE);
456}
457
458void PageFaultHandler::setAtomicDrainHook(AtomicDrainHook hook) {
459 __atomic_store_n(&m_AtomicDrainHook, hook, __ATOMIC_RELEASE);
460}
461
462void PageFaultHandler::withMutationLockForTest(MutationLockHook hook) {
463 m_HandlerLock.acquire();
464 if (hook) {
465 hook();
466 }
467 m_HandlerLock.release();
468}
469
470bool PageFaultHandler::dispatchHandlerForTest(MemoryTrapHandler* pHandler) {
471 InterruptState state;
472 return dispatchHandlers(state, 0, false, false, pHandler);
473}
474
475size_t PageFaultHandler::activeDispatchCountForTest(MemoryTrapHandler* pHandler) {
476 size_t count = 0;
477 for (size_t i = 0; i < MaxActiveDispatches; ++i) {
478 ActiveDispatch& dispatch = m_ActiveDispatches[i];
479 void* token = __atomic_load_n(&dispatch.token, __ATOMIC_ACQUIRE);
480 if (!token) {
481 continue;
482 }
483 const size_t generation = __atomic_load_n(&dispatch.generation, __ATOMIC_ACQUIRE);
484 HandlerSlot* slot = __atomic_load_n(&dispatch.slot, __ATOMIC_SEQ_CST);
485 MemoryTrapHandler* handler = slot ? __atomic_load_n(&slot->handler, __ATOMIC_ACQUIRE) : nullptr;
486 if (handler == pHandler && __atomic_load_n(&dispatch.token, __ATOMIC_ACQUIRE) == token &&
487 __atomic_load_n(&dispatch.generation, __ATOMIC_ACQUIRE) == generation) {
488 ++count;
489 }
490 }
491 return count;
492}
493#endif
virtual bool trap(InterruptState &state, uintptr_t address, bool bIsWrite, bool bWasPresent)=0
HandlerSlot m_Handlers[MaxMemoryTrapHandlers]
EXPORTED_PUBLIC bool unregisterHandler(MemoryTrapHandler *pHandler)
EXPORTED_PUBLIC bool registerHandler(MemoryTrapHandler *pHandler)
PageFaultHandler() INITIALISATION_ONLY
static bool getInterrupts()
static ProcessorInformation & information()
void release()
Definition Spinlock.cc:168
bool acquire(bool recurse=false, bool safe=true)
Definition Spinlock.cc:36