The Pedigree Project 0.1
IrqHandlerRegistry.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 "pedigree/kernel/ActivityDiagnostics.h"
9#include "pedigree/kernel/LockGuard.h"
10#include "pedigree/kernel/Log.h"
11#include "pedigree/kernel/machine/IrqHandler.h"
12#include "pedigree/kernel/machine/IrqHandlerRegistry.h"
13#include "pedigree/kernel/process/Scheduler.h"
14#include "pedigree/kernel/process/TerminationDeferral.h"
15#include "pedigree/kernel/process/Thread.h"
16#include "pedigree/kernel/processor/Processor.h"
17#include "pedigree/kernel/processor/ProcessorInformation.h"
18#include "pedigree/kernel/processor/state.h"
19#include "pedigree/kernel/utilities/assert.h"
20
21#include "system/kernel/core/processor/DeviceHardIrqContext.h"
22
23static_assert(__atomic_always_lock_free(sizeof(size_t), nullptr),
24 "IRQ callback hazard words must be lock-free");
25static_assert(__atomic_always_lock_free(sizeof(void*), nullptr),
26 "IRQ callback hazard pointers must be lock-free");
27
28IrqHandlerRegistry::IrqHandlerRegistry()
29 : m_Handlers(),
30 m_ActiveDispatches(),
31 m_HardHandoffEpochs(),
32 m_ThreadedInvalidationGenerations(),
33 m_ThreadedActionMutationGeneration(0),
34 m_ThreadedActionMutationWriters(0),
35 m_OccurrenceEpochs(),
36 m_OccurrenceReaders(),
37 m_OccurrenceBoundaryLocks(),
38 m_HandlerLock(false),
39 m_AdmissionEpoch(0),
40 m_MutationGeneration(0),
41 m_MutationWriters(0)
42#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
43 ,
44 m_HandlerPinHook(nullptr),
45 m_HandlerPrePinHook(nullptr),
46 m_HandlerHazardHook(nullptr),
47 m_DispatchAbandonHook(nullptr),
48 m_OccurrenceCaptureHook(nullptr)
49#endif
50{
51}
52
53size_t IrqHandlerRegistry::makePublication(size_t generation, uint8_t irq, SlotMode mode,
54 Delivery delivery) {
55 return (generation << GenerationShift) | (static_cast<size_t>(irq) << IrqShift) |
56 (static_cast<size_t>(delivery) << DeliveryShift) | static_cast<size_t>(mode);
57}
58
59size_t IrqHandlerRegistry::generationOf(size_t publication) {
60 return publication >> GenerationShift;
61}
62
63uint8_t IrqHandlerRegistry::irqOf(size_t publication) {
64 return static_cast<uint8_t>((publication & IrqMask) >> IrqShift);
65}
66
67IrqHandlerRegistry::SlotMode IrqHandlerRegistry::modeOf(size_t publication) {
68 return static_cast<SlotMode>(publication & ModeMask);
69}
70
71IrqHandlerRegistry::Delivery IrqHandlerRegistry::deliveryOf(size_t publication) {
72 return static_cast<Delivery>((publication & DeliveryMask) >> DeliveryShift);
73}
74
75bool IrqHandlerRegistry::generationReached(size_t current, size_t target) {
76 return static_cast<intptr_t>(current - target) >= 0;
77}
78
79bool IrqHandlerRegistry::threadedGenerationValid(uint8_t irq, size_t generation) const {
80 if (!generation) {
81 return false;
82 }
83
84 const size_t invalidThrough =
85 __atomic_load_n(&m_ThreadedInvalidationGenerations[irq], __ATOMIC_ACQUIRE);
86 return !invalidThrough || !generationReached(invalidThrough, generation);
87}
88
89size_t* IrqHandlerRegistry::quiescedLane(HandlerSlot& slot, QuiescedLane lane) {
90 const size_t index = static_cast<size_t>(lane);
91 assert(index < QuiescedLaneCount);
92 return &slot.quiescedThreadedGenerations[index];
93}
94
95const size_t* IrqHandlerRegistry::quiescedLane(const HandlerSlot& slot, QuiescedLane lane) {
96 const size_t index = static_cast<size_t>(lane);
97 assert(index < QuiescedLaneCount);
98 return &slot.quiescedThreadedGenerations[index];
99}
100
101bool IrqHandlerRegistry::hasQuiescedGeneration(const HandlerSlot& slot) {
102 for (size_t i = 0; i < QuiescedLaneCount; ++i) {
103 if (__atomic_load_n(&slot.quiescedThreadedGenerations[i], __ATOMIC_ACQUIRE)) {
104 return true;
105 }
106 }
107 return false;
108}
109
110void IrqHandlerRegistry::publishSlotQuiesced(HandlerSlot& slot, uint8_t irq,
111 size_t dispatchGeneration, QuiescedLane lane) {
112 if (!dispatchGeneration) {
113 return;
114 }
115
116 ThreadedActionMutationCleanup actionMutation(this);
117 beginThreadedActionMutation(actionMutation);
118 if (!threadedGenerationValid(irq, dispatchGeneration)) {
119 finishThreadedActionMutation(actionMutation);
120 return;
121 }
122
123 publishSlotQuiescedValue(slot, irq, dispatchGeneration, lane);
124 finishThreadedActionMutation(actionMutation);
125}
126
127void IrqHandlerRegistry::publishSlotQuiescedValue(HandlerSlot& slot, uint8_t irq,
128 size_t dispatchGeneration, QuiescedLane lane) {
129 if (!dispatchGeneration || !threadedGenerationValid(irq, dispatchGeneration)) {
130 return;
131 }
132
133#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
134 HandlerHazardHook mutationHook = __atomic_load_n(&m_HandlerHazardHook, __ATOMIC_ACQUIRE);
135 if (mutationHook) {
136 mutationHook(__atomic_load_n(&slot.handler, __ATOMIC_ACQUIRE),
137 HandlerHazardStage::BeforeQuiescedExchange);
138 }
139#endif
140
141 size_t* publicationLane = quiescedLane(slot, lane);
142 const size_t published = __atomic_load_n(publicationLane, __ATOMIC_ACQUIRE);
143 if (published && generationReached(published, dispatchGeneration)) {
144 return;
145 }
146
147 __atomic_exchange_n(publicationLane, dispatchGeneration, __ATOMIC_ACQ_REL);
148
149#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
150 mutationHook = __atomic_load_n(&m_HandlerHazardHook, __ATOMIC_ACQUIRE);
151 if (mutationHook) {
152 mutationHook(__atomic_load_n(&slot.handler, __ATOMIC_ACQUIRE),
153 HandlerHazardStage::QuiescedExchanged);
154 }
155#endif
156
157 if (!threadedGenerationValid(irq, dispatchGeneration)) {
158 size_t stale = dispatchGeneration;
159 __atomic_compare_exchange_n(publicationLane, &stale, static_cast<size_t>(0), false,
160 __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE);
161 }
162}
163
165 size_t throughGeneration) {
166 if (!throughGeneration) {
167 return;
168 }
169
170 const size_t invalidThrough =
171 __atomic_load_n(&m_ThreadedInvalidationGenerations[irq], __ATOMIC_ACQUIRE);
172 if (invalidThrough && generationReached(invalidThrough, throughGeneration)) {
173 return;
174 }
175
176 __atomic_exchange_n(&m_ThreadedInvalidationGenerations[irq], throughGeneration, __ATOMIC_ACQ_REL);
177}
178
179void IrqHandlerRegistry::invalidateThreadedLine(uint8_t irq, size_t throughGeneration) {
180 if (!throughGeneration) {
181 return;
182 }
183
184 ThreadedActionMutationCleanup actionMutation(this);
185 beginThreadedActionMutation(actionMutation);
186 size_t invalidThrough =
187 __atomic_load_n(&m_ThreadedInvalidationGenerations[irq], __ATOMIC_ACQUIRE);
188 while (!invalidThrough || generationReached(throughGeneration, invalidThrough)) {
189 if (__atomic_compare_exchange_n(&m_ThreadedInvalidationGenerations[irq], &invalidThrough,
190 throughGeneration, false, __ATOMIC_RELEASE, __ATOMIC_ACQUIRE)) {
191 break;
192 }
193 }
194 if (invalidThrough && generationReached(invalidThrough, throughGeneration)) {
195 throughGeneration = invalidThrough;
196 }
197
198 for (size_t i = 0; i < MaxHandlerSlots; ++i) {
199 HandlerSlot& slot = m_Handlers[i];
200 const size_t publication = __atomic_load_n(&slot.publication, __ATOMIC_SEQ_CST);
201 if (irqOf(publication) != irq || deliveryOf(publication) != Delivery::Threaded) {
202 continue;
203 }
204
205 void* owner = currentDispatchOwner();
206 Thread* thread = Processor::information().getCurrentThread();
207 DispatchCleanup cleanup(this, &slot, owner, publication, false);
208#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
209 HandlerHazardHook mutationHook = __atomic_load_n(&m_HandlerHazardHook, __ATOMIC_ACQUIRE);
210 if (mutationHook) {
211 mutationHook(__atomic_load_n(&slot.handler, __ATOMIC_ACQUIRE),
212 HandlerHazardStage::BeforeActionMutationPin);
213 }
214#endif
215 if (!pinActionMutation(slot, publication, cleanup, thread)) {
216 continue;
217 }
218
219 size_t* actions[] = {&slot.pendingThreadedGeneration, &slot.claimedThreadedGeneration,
220 quiescedLane(slot, QuiescedLane::Controller),
221 quiescedLane(slot, QuiescedLane::Callback),
222 quiescedLane(slot, QuiescedLane::Retirement)};
223 for (size_t action = 0; action < sizeof(actions) / sizeof(actions[0]); ++action) {
224 size_t generation = __atomic_load_n(actions[action], __ATOMIC_ACQUIRE);
225 while (generation && generationReached(throughGeneration, generation)) {
226 if (__atomic_compare_exchange_n(actions[action], &generation, static_cast<size_t>(0), false,
227 __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE)) {
228 break;
229 }
230 }
231 }
232 unpinActionMutation(slot, publication, cleanup, thread);
233 }
234 finishThreadedActionMutation(actionMutation);
235 tryReclaimTombstones(irq);
236}
237
238size_t IrqHandlerRegistry::encodePolicy(const IrqPolicy* policy) {
239 if (!policy) {
240 return 0;
241 }
242
243 return PolicyValid | (static_cast<size_t>(policy->trigger()) << PolicyTriggerShift) |
244 (static_cast<size_t>(policy->controllerAck()) << PolicyControllerAckShift) |
245 (static_cast<size_t>(policy->lineRelease()) << PolicyLineReleaseShift);
246}
247
248void IrqHandlerRegistry::decodePolicy(size_t policy, LineConfiguration& configuration) {
249 configuration.policyConfigured = policy & PolicyValid;
250 if (!configuration.policyConfigured) {
251 return;
252 }
253
254 configuration.trigger = static_cast<IrqTrigger>((policy >> PolicyTriggerShift) & 3);
255 configuration.controllerAck =
256 static_cast<IrqControllerAck>((policy >> PolicyControllerAckShift) & 3);
257 configuration.lineRelease = static_cast<IrqLineRelease>((policy >> PolicyLineReleaseShift) & 1);
258}
259
260bool IrqHandlerRegistry::mixedPoliciesCompatible(size_t first, size_t second) {
261 if (!(first & PolicyValid) || !(second & PolicyValid)) {
262 return false;
263 }
264
265 return (first & PolicyMixedCompatibilityMask) == (second & PolicyMixedCompatibilityMask);
266}
267
268size_t IrqHandlerRegistry::effectiveMixedPolicy(size_t hard, size_t threaded) {
269 size_t effective = hard;
270 if (threaded & PolicyLineReleaseMask) {
271 effective |= PolicyLineReleaseMask;
272 }
273 return effective;
274}
275
276IrqHandlerRegistry::LineMode IrqHandlerRegistry::lineModeForDelivery(Delivery delivery) {
277 return delivery == Delivery::Threaded ? LineMode::Threaded : LineMode::HardOnly;
278}
279
280void IrqHandlerRegistry::beginMutation() {
281 // A global epoch keeps the registry compact. Unrelated line churn can only
282 // make a diagnostic attempt conservatively fail.
283 __atomic_add_fetch(&m_MutationWriters, static_cast<size_t>(1), __ATOMIC_SEQ_CST);
284}
285
286void IrqHandlerRegistry::finishMutation() {
287 // Publish the new epoch before dropping the final writer. Snapshot readers
288 // sample the writer count before the epoch at their closing boundary.
289 __atomic_add_fetch(&m_MutationGeneration, static_cast<size_t>(1), __ATOMIC_SEQ_CST);
290 __atomic_sub_fetch(&m_MutationWriters, static_cast<size_t>(1), __ATOMIC_SEQ_CST);
291}
292
293void IrqHandlerRegistry::beginThreadedActionMutation(ThreadedActionMutationCleanup& cleanup) {
294 const bool interruptsWereEnabled = Processor::getInterrupts();
296 cleanup.thread = Processor::information().getCurrentThread();
297 if (cleanup.thread) {
298 cleanup.thread->armAtomicStateCleanup(cleanup.cleanup, abandonThreadedActionMutation, &cleanup);
299 }
300 __atomic_add_fetch(&m_ThreadedActionMutationWriters, static_cast<size_t>(1), __ATOMIC_SEQ_CST);
301 Processor::setInterrupts(interruptsWereEnabled);
302}
303
304void IrqHandlerRegistry::finishThreadedActionMutation(ThreadedActionMutationCleanup& cleanup) {
305 const bool interruptsWereEnabled = Processor::getInterrupts();
307 completeThreadedActionMutation();
308 if (cleanup.thread) {
309 cleanup.thread->disarmAtomicStateCleanup(cleanup.cleanup);
310 }
311 cleanup.registry = nullptr;
312 Processor::setInterrupts(interruptsWereEnabled);
313}
314
315void IrqHandlerRegistry::completeThreadedActionMutation() {
316 __atomic_add_fetch(&m_ThreadedActionMutationGeneration, static_cast<size_t>(1), __ATOMIC_SEQ_CST);
317 const size_t previous = __atomic_fetch_sub(&m_ThreadedActionMutationWriters,
318 static_cast<size_t>(1), __ATOMIC_SEQ_CST);
319 if (!previous) {
320 __atomic_store_n(&m_ThreadedActionMutationWriters, static_cast<size_t>(0), __ATOMIC_SEQ_CST);
321 FATAL_NOLOCK("IRQ action-mutation writer count underflowed.");
322 }
323}
324
325void IrqHandlerRegistry::abandonThreadedActionMutation(void* context) {
326 ThreadedActionMutationCleanup* cleanup =
327 reinterpret_cast<ThreadedActionMutationCleanup*>(context);
328 if (!cleanup || !cleanup->registry) {
329 return;
330 }
331
332 cleanup->registry->completeThreadedActionMutation();
333 cleanup->registry = nullptr;
334}
335
336bool IrqHandlerRegistry::canWaitForActionFinalization() {
337 Thread* current = Processor::information().getCurrentThread();
338 bool canWait = current && Processor::getInterrupts() && !Processor::inDeviceHardIrq();
339#if HOSTED
340 canWait = canWait && !current->getHostedSignalDepth();
341#endif
342 return canWait;
343}
344
345bool IrqHandlerRegistry::acquireFinalizationGate(HandlerSlot& slot, bool canWait) {
346 bool contentionReported = false;
347 while (true) {
348 size_t expected = 0;
349 if (__atomic_compare_exchange_n(&slot.finalizationGate, &expected, static_cast<size_t>(1),
350 false, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED)) {
351 return true;
352 }
353#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
354 if (!contentionReported) {
355 HandlerHazardHook hook = __atomic_load_n(&m_HandlerHazardHook, __ATOMIC_ACQUIRE);
356 if (hook) {
357 hook(__atomic_load_n(&slot.handler, __ATOMIC_ACQUIRE),
358 HandlerHazardStage::FinalizationContended);
359 }
360 contentionReported = true;
361 }
362#endif
363 if (!canWait) {
364 return false;
365 }
367 }
368}
369
370void IrqHandlerRegistry::releaseFinalizationGate(HandlerSlot& slot) {
371 __atomic_store_n(&slot.finalizationGate, static_cast<size_t>(0), __ATOMIC_RELEASE);
372}
373
374bool IrqHandlerRegistry::pinActionMutation(HandlerSlot& slot, size_t publication,
375 DispatchCleanup& cleanup, Thread* thread) {
376 if (thread) {
377 thread->armAtomicStateCleanup(cleanup.cleanup, abandonDispatch, &cleanup);
378 }
379 if (!publishDispatch(slot, cleanup.owner, &cleanup, publication, 0, false)) {
380 if (thread) {
381 thread->disarmAtomicStateCleanup(cleanup.cleanup);
382 }
383 FATAL_NOLOCK("IRQ action-mutation hazard table exhausted.");
384 return false;
385 }
386 if (__atomic_load_n(&slot.publication, __ATOMIC_SEQ_CST) != publication) {
387 unpinActionMutation(slot, publication, cleanup, thread);
388 return false;
389 }
390 return true;
391}
392
393void IrqHandlerRegistry::unpinActionMutation(HandlerSlot& slot, size_t publication,
394 DispatchCleanup& cleanup, Thread* thread) {
395 unpublishDispatch(&cleanup, slot, publication, true);
396 if (thread) {
397 thread->disarmAtomicStateCleanup(cleanup.cleanup);
398 }
399}
400
401bool IrqHandlerRegistry::closeSlotAdmission(HandlerSlot& slot, size_t expectedPublication,
402 size_t& closedPublication) {
403 const size_t originalPublication = expectedPublication;
404 const uint8_t irq = irqOf(expectedPublication);
405 const size_t graceBucket = irq % GraceBucketCount;
406 const bool canWait = canWaitForActionFinalization();
407 if (!acquireFinalizationGate(slot, canWait)) {
408 if (modeOf(originalPublication) == SlotMode::Draining) {
409 size_t draining = originalPublication;
410 beginMutation();
411 __atomic_compare_exchange_n(
412 &slot.publication, &draining,
413 makePublication(generationOf(originalPublication), irq, SlotMode::Enabled,
414 deliveryOf(originalPublication)),
415 false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST);
416 finishMutation();
417 }
418 return false;
419 }
420
421 size_t boundaryExpected = 0;
422 while (!__atomic_compare_exchange_n(&m_OccurrenceBoundaryLocks[graceBucket], &boundaryExpected,
423 static_cast<size_t>(1), false, __ATOMIC_ACQUIRE,
424 __ATOMIC_RELAXED)) {
425 if (!canWait) {
426 releaseFinalizationGate(slot);
427 if (modeOf(originalPublication) == SlotMode::Draining) {
428 size_t draining = originalPublication;
429 beginMutation();
430 __atomic_compare_exchange_n(
431 &slot.publication, &draining,
432 makePublication(generationOf(originalPublication), irq, SlotMode::Enabled,
433 deliveryOf(originalPublication)),
434 false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST);
435 finishMutation();
436 }
437 return false;
438 }
439 boundaryExpected = 0;
441 }
442
443 const size_t occurrenceEpoch =
444 __atomic_load_n(&m_OccurrenceEpochs[graceBucket], __ATOMIC_SEQ_CST);
445 size_t boundary = occurrenceEpoch + 1;
446 if (!boundary) {
447 boundary = 1;
448 }
449 __atomic_store_n(&slot.retirementEpoch, boundary, __ATOMIC_SEQ_CST);
450 const size_t cancellingPublication =
451 makePublication(generationOf(expectedPublication), irq, SlotMode::Cancelling,
452 deliveryOf(expectedPublication));
453 beginMutation();
454 if (!__atomic_compare_exchange_n(&slot.publication, &expectedPublication, cancellingPublication,
455 false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)) {
456 __atomic_store_n(&slot.retirementEpoch, static_cast<size_t>(0), __ATOMIC_SEQ_CST);
457 finishMutation();
458 __atomic_store_n(&m_OccurrenceBoundaryLocks[graceBucket], static_cast<size_t>(0),
459 __ATOMIC_RELEASE);
460 releaseFinalizationGate(slot);
461 return false;
462 }
463
464 // The epoch store is the admission-closure linearization point. The slot
465 // already carries its boundary, so a preempting cutoff on either side can
466 // decide membership without waiting for this remover.
467 __atomic_store_n(&m_OccurrenceEpochs[graceBucket], boundary, __ATOMIC_SEQ_CST);
468
469#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
470 HandlerHazardHook boundaryHook = __atomic_load_n(&m_HandlerHazardHook, __ATOMIC_ACQUIRE);
471 if (boundaryHook) {
472 boundaryHook(__atomic_load_n(&slot.handler, __ATOMIC_ACQUIRE),
473 HandlerHazardStage::RetirementBoundaryPublished);
474 }
475#endif
476
477 closedPublication = makePublication(generationOf(cancellingPublication), irq, SlotMode::Closed,
478 deliveryOf(cancellingPublication));
479 size_t expectedCancelling = cancellingPublication;
480 const bool closed =
481 __atomic_compare_exchange_n(&slot.publication, &expectedCancelling, closedPublication, false,
482 __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST);
483 finishMutation();
484 __atomic_store_n(&m_OccurrenceBoundaryLocks[graceBucket], static_cast<size_t>(0),
485 __ATOMIC_RELEASE);
486 releaseFinalizationGate(slot);
487 if (!closed) {
488 FATAL_NOLOCK("IRQ handler closure state changed before publication.");
489 }
490 return closed;
491}
492
493bool IrqHandlerRegistry::occurrencePrecedesRetirement(const HandlerSlot& slot,
494 AdmissionCutoff admissionCutoff) const {
495 const size_t retirementEpoch = __atomic_load_n(&slot.retirementEpoch, __ATOMIC_SEQ_CST);
496 return retirementEpoch && !generationReached(admissionCutoff.occurrenceEpoch, retirementEpoch);
497}
498
499void IrqHandlerRegistry::tryReclaimTombstones(uint8_t irq) {
500 const size_t graceBucket = irq % GraceBucketCount;
501 if (__atomic_load_n(&m_OccurrenceReaders[graceBucket][0], __ATOMIC_SEQ_CST) ||
502 __atomic_load_n(&m_OccurrenceReaders[graceBucket][1], __ATOMIC_SEQ_CST)) {
503 return;
504 }
505
506 for (size_t i = 0; i < MaxHandlerSlots; ++i) {
507 HandlerSlot& slot = m_Handlers[i];
508 size_t publication = __atomic_load_n(&slot.publication, __ATOMIC_SEQ_CST);
509 const uint8_t slotIrq = irqOf(publication);
510 if (modeOf(publication) != SlotMode::Tombstone || slotIrq % GraceBucketCount != graceBucket ||
511 __atomic_load_n(&slot.pendingThreadedGeneration, __ATOMIC_ACQUIRE) ||
512 __atomic_load_n(&slot.claimedThreadedGeneration, __ATOMIC_ACQUIRE) ||
513 hasQuiescedGeneration(slot)) {
514 continue;
515 }
516
517 const size_t reclaimingPublication = makePublication(
518 generationOf(publication), slotIrq, SlotMode::Retiring, deliveryOf(publication));
519 if (!__atomic_compare_exchange_n(&slot.publication, &publication, reclaimingPublication, false,
520 __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)) {
521 continue;
522 }
523 if (hasActiveDispatch(slot, publication)) {
524 size_t reclaiming = reclaimingPublication;
525 if (!__atomic_compare_exchange_n(&slot.publication, &reclaiming, publication, false,
526 __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)) {
527 FATAL_NOLOCK("IRQ tombstone changed while action mutation was pinned.");
528 }
529 continue;
530 }
531
532 // No pre-retirement reader remains. Readers which announce after the
533 // zero sample validate a post-retirement epoch and never need this
534 // history, so metadata can be cleared before Empty becomes reusable.
535 __atomic_store_n(&slot.admissionEpoch, static_cast<size_t>(0), __ATOMIC_RELEASE);
536 __atomic_store_n(&slot.retirementEpoch, static_cast<size_t>(0), __ATOMIC_RELEASE);
537 __atomic_store_n(&slot.publication,
538 makePublication(generationOf(reclaimingPublication), InvalidIrq,
539 SlotMode::Empty, Delivery::Threaded),
540 __ATOMIC_SEQ_CST);
541 }
542}
543
544bool IrqHandlerRegistry::retireSlot(HandlerSlot& slot, size_t expectedPublication,
545 IrqHandlerBase* expectedHandler) {
546 beginMutation();
547 const size_t retiringPublication =
548 makePublication(generationOf(expectedPublication), irqOf(expectedPublication),
549 SlotMode::Retiring, deliveryOf(expectedPublication));
550 if (!__atomic_compare_exchange_n(&slot.publication, &expectedPublication, retiringPublication,
551 false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)) {
552 finishMutation();
553 return false;
554 }
555
556 if (hasActiveDispatch(slot, expectedPublication)) {
557 size_t retiring = retiringPublication;
558 if (!__atomic_compare_exchange_n(&slot.publication, &retiring, expectedPublication, false,
559 __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)) {
560 FATAL_NOLOCK("IRQ slot changed while action mutation was pinned.");
561 }
562 finishMutation();
563 return false;
564 }
565
566 assert(__atomic_load_n(&slot.handler, __ATOMIC_ACQUIRE) == expectedHandler);
567 const uint8_t irq = irqOf(retiringPublication);
568 if (deliveryOf(retiringPublication) == Delivery::Threaded) {
569 size_t* actions[] = {&slot.pendingThreadedGeneration, &slot.claimedThreadedGeneration};
570 for (size_t action = 0; action < 2; ++action) {
571 size_t generation = __atomic_load_n(actions[action], __ATOMIC_ACQUIRE);
572 while (generation) {
573 // Quiesced is visible before the actionable lane disappears.
574 // The worker therefore observes one side of this transition.
575 publishSlotQuiesced(slot, irq, generation, QuiescedLane::Retirement);
576 if (__atomic_compare_exchange_n(actions[action], &generation, static_cast<size_t>(0), false,
577 __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE)) {
578 break;
579 }
580 }
581 }
582 }
583 const size_t hardHandoffState =
584 __atomic_exchange_n(&slot.hardHandoffState, static_cast<size_t>(0), __ATOMIC_ACQ_REL);
585 if (hardHandoffState & 1U) {
586 __atomic_add_fetch(&m_HardHandoffEpochs[irq % GraceBucketCount], static_cast<size_t>(1),
587 __ATOMIC_ACQ_REL);
588 }
589 __atomic_store_n(&slot.handler, nullptr, __ATOMIC_RELEASE);
590 __atomic_store_n(&slot.policy, static_cast<size_t>(0), __ATOMIC_RELEASE);
591 __atomic_store_n(&slot.publication,
592 makePublication(generationOf(retiringPublication), irq, SlotMode::Tombstone,
593 deliveryOf(retiringPublication)),
594 __ATOMIC_SEQ_CST);
595 finishMutation();
596 tryReclaimTombstones(irq);
597 return true;
598}
599
600bool IrqHandlerRegistry::retireSlotOrObserveClosed(HandlerSlot& slot, size_t expectedPublication,
601 IrqHandlerBase* expectedHandler) {
602 if (retireSlot(slot, expectedPublication, expectedHandler)) {
603 return true;
604 }
605
606 const size_t publication = __atomic_load_n(&slot.publication, __ATOMIC_SEQ_CST);
607 return modeOf(publication) == SlotMode::Empty || modeOf(publication) == SlotMode::Closed ||
608 modeOf(publication) == SlotMode::Retiring || modeOf(publication) == SlotMode::Tombstone ||
609 generationOf(publication) != generationOf(expectedPublication) ||
610 __atomic_load_n(&slot.handler, __ATOMIC_ACQUIRE) != expectedHandler;
611}
612
613IrqHandlerRegistry::ActiveDispatch* IrqHandlerRegistry::publishDispatch(HandlerSlot& slot,
614 void* owner, void* token,
615 size_t admittedPublication,
616 size_t controllerGeneration,
617 bool callback) {
618 assert(owner);
619 assert(token);
620 for (size_t i = 0; i < MaxActiveDispatches; ++i) {
621 ActiveDispatch& dispatch = m_ActiveDispatches[i];
622 void* expectedToken = nullptr;
623 if (__atomic_compare_exchange_n(&dispatch.token, &expectedToken, token, false, __ATOMIC_SEQ_CST,
624 __ATOMIC_SEQ_CST)) {
625 __atomic_add_fetch(&dispatch.generation, static_cast<size_t>(1), __ATOMIC_ACQ_REL);
626 __atomic_store_n(&dispatch.owner, owner, __ATOMIC_RELAXED);
627 __atomic_store_n(&dispatch.admittedPublication, admittedPublication, __ATOMIC_RELAXED);
628 __atomic_store_n(&dispatch.controllerGeneration, controllerGeneration, __ATOMIC_RELAXED);
629 __atomic_store_n(&dispatch.callback,
630 callback ? static_cast<size_t>(1) : static_cast<size_t>(0),
631 __ATOMIC_RELAXED);
632
633#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
634 HandlerHazardHook hazardHook = nullptr;
635 if (callback) {
636 hazardHook = __atomic_load_n(&m_HandlerHazardHook, __ATOMIC_ACQUIRE);
637 if (hazardHook) {
638 hazardHook(__atomic_load_n(&slot.handler, __ATOMIC_ACQUIRE), HandlerHazardStage::Claimed);
639 }
640 }
641#endif
642
643 // Publishing the slot commits the callback pin. A removal which
644 // closes admission before this store can retire the handler; the
645 // dispatch revalidation below will then reject this callback.
646 __atomic_store_n(&dispatch.slot, &slot, __ATOMIC_SEQ_CST);
647
648#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
649 if (callback) {
650 hazardHook = __atomic_load_n(&m_HandlerHazardHook, __ATOMIC_ACQUIRE);
651 if (hazardHook) {
652 hazardHook(__atomic_load_n(&slot.handler, __ATOMIC_ACQUIRE),
653 HandlerHazardStage::Committed);
654 }
655 }
656#endif
657 return &dispatch;
658 }
659 }
660
661 return nullptr;
662}
663
664bool IrqHandlerRegistry::unpublishDispatch(void* token, HandlerSlot& slot,
665 size_t admittedPublication, bool required) {
666 assert(token);
667 bool found = false;
668 bool committed = false;
669 bool callback = false;
670 size_t controllerGeneration = 0;
671 for (size_t i = 0; i < MaxActiveDispatches; ++i) {
672 ActiveDispatch& dispatch = m_ActiveDispatches[i];
673 if (__atomic_load_n(&dispatch.token, __ATOMIC_ACQUIRE) != token) {
674 continue;
675 }
676
677 const size_t generation = __atomic_load_n(&dispatch.generation, __ATOMIC_ACQUIRE);
678 HandlerSlot* publishedSlot = __atomic_load_n(&dispatch.slot, __ATOMIC_SEQ_CST);
679 if (__atomic_load_n(&dispatch.token, __ATOMIC_ACQUIRE) != token ||
680 __atomic_load_n(&dispatch.generation, __ATOMIC_ACQUIRE) != generation) {
681 continue;
682 }
683
684 if (publishedSlot && publishedSlot != &slot) {
685 FATAL_NOLOCK("IRQ callback hazard changed slots during release.");
686 return false;
687 }
688
689 committed = publishedSlot == &slot;
690 callback = __atomic_load_n(&dispatch.callback, __ATOMIC_RELAXED) != 0;
691 controllerGeneration = __atomic_load_n(&dispatch.controllerGeneration, __ATOMIC_RELAXED);
692 __atomic_store_n(&dispatch.slot, nullptr, __ATOMIC_SEQ_CST);
693 __atomic_store_n(&dispatch.admittedPublication, static_cast<size_t>(0), __ATOMIC_RELAXED);
694 __atomic_store_n(&dispatch.controllerGeneration, static_cast<size_t>(0), __ATOMIC_RELAXED);
695 __atomic_store_n(&dispatch.callback, static_cast<size_t>(0), __ATOMIC_RELAXED);
696 __atomic_store_n(&dispatch.owner, nullptr, __ATOMIC_RELAXED);
697 __atomic_store_n(&dispatch.token, nullptr, __ATOMIC_RELEASE);
698 found = true;
699 break;
700 }
701
702 if (!found) {
703 if (required) {
704 FATAL_NOLOCK("IRQ callback hazard was released more than once.");
705 }
706 return false;
707 }
708
709#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
710 IrqHandlerBase* releasedHandler = nullptr;
711 HandlerHazardHook releaseHook = nullptr;
712 if (committed && callback) {
713 releasedHandler = __atomic_load_n(&slot.handler, __ATOMIC_ACQUIRE);
714 releaseHook = __atomic_load_n(&m_HandlerHazardHook, __ATOMIC_ACQUIRE);
715 }
716#endif
717
718 if (committed && callback && !required && controllerGeneration &&
719 deliveryOf(admittedPublication) == Delivery::Threaded) {
720 publishSlotQuiesced(slot, irqOf(admittedPublication), controllerGeneration,
721 QuiescedLane::Callback);
722 size_t claimed = controllerGeneration;
723 __atomic_compare_exchange_n(&slot.claimedThreadedGeneration, &claimed, static_cast<size_t>(0),
724 false, __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE);
725 }
726
727 if (committed && !hasActiveDispatch(slot, admittedPublication)) {
728 const size_t publication = __atomic_load_n(&slot.publication, __ATOMIC_SEQ_CST);
729 if (generationOf(publication) == generationOf(admittedPublication) &&
730 modeOf(publication) == SlotMode::Closed) {
731 IrqHandlerBase* handler = __atomic_load_n(&slot.handler, __ATOMIC_ACQUIRE);
732 if (handler) {
733 retireSlot(slot, publication, handler);
734 }
735 }
736 }
737
738#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
739 if (releaseHook) {
740 releaseHook(releasedHandler, HandlerHazardStage::Released);
741 }
742#endif
743 return true;
744}
745
746void IrqHandlerRegistry::abandonDispatch(void* context) {
747 DispatchCleanup* dispatch = reinterpret_cast<DispatchCleanup*>(context);
748 const bool callbackBoundaryEntered = dispatch->restoreInterruptState;
749 if (dispatch->restoreDeviceHardIrqDepth) {
750 DeviceHardIrqContext::restoreDepth(dispatch->previousDeviceHardIrqDepth);
751 dispatch->restoreDeviceHardIrqDepth = false;
752 }
753 dispatch->registry->unpublishDispatch(dispatch, *dispatch->slot, dispatch->publication, false);
754 restoreDispatchInterruptState(*dispatch);
755
756#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
757 if (dispatch->callback) {
758 DispatchAbandonHook hook =
759 __atomic_load_n(&dispatch->registry->m_DispatchAbandonHook, __ATOMIC_ACQUIRE);
760 if (hook) {
761 hook(dispatch->owner, callbackBoundaryEntered);
762 }
763 }
764#endif
765}
766
767void IrqHandlerRegistry::restoreDispatchInterruptState(DispatchCleanup& dispatch) {
768 if (!dispatch.restoreInterruptState) {
769 return;
770 }
771
772 const bool previousInterruptState = dispatch.previousInterruptState;
773 dispatch.restoreInterruptState = false;
774 Processor::setInterrupts(previousInterruptState);
775}
776
777bool IrqHandlerRegistry::hasActiveDispatch(HandlerSlot& target, size_t admittedPublication) const {
778 for (size_t i = 0; i < MaxActiveDispatches; ++i) {
779 const ActiveDispatch& dispatch = m_ActiveDispatches[i];
780 void* token = __atomic_load_n(&dispatch.token, __ATOMIC_ACQUIRE);
781 if (!token) {
782 continue;
783 }
784
785 const size_t generation = __atomic_load_n(&dispatch.generation, __ATOMIC_ACQUIRE);
786 HandlerSlot* slot = __atomic_load_n(&dispatch.slot, __ATOMIC_SEQ_CST);
787 const size_t dispatchPublication =
788 __atomic_load_n(&dispatch.admittedPublication, __ATOMIC_RELAXED);
789 if (slot == &target && generationOf(dispatchPublication) == generationOf(admittedPublication) &&
790 __atomic_load_n(&dispatch.token, __ATOMIC_ACQUIRE) == token &&
791 __atomic_load_n(&dispatch.generation, __ATOMIC_ACQUIRE) == generation) {
792 return true;
793 }
794 }
795 return false;
796}
797
798bool IrqHandlerRegistry::findCurrentDispatch(void* owner, HandlerSlot* target,
799 size_t targetPublication,
800 bool& callbackContext) const {
801 callbackContext = false;
802 bool foundTarget = false;
803 for (size_t i = 0; i < MaxActiveDispatches; ++i) {
804 const ActiveDispatch& dispatch = m_ActiveDispatches[i];
805 void* token = __atomic_load_n(&dispatch.token, __ATOMIC_ACQUIRE);
806 if (!token) {
807 continue;
808 }
809
810 const size_t generation = __atomic_load_n(&dispatch.generation, __ATOMIC_ACQUIRE);
811 void* dispatchOwner = __atomic_load_n(&dispatch.owner, __ATOMIC_RELAXED);
812 HandlerSlot* slot = __atomic_load_n(&dispatch.slot, __ATOMIC_SEQ_CST);
813 const size_t dispatchPublication =
814 __atomic_load_n(&dispatch.admittedPublication, __ATOMIC_RELAXED);
815 if (__atomic_load_n(&dispatch.token, __ATOMIC_ACQUIRE) != token ||
816 __atomic_load_n(&dispatch.generation, __ATOMIC_ACQUIRE) != generation) {
817 continue;
818 }
819
820 if (dispatchOwner == owner && slot && __atomic_load_n(&dispatch.callback, __ATOMIC_RELAXED)) {
821 callbackContext = true;
822 foundTarget |= slot == target && target &&
823 generationOf(dispatchPublication) == generationOf(targetPublication);
824 }
825 }
826 return foundTarget;
827}
828
829void* IrqHandlerRegistry::currentDispatchOwner() {
830 ProcessorInformation& information = Processor::information();
831 Thread* thread = information.getCurrentThread();
832 return thread ? static_cast<void*>(thread) : static_cast<void*>(&information);
833}
834
835bool IrqHandlerRegistry::registerHandler(uint8_t irq, IrqHandlerBase* handler, Delivery delivery,
836 size_t policy) {
837 if (!handler || (delivery != Delivery::Threaded && delivery != Delivery::HardOnly)) {
838 return false;
839 }
840
841 LockGuard<Spinlock> guard(m_HandlerLock);
842 for (size_t i = 0; i < MaxHandlerSlots; ++i) {
843 HandlerSlot& slot = m_Handlers[i];
844 const size_t publication = __atomic_load_n(&slot.publication, __ATOMIC_SEQ_CST);
845 const SlotMode mode = modeOf(publication);
846 if (mode == SlotMode::Empty || mode == SlotMode::Cancelling || mode == SlotMode::Closed ||
847 mode == SlotMode::Retiring || mode == SlotMode::Tombstone || irqOf(publication) != irq) {
848 continue;
849 }
850 const Delivery existingDelivery = deliveryOf(publication);
851 const size_t existingPolicy = __atomic_load_n(&slot.policy, __ATOMIC_ACQUIRE);
852 if ((existingDelivery == delivery && existingPolicy != policy) ||
853 (existingDelivery != delivery && !mixedPoliciesCompatible(existingPolicy, policy))) {
854 return false;
855 }
856
857 if (__atomic_load_n(&slot.handler, __ATOMIC_ACQUIRE) == handler) {
858 return false;
859 }
860 }
861
862 for (size_t i = 0; i < MaxHandlerSlots; ++i) {
863 HandlerSlot& slot = m_Handlers[i];
864 const size_t publication = __atomic_load_n(&slot.publication, __ATOMIC_SEQ_CST);
865 if (modeOf(publication) == SlotMode::Empty &&
866 !__atomic_load_n(&slot.handler, __ATOMIC_ACQUIRE) &&
867 !hasActiveDispatch(slot, publication)) {
868 const size_t generation = generationOf(publication) + 1;
869 if (!generation || generation > MaximumPublicationGeneration) {
870 continue;
871 }
872 beginMutation();
873 size_t admissionEpoch = __atomic_load_n(&m_AdmissionEpoch, __ATOMIC_RELAXED) + 1;
874 if (!admissionEpoch) {
875 ++admissionEpoch;
876 }
877 __atomic_store_n(&slot.pendingThreadedGeneration, static_cast<size_t>(0), __ATOMIC_RELEASE);
878 __atomic_store_n(&slot.claimedThreadedGeneration, static_cast<size_t>(0), __ATOMIC_RELEASE);
879 for (size_t lane = 0; lane < QuiescedLaneCount; ++lane) {
880 __atomic_store_n(&slot.quiescedThreadedGenerations[lane], static_cast<size_t>(0),
881 __ATOMIC_RELEASE);
882 }
883 __atomic_store_n(&slot.retirementEpoch, static_cast<size_t>(0), __ATOMIC_RELEASE);
884 __atomic_store_n(&slot.hardHandoffState, delivery == Delivery::HardOnly ? generation << 1 : 0,
885 __ATOMIC_RELEASE);
886 __atomic_store_n(&slot.handler, handler, __ATOMIC_RELEASE);
887 // Policy remains immutable until Retiring closes this publication.
888 __atomic_store_n(&slot.policy, policy, __ATOMIC_RELEASE);
889 __atomic_store_n(&slot.admissionEpoch, admissionEpoch, __ATOMIC_RELEASE);
890 __atomic_store_n(&slot.publication,
891 makePublication(generation, irq, SlotMode::Enabled, delivery),
892 __ATOMIC_SEQ_CST);
893 // This is the registration linearization point used by interrupt
894 // dispatch cutoffs. The preceding slot publication is visible to
895 // a cutoff which observes this epoch.
896 __atomic_store_n(&m_AdmissionEpoch, admissionEpoch, __ATOMIC_RELEASE);
897 finishMutation();
898 return true;
899 }
900 }
901
902 return false;
903}
904
906 return registerHandler(irq, handler, Delivery::Threaded, 0);
907}
908
910 const IrqPolicy& policy) {
911 return policy.validForThreaded() &&
912 registerHandler(irq, handler, Delivery::Threaded, encodePolicy(&policy));
913}
914
916 return registerHandler(irq, handler, Delivery::HardOnly, 0);
917}
918
920 const IrqPolicy& policy) {
921 return policy.validForHard() &&
922 registerHandler(irq, handler, Delivery::HardOnly, encodePolicy(&policy));
923}
924
925IrqHandlerRegistry::UnregisterResult IrqHandlerRegistry::unregisterHandler(
926 uint8_t irq, IrqHandlerBase* handler) {
927 LineMode ignoredDelivery = LineMode::Empty;
928 return unregisterHandler(irq, handler, ignoredDelivery);
929}
930
931IrqHandlerRegistry::UnregisterResult IrqHandlerRegistry::unregisterHandler(
932 uint8_t irq, IrqHandlerBase* handler, LineMode& removedDelivery) {
933 removedDelivery = LineMode::Empty;
934 if (!handler) {
935 return UnregisterResult::NotFound;
936 }
937
938 void* owner = currentDispatchOwner();
939 Thread* current = Processor::information().getCurrentThread();
940 bool canYield = current && Processor::getInterrupts();
941#if HOSTED
942 canYield = canYield && !current->getHostedSignalDepth();
943#endif
944
945 bool callbackContext = false;
946 findCurrentDispatch(owner, nullptr, 0, callbackContext);
947
948 if (!canYield || callbackContext) {
949 // Callback and early atomic contexts cannot wait on a writer. A
950 // callback can close its own admission and let its final pin retire
951 // the slot; a non-callback removal completes only when no callback is
952 // already committed.
953 for (size_t i = 0; i < MaxHandlerSlots; ++i) {
954 HandlerSlot& candidate = m_Handlers[i];
955 size_t publication = __atomic_load_n(&candidate.publication, __ATOMIC_SEQ_CST);
956 if (modeOf(publication) != SlotMode::Enabled || irqOf(publication) != irq ||
957 __atomic_load_n(&candidate.handler, __ATOMIC_ACQUIRE) != handler) {
958 continue;
959 }
960
961 removedDelivery = lineModeForDelivery(deliveryOf(publication));
962 bool currentTargetDispatch = false;
963 if (findCurrentDispatch(owner, &candidate, publication, currentTargetDispatch)) {
964 size_t closedPublication = 0;
965 const bool deferred = closeSlotAdmission(candidate, publication, closedPublication);
966 if (deferred) {
967 return UnregisterResult::Deferred;
968 }
969 return UnregisterResult::Rejected;
970 }
971
972 const size_t drainingPublication = makePublication(
973 generationOf(publication), irq, SlotMode::Draining, deliveryOf(publication));
974 beginMutation();
975 const bool draining =
976 __atomic_compare_exchange_n(&candidate.publication, &publication, drainingPublication,
977 false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST);
978 finishMutation();
979 if (!draining) {
980 return UnregisterResult::Rejected;
981 }
982
983 if (hasActiveDispatch(candidate, drainingPublication)) {
984 size_t expectedPublication = drainingPublication;
985 beginMutation();
986 __atomic_compare_exchange_n(
987 &candidate.publication, &expectedPublication,
988 makePublication(generationOf(drainingPublication), irq, SlotMode::Enabled,
989 deliveryOf(drainingPublication)),
990 false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST);
991 finishMutation();
992 return UnregisterResult::Rejected;
993 }
994
995 size_t closedPublication = 0;
996 if (!closeSlotAdmission(candidate, drainingPublication, closedPublication)) {
997 return UnregisterResult::Rejected;
998 }
999
1000 return retireSlotOrObserveClosed(candidate, closedPublication, handler)
1001 ? UnregisterResult::Completed
1002 : UnregisterResult::Rejected;
1003 }
1004
1005 return UnregisterResult::NotFound;
1006 }
1007
1008 // Only an ordinary thread can synchronously drain. Keep its stack alive
1009 // after the atomic callback path has returned without touching Thread
1010 // deferred-scope state.
1011 TerminationDeferral terminationDeferral;
1012 m_HandlerLock.acquire();
1013
1014 HandlerSlot* slot = nullptr;
1015 size_t publication = 0;
1016 for (size_t i = 0; i < MaxHandlerSlots; ++i) {
1017 size_t candidatePublication = __atomic_load_n(&m_Handlers[i].publication, __ATOMIC_SEQ_CST);
1018 if (modeOf(candidatePublication) != SlotMode::Empty && irqOf(candidatePublication) == irq &&
1019 __atomic_load_n(&m_Handlers[i].handler, __ATOMIC_ACQUIRE) == handler) {
1020 slot = &m_Handlers[i];
1021 publication = candidatePublication;
1022 break;
1023 }
1024 }
1025
1026 if (!slot) {
1027 m_HandlerLock.release();
1028 return UnregisterResult::NotFound;
1029 }
1030
1031 removedDelivery = lineModeForDelivery(deliveryOf(publication));
1032 const bool selfUnregister = findCurrentDispatch(owner, slot, publication, callbackContext);
1033 if (selfUnregister) {
1034 if (modeOf(publication) != SlotMode::Enabled) {
1035 m_HandlerLock.release();
1036 return UnregisterResult::Rejected;
1037 }
1038
1039 size_t closedPublication = 0;
1040 const bool deferred = closeSlotAdmission(*slot, publication, closedPublication);
1041 m_HandlerLock.release();
1042 return deferred ? UnregisterResult::Deferred : UnregisterResult::Rejected;
1043 }
1044
1045 if (modeOf(publication) != SlotMode::Enabled) {
1046 m_HandlerLock.release();
1047 return UnregisterResult::Rejected;
1048 }
1049
1050 // Closing may need to wait for a callback's short finalization gate.
1051 // Spinlock ownership disables interrupts, so retain the exact publication
1052 // as our mutation token and perform that wait after releasing the writer
1053 // lock. Concurrent removers or slot reuse are rejected by the closure CAS.
1054 m_HandlerLock.release();
1055 size_t closedPublication = 0;
1056 if (!closeSlotAdmission(*slot, publication, closedPublication)) {
1057 return UnregisterResult::Rejected;
1058 }
1059
1060 if (!hasActiveDispatch(*slot, closedPublication)) {
1061 m_HandlerLock.acquire();
1062 const bool retired = retireSlotOrObserveClosed(*slot, closedPublication, handler);
1063 m_HandlerLock.release();
1064 return retired ? UnregisterResult::Completed : UnregisterResult::Rejected;
1065 }
1066
1067 uintptr_t previousDebugAddress = 0;
1068 const Thread::DebugState previousDebugState = current->getDebugState(previousDebugAddress);
1069 current->setDebugState(Thread::CallbackDrain, reinterpret_cast<uintptr_t>(handler));
1070 while (hasActiveDispatch(*slot, closedPublication)) {
1071 // Callback release can run in hard IRQ context. It only clears its
1072 // atomic hazard; this ordinary teardown thread owns all scheduling.
1074 }
1075 current->setDebugState(previousDebugState, previousDebugAddress);
1076
1077 m_HandlerLock.acquire();
1078 const bool retired = retireSlotOrObserveClosed(*slot, closedPublication, handler);
1079 m_HandlerLock.release();
1080 return retired ? UnregisterResult::Completed : UnregisterResult::Rejected;
1081}
1082
1083bool IrqHandlerRegistry::dispatchHard(uint8_t irq, InterruptState& state,
1084 HardIrqDisposition& disposition, HardIrqHandler* onlyHandler,
1085 size_t dispatchGeneration) {
1086 AdmissionCutoff admissionCutoff = {};
1087 if (!captureAdmissionCutoff(irq, admissionCutoff)) {
1088 disposition = HardIrqDisposition::NotHandled;
1089 return false;
1090 }
1091 return dispatchHard(irq, state, disposition, onlyHandler, dispatchGeneration, admissionCutoff);
1092}
1093
1095 cutoff = {};
1096 const size_t graceBucket = irq % GraceBucketCount;
1097 if (!acquireOccurrenceReaderLeases(irq, 0, 1)) {
1098 return false;
1099 }
1100#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
1101 observeOccurrenceCaptureForTest(irq, OccurrenceCaptureStage::BankZeroClaimed, 0);
1102#endif
1103 if (!acquireOccurrenceReaderLeases(irq, 1, 1)) {
1104 releaseOccurrenceReaderLeases(irq, 0, 1);
1105 return false;
1106 }
1107#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
1108 observeOccurrenceCaptureForTest(irq, OccurrenceCaptureStage::BankOneClaimed, 0);
1109#endif
1110
1111 // Claiming both banks before sampling makes the cutoff wait-free. If a
1112 // retirement publishes first, this load observes its new epoch. If this
1113 // load observes the old epoch, one of the already-visible leases keeps
1114 // that retired publication's tombstone alive. The unused bank can then be
1115 // released without retrying around the boundary.
1116 const size_t occurrenceEpoch =
1117 __atomic_load_n(&m_OccurrenceEpochs[graceBucket], __ATOMIC_SEQ_CST);
1118#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
1119 observeOccurrenceCaptureForTest(irq, OccurrenceCaptureStage::EpochSampled, occurrenceEpoch);
1120#endif
1121 const size_t readerBank = occurrenceEpoch & 1;
1122 releaseOccurrenceReaderLeases(irq, readerBank ^ 1, 1);
1123#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
1124 observeOccurrenceCaptureForTest(irq, OccurrenceCaptureStage::UnusedBankReleased, occurrenceEpoch);
1125#endif
1126 const size_t admissionEpoch = __atomic_load_n(&m_AdmissionEpoch, __ATOMIC_ACQUIRE);
1127 cutoff = {admissionEpoch, occurrenceEpoch, (static_cast<size_t>(irq) * 2) + readerBank + 1};
1128 return true;
1129}
1130
1132 cutoffs = {};
1133 const size_t graceBucket = irq % GraceBucketCount;
1134 if (!acquireOccurrenceReaderLeases(irq, 0, 2)) {
1135 return false;
1136 }
1137#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
1138 observeOccurrenceCaptureForTest(irq, OccurrenceCaptureStage::BankZeroClaimed, 0);
1139#endif
1140 if (!acquireOccurrenceReaderLeases(irq, 1, 2)) {
1141 releaseOccurrenceReaderLeases(irq, 0, 2);
1142 return false;
1143 }
1144#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
1145 observeOccurrenceCaptureForTest(irq, OccurrenceCaptureStage::BankOneClaimed, 0);
1146#endif
1147
1148 const size_t occurrenceEpoch =
1149 __atomic_load_n(&m_OccurrenceEpochs[graceBucket], __ATOMIC_SEQ_CST);
1150#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
1151 observeOccurrenceCaptureForTest(irq, OccurrenceCaptureStage::EpochSampled, occurrenceEpoch);
1152#endif
1153 const size_t readerBank = occurrenceEpoch & 1;
1154 releaseOccurrenceReaderLeases(irq, readerBank ^ 1, 2);
1155#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
1156 observeOccurrenceCaptureForTest(irq, OccurrenceCaptureStage::UnusedBankReleased, occurrenceEpoch);
1157#endif
1158 const size_t admissionEpoch = __atomic_load_n(&m_AdmissionEpoch, __ATOMIC_ACQUIRE);
1159 const AdmissionCutoff cutoff = {admissionEpoch, occurrenceEpoch,
1160 (static_cast<size_t>(irq) * 2) + readerBank + 1};
1161 cutoffs = {cutoff, cutoff};
1162 return true;
1163}
1164
1165bool IrqHandlerRegistry::acquireOccurrenceReaderLeases(uint8_t irq, size_t readerBank,
1166 size_t count) {
1167 if (readerBank > 1 || !count) {
1168 FATAL_NOLOCK("Invalid IRQ occurrence reader acquisition.");
1169 return false;
1170 }
1171
1172 const size_t graceBucket = irq % GraceBucketCount;
1173 const size_t previous =
1174 __atomic_fetch_add(&m_OccurrenceReaders[graceBucket][readerBank], count, __ATOMIC_SEQ_CST);
1175 if (previous > ~static_cast<size_t>(0) - count) {
1176 __atomic_fetch_sub(&m_OccurrenceReaders[graceBucket][readerBank], count, __ATOMIC_SEQ_CST);
1177 FATAL_NOLOCK("IRQ occurrence reader count overflowed.");
1178 return false;
1179 }
1180 return true;
1181}
1182
1183void IrqHandlerRegistry::releaseOccurrenceReaderLeases(uint8_t irq, size_t readerBank,
1184 size_t count) {
1185 if (readerBank > 1 || !count) {
1186 FATAL_NOLOCK("Invalid IRQ occurrence reader release.");
1187 return;
1188 }
1189
1190 const size_t graceBucket = irq % GraceBucketCount;
1191 const size_t previous =
1192 __atomic_fetch_sub(&m_OccurrenceReaders[graceBucket][readerBank], count, __ATOMIC_SEQ_CST);
1193 if (previous < count) {
1194 __atomic_fetch_add(&m_OccurrenceReaders[graceBucket][readerBank], count, __ATOMIC_SEQ_CST);
1195 FATAL_NOLOCK("IRQ occurrence reader count underflowed.");
1196 return;
1197 }
1198
1199 const size_t remaining = previous - count;
1200 if (!remaining) {
1201 tryReclaimTombstones(irq);
1202 }
1203}
1204
1206 if (!admissionCutoff.readerToken) {
1207 return;
1208 }
1209
1210 const size_t token = admissionCutoff.readerToken - 1;
1211 if (token >= IrqCount * 2) {
1212 FATAL_NOLOCK("Invalid IRQ occurrence reader token.");
1213 return;
1214 }
1215 const uint8_t irq = static_cast<uint8_t>(token / 2);
1216 const size_t readerBank = token & 1;
1217 releaseOccurrenceReaderLeases(irq, readerBank, 1);
1218}
1219
1220void IrqHandlerRegistry::beginAdmissionCutoffCleanup(AdmissionCutoffCleanup& cleanup) {
1221 const bool interruptsWereEnabled = Processor::getInterrupts();
1223 cleanup.thread = Processor::information().getCurrentThread();
1224 cleanup.ownsCutoff = cleanup.cutoff.readerToken != 0;
1225 if (cleanup.thread && cleanup.ownsCutoff) {
1226 // This remains below each per-slot hazard cleanup. Stack abandonment
1227 // therefore unpublishes hazards before the final lease can reclaim a
1228 // tombstone.
1229 cleanup.thread->armAtomicStateCleanup(cleanup.cleanup, abandonAdmissionCutoff, &cleanup);
1230 }
1231 Processor::setInterrupts(interruptsWereEnabled);
1232}
1233
1234void IrqHandlerRegistry::finishAdmissionCutoffCleanup(AdmissionCutoffCleanup& cleanup) {
1235 const bool interruptsWereEnabled = Processor::getInterrupts();
1237 if (cleanup.ownsCutoff) {
1238 // Transfer ownership before release so an unwind from the release
1239 // path cannot consume the same lease through this cleanup record.
1240 cleanup.ownsCutoff = false;
1241 releaseAdmissionCutoff(cleanup.cutoff);
1242 }
1243 if (cleanup.thread && cleanup.cleanup.armed) {
1244 cleanup.thread->disarmAtomicStateCleanup(cleanup.cleanup);
1245 }
1246 cleanup.registry = nullptr;
1247 Processor::setInterrupts(interruptsWereEnabled);
1248}
1249
1250void IrqHandlerRegistry::abandonAdmissionCutoff(void* context) {
1251 AdmissionCutoffCleanup* cleanup = reinterpret_cast<AdmissionCutoffCleanup*>(context);
1252 if (!cleanup || !cleanup->registry || !cleanup->ownsCutoff) {
1253 return;
1254 }
1255
1256 cleanup->ownsCutoff = false;
1257 cleanup->registry->releaseAdmissionCutoff(cleanup->cutoff);
1258 cleanup->registry = nullptr;
1259}
1260
1261bool IrqHandlerRegistry::dispatchHard(uint8_t irq, InterruptState& state,
1262 HardIrqDisposition& disposition, HardIrqHandler* onlyHandler,
1263 size_t dispatchGeneration, AdmissionCutoff admissionCutoff) {
1264 ActivityDiagnostics::HardDispatchScope activityScope(irq);
1265 AdmissionCutoffCleanup cutoffCleanup(this, admissionCutoff);
1266 beginAdmissionCutoffCleanup(cutoffCleanup);
1267 bool admitted = false;
1268 disposition = HardIrqDisposition::NotHandled;
1269 const size_t cutoffEpoch = admissionCutoff.epoch;
1270 const size_t expectedReaderToken =
1271 (static_cast<size_t>(irq) * 2) + (admissionCutoff.occurrenceEpoch & 1) + 1;
1272 if (admissionCutoff.readerToken != expectedReaderToken) {
1273 finishAdmissionCutoffCleanup(cutoffCleanup);
1274 return false;
1275 }
1276
1277 for (size_t i = 0; i < MaxHandlerSlots; ++i) {
1278 HandlerSlot& slot = m_Handlers[i];
1279 const size_t publication = __atomic_load_n(&slot.publication, __ATOMIC_SEQ_CST);
1280 const SlotMode mode = modeOf(publication);
1281 if (irqOf(publication) != irq || deliveryOf(publication) != Delivery::HardOnly) {
1282 continue;
1283 }
1284 const size_t admissionEpoch = __atomic_load_n(&slot.admissionEpoch, __ATOMIC_ACQUIRE);
1285 if (!admissionEpoch ||
1286 (admissionEpoch != cutoffEpoch && generationReached(admissionEpoch, cutoffEpoch))) {
1287 continue;
1288 }
1289
1290 if (mode == SlotMode::Cancelling || mode == SlotMode::Closed || mode == SlotMode::Retiring ||
1291 mode == SlotMode::Tombstone) {
1292 if (occurrencePrecedesRetirement(slot, admissionCutoff)) {
1293 admitted = true;
1294 if (disposition == HardIrqDisposition::NotHandled) {
1295 disposition = HardIrqDisposition::Handled;
1296 }
1297 }
1298 continue;
1299 }
1300 if (mode != SlotMode::Enabled && mode != SlotMode::Draining) {
1301 continue;
1302 }
1303
1304 IrqHandlerBase* handler = __atomic_load_n(&slot.handler, __ATOMIC_ACQUIRE);
1305 if (!handler || (onlyHandler && handler != static_cast<IrqHandlerBase*>(onlyHandler))) {
1306 continue;
1307 }
1308
1309#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
1310 HandlerPrePinHook prePinHook = __atomic_load_n(&m_HandlerPrePinHook, __ATOMIC_ACQUIRE);
1311 if (prePinHook) {
1312 prePinHook(handler);
1313 }
1314#endif
1315
1316 void* owner = currentDispatchOwner();
1317 Thread* thread = Processor::information().getCurrentThread();
1318 DispatchCleanup dispatchCleanup(this, &slot, owner, publication);
1319 if (thread) {
1320 // Publish cleanup before committing the active-dispatch hazard.
1321 // A nested exception can therefore abandon this stack at every
1322 // later instruction without leaking callback admission.
1323 thread->armAtomicStateCleanup(dispatchCleanup.cleanup, abandonDispatch, &dispatchCleanup);
1324 }
1325
1326#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
1327 HandlerHazardHook hazardHook = __atomic_load_n(&m_HandlerHazardHook, __ATOMIC_ACQUIRE);
1328 if (hazardHook) {
1329 hazardHook(handler, HandlerHazardStage::BeforeClaim);
1330 }
1331#endif
1332
1333 if (!publishDispatch(slot, owner, &dispatchCleanup, publication, dispatchGeneration)) {
1334 if (thread) {
1335 thread->disarmAtomicStateCleanup(dispatchCleanup.cleanup);
1336 }
1337 finishAdmissionCutoffCleanup(cutoffCleanup);
1338 FATAL_NOLOCK("IRQ callback hazard table exhausted.");
1339 return admitted;
1340 }
1341
1342 size_t currentPublication = __atomic_load_n(&slot.publication, __ATOMIC_SEQ_CST);
1343 SlotMode currentMode = modeOf(currentPublication);
1344 const bool sameLifetime =
1345 generationOf(currentPublication) == generationOf(publication) &&
1346 irqOf(currentPublication) == irq && deliveryOf(currentPublication) == Delivery::HardOnly &&
1347 __atomic_load_n(&slot.handler, __ATOMIC_ACQUIRE) == handler &&
1348 __atomic_load_n(&slot.admissionEpoch, __ATOMIC_ACQUIRE) == admissionEpoch;
1349 if (sameLifetime && currentMode == SlotMode::Draining) {
1350 // The marker is already visible to unregister. Restoring Enabled
1351 // makes callback admission and removal arbitrate with one CAS:
1352 // either this wins and removal rejects, or Cancelling wins and no
1353 // callback starts.
1354 size_t expectedPublication = currentPublication;
1355 const size_t enabledPublication = makePublication(generationOf(currentPublication), irq,
1356 SlotMode::Enabled, Delivery::HardOnly);
1357 beginMutation();
1358 __atomic_compare_exchange_n(&slot.publication, &expectedPublication, enabledPublication,
1359 false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST);
1360 finishMutation();
1361 currentPublication = __atomic_load_n(&slot.publication, __ATOMIC_SEQ_CST);
1362 currentMode = modeOf(currentPublication);
1363 }
1364
1365 if (generationOf(currentPublication) != generationOf(publication) ||
1366 irqOf(currentPublication) != irq || deliveryOf(currentPublication) != Delivery::HardOnly ||
1367 currentMode != SlotMode::Enabled ||
1368 __atomic_load_n(&slot.handler, __ATOMIC_ACQUIRE) != handler ||
1369 __atomic_load_n(&slot.admissionEpoch, __ATOMIC_ACQUIRE) != admissionEpoch) {
1370 unpublishDispatch(&dispatchCleanup, slot, publication, true);
1371 if (thread) {
1372 thread->disarmAtomicStateCleanup(dispatchCleanup.cleanup);
1373 }
1374 continue;
1375 }
1376
1377 admitted = true;
1378
1379#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
1380 HandlerPinHook hook = __atomic_load_n(&m_HandlerPinHook, __ATOMIC_ACQUIRE);
1381 if (hook) {
1382 hook(handler);
1383 }
1384#endif
1385
1386 {
1387 // Test dispatch can enter here from ordinary thread context. Keep
1388 // the lifetime hooks schedulable, but give the callback the same
1389 // interrupt boundary as a controller-delivered hard IRQ.
1390 dispatchCleanup.previousInterruptState = Processor::getInterrupts();
1391 dispatchCleanup.restoreInterruptState = true;
1393 DeviceHardIrqContext deviceHardIrqContext(dispatchCleanup.previousDeviceHardIrqDepth,
1394 dispatchCleanup.restoreDeviceHardIrqDepth);
1395 const HardIrqDisposition callbackDisposition =
1396 static_cast<HardIrqHandler*>(handler)->irq(irq, state);
1397 if (callbackDisposition == HardIrqDisposition::KeepMasked) {
1398 size_t expected = generationOf(publication) << 1;
1399 if (__atomic_compare_exchange_n(&slot.hardHandoffState, &expected, expected | 1U, false,
1400 __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE)) {
1401 __atomic_add_fetch(&m_HardHandoffEpochs[irq % GraceBucketCount], static_cast<size_t>(1),
1402 __ATOMIC_ACQ_REL);
1403 }
1404 }
1405 if (callbackDisposition == HardIrqDisposition::KeepMasked ||
1406 (callbackDisposition == HardIrqDisposition::Handled &&
1407 disposition == HardIrqDisposition::NotHandled)) {
1408 disposition = callbackDisposition;
1409 }
1410 }
1411 unpublishDispatch(&dispatchCleanup, slot, publication, true);
1412 if (thread) {
1413 thread->disarmAtomicStateCleanup(dispatchCleanup.cleanup);
1414 }
1415 restoreDispatchInterruptState(dispatchCleanup);
1416 }
1417
1418 finishAdmissionCutoffCleanup(cutoffCleanup);
1419 if (disposition == HardIrqDisposition::KeepMasked && !hardLineQuarantined(irq)) {
1420 disposition = HardIrqDisposition::Handled;
1421 }
1422 return admitted;
1423}
1424
1426 const size_t epochBucket = irq % GraceBucketCount;
1427 for (size_t attempt = 0; attempt < 2; ++attempt) {
1428 const size_t startEpoch = __atomic_load_n(&m_HardHandoffEpochs[epochBucket], __ATOMIC_ACQUIRE);
1429 for (size_t i = 0; i < MaxHandlerSlots; ++i) {
1430 const HandlerSlot& slot = m_Handlers[i];
1431 const size_t publication = __atomic_load_n(&slot.publication, __ATOMIC_SEQ_CST);
1432 const SlotMode mode = modeOf(publication);
1433 if ((mode != SlotMode::Enabled && mode != SlotMode::Draining) || irqOf(publication) != irq ||
1434 deliveryOf(publication) != Delivery::HardOnly) {
1435 continue;
1436 }
1437
1438 const size_t generation = generationOf(publication);
1439 if (__atomic_load_n(&slot.hardHandoffState, __ATOMIC_ACQUIRE) != ((generation << 1) | 1U)) {
1440 continue;
1441 }
1442
1443 const size_t currentPublication = __atomic_load_n(&slot.publication, __ATOMIC_SEQ_CST);
1444 const SlotMode currentMode = modeOf(currentPublication);
1445 if ((currentMode == SlotMode::Enabled || currentMode == SlotMode::Draining) &&
1446 irqOf(currentPublication) == irq &&
1447 deliveryOf(currentPublication) == Delivery::HardOnly &&
1448 generationOf(currentPublication) == generation) {
1449 return true;
1450 }
1451 }
1452
1453 if (startEpoch == __atomic_load_n(&m_HardHandoffEpochs[epochBucket], __ATOMIC_ACQUIRE)) {
1454 return false;
1455 }
1456 }
1457
1458 // Repeated mutation means there was no stable false snapshot. Keeping the
1459 // physical line masked is the only safe bounded answer.
1460 return true;
1461}
1462
1463bool IrqHandlerRegistry::publishThreadedDispatch(uint8_t irq, size_t dispatchGeneration) {
1464 AdmissionCutoff admissionCutoff = {};
1465 if (!captureAdmissionCutoff(irq, admissionCutoff)) {
1466 return false;
1467 }
1468 return publishThreadedDispatch(irq, dispatchGeneration, admissionCutoff);
1469}
1470
1471bool IrqHandlerRegistry::publishThreadedDispatch(uint8_t irq, size_t dispatchGeneration,
1472 AdmissionCutoff admissionCutoff) {
1473 AdmissionCutoffCleanup cutoffCleanup(this, admissionCutoff);
1474 beginAdmissionCutoffCleanup(cutoffCleanup);
1475 const size_t expectedReaderToken =
1476 (static_cast<size_t>(irq) * 2) + (admissionCutoff.occurrenceEpoch & 1) + 1;
1477 if (admissionCutoff.readerToken != expectedReaderToken) {
1478 finishAdmissionCutoffCleanup(cutoffCleanup);
1479 return false;
1480 }
1481 if (!threadedGenerationValid(irq, dispatchGeneration)) {
1482 finishAdmissionCutoffCleanup(cutoffCleanup);
1483 return false;
1484 }
1485
1486 struct Candidate {
1487 HandlerSlot* slot;
1488 size_t publication;
1489 IrqHandlerBase* handler;
1490 size_t admissionEpoch;
1491 };
1492 Candidate candidates[MaxHandlerSlots];
1493 size_t candidateCount = 0;
1494 const size_t cutoffEpoch = admissionCutoff.epoch;
1495 bool admitted = false;
1496
1497 for (size_t i = 0; i < MaxHandlerSlots; ++i) {
1498 HandlerSlot& slot = m_Handlers[i];
1499 const size_t publication = __atomic_load_n(&slot.publication, __ATOMIC_SEQ_CST);
1500 const SlotMode mode = modeOf(publication);
1501 if (irqOf(publication) != irq || deliveryOf(publication) != Delivery::Threaded) {
1502 continue;
1503 }
1504
1505 const size_t admissionEpoch = __atomic_load_n(&slot.admissionEpoch, __ATOMIC_ACQUIRE);
1506 if (!admissionEpoch ||
1507 (admissionEpoch != cutoffEpoch && generationReached(admissionEpoch, cutoffEpoch))) {
1508 continue;
1509 }
1510
1511 if (mode == SlotMode::Cancelling || mode == SlotMode::Closed || mode == SlotMode::Retiring ||
1512 mode == SlotMode::Tombstone) {
1513 if (occurrencePrecedesRetirement(slot, admissionCutoff)) {
1514 publishSlotQuiesced(slot, irq, dispatchGeneration, QuiescedLane::Controller);
1515 admitted = true;
1516 }
1517 continue;
1518 }
1519 if (mode != SlotMode::Enabled && mode != SlotMode::Draining) {
1520 continue;
1521 }
1522
1523 IrqHandlerBase* handler = __atomic_load_n(&slot.handler, __ATOMIC_ACQUIRE);
1524 if (handler) {
1525 candidates[candidateCount++] = {&slot, publication, handler, admissionEpoch};
1526 }
1527 }
1528
1529 for (size_t i = 0; i < candidateCount; ++i) {
1530 HandlerSlot& slot = *candidates[i].slot;
1531 const size_t publication = candidates[i].publication;
1532 IrqHandlerBase* handler = candidates[i].handler;
1533 const size_t admissionEpoch = candidates[i].admissionEpoch;
1534 void* owner = currentDispatchOwner();
1535 Thread* thread = Processor::information().getCurrentThread();
1536 DispatchCleanup dispatchCleanup(this, &slot, owner, publication, false);
1537 if (thread) {
1538 // Removal drains this short publication hazard before it can reuse
1539 // the slot for a different handler lifetime.
1540 thread->armAtomicStateCleanup(dispatchCleanup.cleanup, abandonDispatch, &dispatchCleanup);
1541 }
1542
1543#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
1544 HandlerHazardHook hazardHook = __atomic_load_n(&m_HandlerHazardHook, __ATOMIC_ACQUIRE);
1545 if (hazardHook) {
1546 hazardHook(handler, HandlerHazardStage::BeforeClaim);
1547 }
1548#endif
1549
1550 if (!publishDispatch(slot, owner, &dispatchCleanup, publication, dispatchGeneration, false)) {
1551 if (thread) {
1552 thread->disarmAtomicStateCleanup(dispatchCleanup.cleanup);
1553 }
1554 finishAdmissionCutoffCleanup(cutoffCleanup);
1555 FATAL_NOLOCK("IRQ callback hazard table exhausted.");
1556 return admitted;
1557 }
1558
1559 size_t currentPublication = __atomic_load_n(&slot.publication, __ATOMIC_SEQ_CST);
1560 SlotMode currentMode = modeOf(currentPublication);
1561 const bool sameLifetime =
1562 generationOf(currentPublication) == generationOf(publication) &&
1563 irqOf(currentPublication) == irq && deliveryOf(currentPublication) == Delivery::Threaded &&
1564 __atomic_load_n(&slot.handler, __ATOMIC_ACQUIRE) == handler &&
1565 __atomic_load_n(&slot.admissionEpoch, __ATOMIC_ACQUIRE) == admissionEpoch;
1566 if (sameLifetime && (currentMode == SlotMode::Enabled || currentMode == SlotMode::Draining) &&
1567 threadedGenerationValid(irq, dispatchGeneration)) {
1568 if (currentMode == SlotMode::Draining) {
1569 size_t expectedPublication = currentPublication;
1570 const size_t enabledPublication = makePublication(generationOf(currentPublication), irq,
1571 SlotMode::Enabled, Delivery::Threaded);
1572 beginMutation();
1573 __atomic_compare_exchange_n(&slot.publication, &expectedPublication, enabledPublication,
1574 false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST);
1575 finishMutation();
1576 currentPublication = __atomic_load_n(&slot.publication, __ATOMIC_SEQ_CST);
1577 currentMode = modeOf(currentPublication);
1578 }
1579
1580 if (currentMode != SlotMode::Enabled) {
1581 publishSlotQuiesced(slot, irq, dispatchGeneration, QuiescedLane::Controller);
1582 }
1583
1584 if (currentMode == SlotMode::Enabled) {
1585#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
1586 HandlerHazardHook publicationHook = __atomic_load_n(&m_HandlerHazardHook, __ATOMIC_ACQUIRE);
1587 if (publicationHook) {
1588 publicationHook(handler, HandlerHazardStage::BeforePendingExchange);
1589 }
1590#endif
1591
1592 const size_t pending = __atomic_load_n(&slot.pendingThreadedGeneration, __ATOMIC_ACQUIRE);
1593 if (!pending || !generationReached(pending, dispatchGeneration)) {
1594 // The controller is the only producer for this line. A
1595 // single exchange cannot lose a concurrent worker claim:
1596 // the worker's exact CAS either consumed the old
1597 // generation first or observes this newer one and leaves
1598 // it for the matching cookie.
1599 __atomic_exchange_n(&slot.pendingThreadedGeneration, dispatchGeneration,
1600 __ATOMIC_ACQ_REL);
1601 }
1602
1603#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
1604 publicationHook = __atomic_load_n(&m_HandlerHazardHook, __ATOMIC_ACQUIRE);
1605 if (publicationHook) {
1606 publicationHook(handler, HandlerHazardStage::PendingExchanged);
1607 }
1608#endif
1609
1610 if (!threadedGenerationValid(irq, dispatchGeneration)) {
1611 size_t stale = dispatchGeneration;
1612 __atomic_compare_exchange_n(&slot.pendingThreadedGeneration, &stale,
1613 static_cast<size_t>(0), false, __ATOMIC_ACQ_REL,
1614 __ATOMIC_ACQUIRE);
1615 }
1616 }
1617
1618 const size_t finalPublication = __atomic_load_n(&slot.publication, __ATOMIC_SEQ_CST);
1619 const SlotMode finalMode = modeOf(finalPublication);
1620 if (generationOf(finalPublication) != generationOf(publication) ||
1621 irqOf(finalPublication) != irq ||
1622 (finalMode != SlotMode::Enabled && finalMode != SlotMode::Draining) ||
1623 __atomic_load_n(&slot.handler, __ATOMIC_ACQUIRE) != handler ||
1624 __atomic_load_n(&slot.admissionEpoch, __ATOMIC_ACQUIRE) != admissionEpoch) {
1625 publishSlotQuiesced(slot, irq, dispatchGeneration, QuiescedLane::Controller);
1626 }
1627 } else {
1628 // This publication belonged to the occurrence cutoff. If removal
1629 // closed it before token publication, preserve that membership as
1630 // a quiesced retirement marker for the worker.
1631 if (occurrencePrecedesRetirement(slot, admissionCutoff)) {
1632 publishSlotQuiesced(slot, irq, dispatchGeneration, QuiescedLane::Controller);
1633 }
1634 }
1635 admitted = true;
1636
1637 unpublishDispatch(&dispatchCleanup, slot, publication, true);
1638 if (thread) {
1639 thread->disarmAtomicStateCleanup(dispatchCleanup.cleanup);
1640 }
1641 }
1642
1643 finishAdmissionCutoffCleanup(cutoffCleanup);
1644 return admitted;
1645}
1646
1647bool IrqHandlerRegistry::dispatchThreaded(uint8_t irq, size_t dispatchGeneration,
1648 ThreadedDispatchResult& result, IrqHandler* onlyHandler) {
1649 result = {false, false};
1650 bool admitted = false;
1651 if (!threadedGenerationValid(irq, dispatchGeneration)) {
1652 return false;
1653 }
1654 Thread* dispatchThread = Processor::information().getCurrentThread();
1655 if (!dispatchThread || !Processor::getInterrupts()) {
1656 return false;
1657 }
1658#if HOSTED
1659 if (dispatchThread->getHostedSignalDepth()) {
1660 return false;
1661 }
1662#endif
1663
1664 const bool interruptsWereEnabled = Processor::getInterrupts();
1666 AdmissionCutoff workerCutoff = {};
1667 const bool cutoffCaptured = captureAdmissionCutoff(irq, workerCutoff);
1668 AdmissionCutoffCleanup cutoffCleanup(this, workerCutoff);
1669 if (cutoffCaptured) {
1670 beginAdmissionCutoffCleanup(cutoffCleanup);
1671 }
1672 Processor::setInterrupts(interruptsWereEnabled);
1673 if (!cutoffCaptured) {
1674 return false;
1675 }
1676
1677 struct Candidate {
1678 HandlerSlot* slot;
1679 size_t publication;
1680 IrqHandlerBase* handler;
1681 };
1682 while (true) {
1683 if (__atomic_load_n(&m_ThreadedActionMutationWriters, __ATOMIC_SEQ_CST)) {
1685 continue;
1686 }
1687 const size_t actionMutationGeneration =
1688 __atomic_load_n(&m_ThreadedActionMutationGeneration, __ATOMIC_SEQ_CST);
1689 if (__atomic_load_n(&m_ThreadedActionMutationWriters, __ATOMIC_SEQ_CST)) {
1691 continue;
1692 }
1693
1694 Candidate candidates[MaxHandlerSlots];
1695 size_t candidateCount = 0;
1696
1697 // The hard stage marked exact slot publications. A later registration
1698 // has no token, and a newer occurrence remains pending when an older
1699 // worker batch is already active.
1700 for (size_t i = 0; i < MaxHandlerSlots; ++i) {
1701 HandlerSlot& slot = m_Handlers[i];
1702 const size_t publication = __atomic_load_n(&slot.publication, __ATOMIC_SEQ_CST);
1703 if (irqOf(publication) != irq || deliveryOf(publication) != Delivery::Threaded) {
1704 continue;
1705 }
1706
1707 for (size_t lane = 0; lane < QuiescedLaneCount; ++lane) {
1708 size_t* publicationLane = &slot.quiescedThreadedGenerations[lane];
1709 size_t quiesced = __atomic_load_n(publicationLane, __ATOMIC_ACQUIRE);
1710 if (quiesced && !threadedGenerationValid(irq, quiesced)) {
1711 __atomic_compare_exchange_n(publicationLane, &quiesced, static_cast<size_t>(0), false,
1712 __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE);
1713 continue;
1714 }
1715 if (quiesced && generationReached(dispatchGeneration, quiesced) &&
1716 __atomic_compare_exchange_n(publicationLane, &quiesced, static_cast<size_t>(0), false,
1717 __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE)) {
1718 admitted = true;
1719 result.allowRearm = true;
1720 }
1721 }
1722
1723#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
1724 HandlerHazardHook quiescedHook = __atomic_load_n(&m_HandlerHazardHook, __ATOMIC_ACQUIRE);
1725 if (quiescedHook) {
1726 quiescedHook(__atomic_load_n(&slot.handler, __ATOMIC_ACQUIRE),
1727 HandlerHazardStage::QuiescedObserved);
1728 }
1729#endif
1730
1731 if (modeOf(publication) != SlotMode::Enabled) {
1732 continue;
1733 }
1734
1735 const size_t pending = __atomic_load_n(&slot.pendingThreadedGeneration, __ATOMIC_ACQUIRE);
1736 if (!pending || !generationReached(dispatchGeneration, pending)) {
1737 continue;
1738 }
1739 if (!threadedGenerationValid(irq, pending)) {
1740 size_t stale = pending;
1741 __atomic_compare_exchange_n(&slot.pendingThreadedGeneration, &stale, static_cast<size_t>(0),
1742 false, __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE);
1743 continue;
1744 }
1745
1746 IrqHandlerBase* handler = __atomic_load_n(&slot.handler, __ATOMIC_ACQUIRE);
1747 if (!handler || (onlyHandler && handler != static_cast<IrqHandlerBase*>(onlyHandler))) {
1748 continue;
1749 }
1750
1751 candidates[candidateCount++] = {&slot, publication, handler};
1752 }
1753
1754 for (size_t i = 0; i < candidateCount; ++i) {
1755 HandlerSlot& slot = *candidates[i].slot;
1756 const size_t publication = candidates[i].publication;
1757 IrqHandlerBase* handler = candidates[i].handler;
1758
1759#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
1760 HandlerPrePinHook prePinHook = __atomic_load_n(&m_HandlerPrePinHook, __ATOMIC_ACQUIRE);
1761 if (prePinHook) {
1762 prePinHook(handler);
1763 }
1764#endif
1765
1766 void* owner = currentDispatchOwner();
1767 Thread* thread = Processor::information().getCurrentThread();
1768 DispatchCleanup dispatchCleanup(this, &slot, owner, publication);
1769 if (thread) {
1770 // Publish cleanup before committing the active-dispatch hazard.
1771 // A nested exception can therefore abandon this stack at every
1772 // later instruction without leaking callback admission.
1773 thread->armAtomicStateCleanup(dispatchCleanup.cleanup, abandonDispatch, &dispatchCleanup);
1774 }
1775
1776#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
1777 HandlerHazardHook hazardHook = __atomic_load_n(&m_HandlerHazardHook, __ATOMIC_ACQUIRE);
1778 if (hazardHook) {
1779 hazardHook(handler, HandlerHazardStage::BeforeClaim);
1780 }
1781#endif
1782
1783 if (!publishDispatch(slot, owner, &dispatchCleanup, publication, dispatchGeneration)) {
1784 if (thread) {
1785 thread->disarmAtomicStateCleanup(dispatchCleanup.cleanup);
1786 }
1787 finishAdmissionCutoffCleanup(cutoffCleanup);
1788 FATAL_NOLOCK("IRQ callback hazard table exhausted.");
1789 return admitted;
1790 }
1791
1792 if (__atomic_load_n(&slot.publication, __ATOMIC_SEQ_CST) != publication ||
1793 __atomic_load_n(&slot.handler, __ATOMIC_ACQUIRE) != handler) {
1794 unpublishDispatch(&dispatchCleanup, slot, publication, true);
1795 if (thread) {
1796 thread->disarmAtomicStateCleanup(dispatchCleanup.cleanup);
1797 }
1798 continue;
1799 }
1800
1801#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
1802 HandlerPinHook hook = __atomic_load_n(&m_HandlerPinHook, __ATOMIC_ACQUIRE);
1803 if (hook) {
1804 hook(handler);
1805 }
1806#endif
1807
1808 size_t pending = __atomic_load_n(&slot.pendingThreadedGeneration, __ATOMIC_ACQUIRE);
1809 bool claimed = false;
1810 size_t claimedGeneration = 0;
1811 while (pending && generationReached(dispatchGeneration, pending)) {
1812 size_t emptyClaim = 0;
1813 if (!__atomic_compare_exchange_n(&slot.claimedThreadedGeneration, &emptyClaim, pending,
1814 false, __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE)) {
1815 break;
1816 }
1817
1818 claimedGeneration = pending;
1819 size_t exactPending = pending;
1820 if (__atomic_compare_exchange_n(&slot.pendingThreadedGeneration, &exactPending,
1821 static_cast<size_t>(0), false, __ATOMIC_ACQ_REL,
1822 __ATOMIC_ACQUIRE)) {
1823 claimed = true;
1824 break;
1825 }
1826 size_t exactClaim = claimedGeneration;
1827 __atomic_compare_exchange_n(&slot.claimedThreadedGeneration, &exactClaim,
1828 static_cast<size_t>(0), false, __ATOMIC_ACQ_REL,
1829 __ATOMIC_ACQUIRE);
1830 pending = exactPending;
1831 }
1832 if (!claimed) {
1833 unpublishDispatch(&dispatchCleanup, slot, publication, true);
1834 if (thread) {
1835 thread->disarmAtomicStateCleanup(dispatchCleanup.cleanup);
1836 }
1837 continue;
1838 }
1839
1840 admitted = true;
1841
1842 const IrqDisposition disposition = static_cast<IrqHandler*>(handler)->irq(irq);
1843 if (disposition == IrqDisposition::Handled) {
1844 result.handled = true;
1845 result.allowRearm = true;
1846 } else if (disposition == IrqDisposition::Quiesced) {
1847 result.allowRearm = true;
1848 }
1849 unpublishDispatch(&dispatchCleanup, slot, publication, true);
1850 if (thread) {
1851 thread->disarmAtomicStateCleanup(dispatchCleanup.cleanup);
1852 }
1853
1854 if (!acquireFinalizationGate(slot, true)) {
1855 finishAdmissionCutoffCleanup(cutoffCleanup);
1856 FATAL("IRQ action finalization gate could not be acquired.");
1857 return admitted;
1858 }
1859
1860 size_t finalPublication = __atomic_load_n(&slot.publication, __ATOMIC_SEQ_CST);
1861#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
1862 HandlerHazardHook finalizationHook = __atomic_load_n(&m_HandlerHazardHook, __ATOMIC_ACQUIRE);
1863 if (finalizationHook) {
1864 finalizationHook(handler, HandlerHazardStage::BeforeClaimFinalization);
1865 }
1866#endif
1867 if (generationOf(finalPublication) == generationOf(publication) &&
1868 irqOf(finalPublication) == irq && deliveryOf(finalPublication) == Delivery::Threaded &&
1869 modeOf(finalPublication) == SlotMode::Draining &&
1870 __atomic_load_n(&slot.handler, __ATOMIC_ACQUIRE) == handler) {
1871 size_t expectedPublication = finalPublication;
1872 beginMutation();
1873 __atomic_compare_exchange_n(&slot.publication, &expectedPublication, publication, false,
1874 __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST);
1875 finishMutation();
1876 finalPublication = __atomic_load_n(&slot.publication, __ATOMIC_SEQ_CST);
1877 }
1878 if (finalPublication == publication) {
1879 size_t exactClaim = claimedGeneration;
1880 __atomic_compare_exchange_n(&slot.claimedThreadedGeneration, &exactClaim,
1881 static_cast<size_t>(0), false, __ATOMIC_ACQ_REL,
1882 __ATOMIC_ACQUIRE);
1883 } else {
1884 publishSlotQuiesced(slot, irq, claimedGeneration, QuiescedLane::Callback);
1885 size_t exactClaim = claimedGeneration;
1886 __atomic_compare_exchange_n(&slot.claimedThreadedGeneration, &exactClaim,
1887 static_cast<size_t>(0), false, __ATOMIC_ACQ_REL,
1888 __ATOMIC_ACQUIRE);
1889 }
1890 releaseFinalizationGate(slot);
1891 }
1892
1893 bool admissionResolving = false;
1894 for (size_t i = 0; i < MaxHandlerSlots; ++i) {
1895 HandlerSlot& slot = m_Handlers[i];
1896 const size_t publication = __atomic_load_n(&slot.publication, __ATOMIC_SEQ_CST);
1897 if (irqOf(publication) != irq || deliveryOf(publication) != Delivery::Threaded) {
1898 continue;
1899 }
1900
1901 size_t pending = __atomic_load_n(&slot.pendingThreadedGeneration, __ATOMIC_ACQUIRE);
1902 size_t claimed = __atomic_load_n(&slot.claimedThreadedGeneration, __ATOMIC_ACQUIRE);
1903 const bool pendingReached = pending && generationReached(dispatchGeneration, pending);
1904 const bool claimedReached = claimed && generationReached(dispatchGeneration, claimed);
1905 if (!pendingReached && !claimedReached) {
1906 continue;
1907 }
1908 if (pendingReached && !threadedGenerationValid(irq, pending)) {
1909 size_t stale = pending;
1910 __atomic_compare_exchange_n(&slot.pendingThreadedGeneration, &stale, static_cast<size_t>(0),
1911 false, __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE);
1912 pending = 0;
1913 }
1914
1915 const SlotMode mode = modeOf(publication);
1916 if (mode == SlotMode::Draining || mode == SlotMode::Cancelling || mode == SlotMode::Closed ||
1917 mode == SlotMode::Retiring) {
1918 // Draining may restore Enabled; committed removal reaches
1919 // Empty only after publishing quiescence and clearing the
1920 // token. Do not claim here or retirement could publish a stale
1921 // marker after this worker's final watermark scan.
1922 admissionResolving = true;
1923 continue;
1924 }
1925 }
1926
1927 const size_t finalActionMutationWriters =
1928 __atomic_load_n(&m_ThreadedActionMutationWriters, __ATOMIC_SEQ_CST);
1929 const size_t finalActionMutationGeneration =
1930 __atomic_load_n(&m_ThreadedActionMutationGeneration, __ATOMIC_SEQ_CST);
1931 if (finalActionMutationWriters || finalActionMutationGeneration != actionMutationGeneration) {
1932 continue;
1933 }
1934
1935 if (!admissionResolving && !candidateCount) {
1936 break;
1937 }
1938 if (admissionResolving) {
1940 }
1941 }
1942
1943 finishAdmissionCutoffCleanup(cutoffCleanup);
1944 return admitted;
1945}
1946
1947size_t IrqHandlerRegistry::handlerCount(uint8_t irq) {
1948 size_t count = 0;
1949 for (size_t i = 0; i < MaxHandlerSlots; ++i) {
1950 const size_t publication = __atomic_load_n(&m_Handlers[i].publication, __ATOMIC_SEQ_CST);
1951 const SlotMode mode = modeOf(publication);
1952 if ((mode == SlotMode::Enabled || mode == SlotMode::Draining) && irqOf(publication) == irq) {
1953 ++count;
1954 }
1955 }
1956 return count;
1957}
1958
1959IrqHandlerRegistry::LineMode IrqHandlerRegistry::lineMode(uint8_t irq) {
1960 bool threaded = false;
1961 bool hard = false;
1962 for (size_t i = 0; i < MaxHandlerSlots; ++i) {
1963 const size_t publication = __atomic_load_n(&m_Handlers[i].publication, __ATOMIC_SEQ_CST);
1964 const SlotMode mode = modeOf(publication);
1965 if ((mode != SlotMode::Enabled && mode != SlotMode::Draining) || irqOf(publication) != irq) {
1966 continue;
1967 }
1968
1969 if (deliveryOf(publication) == Delivery::Threaded) {
1970 threaded = true;
1971 } else {
1972 hard = true;
1973 }
1974 if (threaded && hard) {
1975 return LineMode::Mixed;
1976 }
1977 }
1978 return threaded ? LineMode::Threaded : hard ? LineMode::HardOnly : LineMode::Empty;
1979}
1980
1982 LineConfiguration& configuration) const {
1983 for (size_t attempt = 0; attempt < LineSnapshotAttempts; ++attempt) {
1984 if (__atomic_load_n(&m_MutationWriters, __ATOMIC_SEQ_CST)) {
1985 continue;
1986 }
1987 const size_t generation = __atomic_load_n(&m_MutationGeneration, __ATOMIC_SEQ_CST);
1988 if (__atomic_load_n(&m_MutationWriters, __ATOMIC_SEQ_CST)) {
1989 continue;
1990 }
1991
1992 LineConfiguration observed;
1993 size_t threadedPolicy = 0;
1994 size_t hardPolicy = 0;
1995 bool observedThreaded = false;
1996 bool observedHard = false;
1997 bool consistent = true;
1998 for (size_t i = 0; i < MaxHandlerSlots; ++i) {
1999 const HandlerSlot& slot = m_Handlers[i];
2000 const size_t publication = __atomic_load_n(&slot.publication, __ATOMIC_SEQ_CST);
2001 if (modeOf(publication) != SlotMode::Enabled || irqOf(publication) != irq) {
2002 continue;
2003 }
2004
2005 const size_t policy = __atomic_load_n(&slot.policy, __ATOMIC_ACQUIRE);
2006 if (__atomic_load_n(&slot.publication, __ATOMIC_SEQ_CST) != publication) {
2007 consistent = false;
2008 break;
2009 }
2010
2011 if (deliveryOf(publication) == Delivery::Threaded) {
2012 if (observedThreaded && threadedPolicy != policy) {
2013 consistent = false;
2014 break;
2015 }
2016 observedThreaded = true;
2017 threadedPolicy = policy;
2018 } else {
2019 if (observedHard && hardPolicy != policy) {
2020 consistent = false;
2021 break;
2022 }
2023 observedHard = true;
2024 hardPolicy = policy;
2025 }
2026 ++observed.handlerCount;
2027 }
2028
2029 size_t observedPolicy = 0;
2030 if (consistent && observedThreaded && observedHard) {
2031 consistent = mixedPoliciesCompatible(hardPolicy, threadedPolicy);
2032 observed.mode = LineMode::Mixed;
2033 observedPolicy = effectiveMixedPolicy(hardPolicy, threadedPolicy);
2034 } else if (consistent && observedThreaded) {
2035 observed.mode = LineMode::Threaded;
2036 observedPolicy = threadedPolicy;
2037 } else if (consistent && observedHard) {
2038 observed.mode = LineMode::HardOnly;
2039 observedPolicy = hardPolicy;
2040 }
2041
2042 // Writer count must be sampled first: a writer which finishes between
2043 // these loads changes the following generation and forces a retry.
2044 const size_t finalWriters = __atomic_load_n(&m_MutationWriters, __ATOMIC_SEQ_CST);
2045 const size_t finalGeneration = __atomic_load_n(&m_MutationGeneration, __ATOMIC_SEQ_CST);
2046 if (finalWriters || generation != finalGeneration) {
2047 continue;
2048 }
2049 if (!consistent) {
2050 return false;
2051 }
2052
2053 observed.mutationGeneration = finalGeneration;
2054 decodePolicy(observedPolicy, observed);
2055 configuration = observed;
2056 return true;
2057 }
2058
2059 return false;
2060}
2061
2062size_t IrqHandlerRegistry::hardDispatchState(uint8_t irq, size_t& exactGeneration) const {
2063 size_t count = 0;
2064 exactGeneration = 0;
2065 for (size_t i = 0; i < MaxActiveDispatches; ++i) {
2066 const ActiveDispatch& dispatch = m_ActiveDispatches[i];
2067 void* token = __atomic_load_n(&dispatch.token, __ATOMIC_ACQUIRE);
2068 if (!token) {
2069 continue;
2070 }
2071
2072 const size_t generation = __atomic_load_n(&dispatch.generation, __ATOMIC_ACQUIRE);
2073 HandlerSlot* slot = __atomic_load_n(&dispatch.slot, __ATOMIC_SEQ_CST);
2074 const size_t publication = __atomic_load_n(&dispatch.admittedPublication, __ATOMIC_RELAXED);
2075 const size_t controllerGeneration =
2076 __atomic_load_n(&dispatch.controllerGeneration, __ATOMIC_RELAXED);
2077 if (slot && irqOf(publication) == irq && deliveryOf(publication) == Delivery::HardOnly &&
2078 __atomic_load_n(&dispatch.token, __ATOMIC_ACQUIRE) == token &&
2079 __atomic_load_n(&dispatch.generation, __ATOMIC_ACQUIRE) == generation &&
2080 __atomic_load_n(&dispatch.slot, __ATOMIC_SEQ_CST) == slot) {
2081 ++count;
2082 exactGeneration = count == 1 ? controllerGeneration : 0;
2083 }
2084 }
2085 return count;
2086}
2087
2089 uintptr_t& exactHandlerIdentity) const {
2090 size_t count = 0;
2091 exactHandlerIdentity = 0;
2092 for (size_t i = 0; i < MaxActiveDispatches; ++i) {
2093 const ActiveDispatch& dispatch = m_ActiveDispatches[i];
2094 void* token = __atomic_load_n(&dispatch.token, __ATOMIC_ACQUIRE);
2095 if (!token) {
2096 continue;
2097 }
2098
2099 const size_t generation = __atomic_load_n(&dispatch.generation, __ATOMIC_ACQUIRE);
2100 HandlerSlot* slot = __atomic_load_n(&dispatch.slot, __ATOMIC_SEQ_CST);
2101 const size_t publication = __atomic_load_n(&dispatch.admittedPublication, __ATOMIC_RELAXED);
2102 IrqHandlerBase* handler = slot ? __atomic_load_n(&slot->handler, __ATOMIC_ACQUIRE) : nullptr;
2103 if (slot && handler && irqOf(publication) == irq &&
2104 deliveryOf(publication) == Delivery::Threaded &&
2105 __atomic_load_n(&dispatch.callback, __ATOMIC_RELAXED) &&
2106 __atomic_load_n(&dispatch.token, __ATOMIC_ACQUIRE) == token &&
2107 __atomic_load_n(&dispatch.generation, __ATOMIC_ACQUIRE) == generation &&
2108 __atomic_load_n(&dispatch.slot, __ATOMIC_SEQ_CST) == slot) {
2109 ++count;
2110 exactHandlerIdentity = count == 1 ? reinterpret_cast<uintptr_t>(handler) : 0;
2111 }
2112 }
2113 return count;
2114}
2115
2116#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
2117void IrqHandlerRegistry::setHandlerPinHook(HandlerPinHook hook) {
2118 __atomic_store_n(&m_HandlerPinHook, hook, __ATOMIC_RELEASE);
2119}
2120
2121void IrqHandlerRegistry::setHandlerPrePinHook(HandlerPrePinHook hook) {
2122 __atomic_store_n(&m_HandlerPrePinHook, hook, __ATOMIC_RELEASE);
2123}
2124
2125void IrqHandlerRegistry::setHandlerHazardHook(HandlerHazardHook hook) {
2126 __atomic_store_n(&m_HandlerHazardHook, hook, __ATOMIC_RELEASE);
2127}
2128
2129void IrqHandlerRegistry::setDispatchAbandonHook(DispatchAbandonHook hook) {
2130 __atomic_store_n(&m_DispatchAbandonHook, hook, __ATOMIC_RELEASE);
2131}
2132
2133void IrqHandlerRegistry::setOccurrenceCaptureHookForTest(OccurrenceCaptureHook hook) {
2134 __atomic_store_n(&m_OccurrenceCaptureHook, hook, __ATOMIC_RELEASE);
2135}
2136
2137void IrqHandlerRegistry::observeOccurrenceCaptureForTest(uint8_t irq, OccurrenceCaptureStage stage,
2138 size_t occurrenceEpoch) {
2139 OccurrenceCaptureHook hook = __atomic_load_n(&m_OccurrenceCaptureHook, __ATOMIC_ACQUIRE);
2140 if (hook) {
2141 hook(this, irq, stage, occurrenceEpoch);
2142 }
2143}
2144
2145void IrqHandlerRegistry::withMutationLockForTest(MutationLockHook hook) {
2146 m_HandlerLock.acquire();
2147 if (hook) {
2148 hook();
2149 }
2150 m_HandlerLock.release();
2151}
2152
2153void IrqHandlerRegistry::withMutationEpochForTest(MutationLockHook hook) {
2154 beginMutation();
2155 if (hook) {
2156 hook();
2157 }
2158 finishMutation();
2159}
2160
2161size_t IrqHandlerRegistry::activeDispatchCountForTest(IrqHandlerBase* handler) {
2162 size_t count = 0;
2163 for (size_t i = 0; i < MaxActiveDispatches; ++i) {
2164 ActiveDispatch& dispatch = m_ActiveDispatches[i];
2165 void* token = __atomic_load_n(&dispatch.token, __ATOMIC_ACQUIRE);
2166 if (!token) {
2167 continue;
2168 }
2169 const size_t generation = __atomic_load_n(&dispatch.generation, __ATOMIC_ACQUIRE);
2170 HandlerSlot* slot = __atomic_load_n(&dispatch.slot, __ATOMIC_SEQ_CST);
2171 IrqHandlerBase* activeHandler =
2172 slot ? __atomic_load_n(&slot->handler, __ATOMIC_ACQUIRE) : nullptr;
2173 if (activeHandler == handler && __atomic_load_n(&dispatch.callback, __ATOMIC_RELAXED) &&
2174 __atomic_load_n(&dispatch.token, __ATOMIC_ACQUIRE) == token &&
2175 __atomic_load_n(&dispatch.generation, __ATOMIC_ACQUIRE) == generation) {
2176 ++count;
2177 }
2178 }
2179 return count;
2180}
2181
2182size_t IrqHandlerRegistry::claimedDispatchCountForOwnerForTest(void* owner) {
2183 if (!owner) {
2184 return 0;
2185 }
2186
2187 size_t count = 0;
2188 for (size_t i = 0; i < MaxActiveDispatches; ++i) {
2189 ActiveDispatch& dispatch = m_ActiveDispatches[i];
2190 void* token = __atomic_load_n(&dispatch.token, __ATOMIC_ACQUIRE);
2191 if (!token) {
2192 continue;
2193 }
2194
2195 const size_t generation = __atomic_load_n(&dispatch.generation, __ATOMIC_ACQUIRE);
2196 void* dispatchOwner = __atomic_load_n(&dispatch.owner, __ATOMIC_RELAXED);
2197 if (dispatchOwner == owner && __atomic_load_n(&dispatch.callback, __ATOMIC_RELAXED) &&
2198 __atomic_load_n(&dispatch.token, __ATOMIC_ACQUIRE) == token &&
2199 __atomic_load_n(&dispatch.generation, __ATOMIC_ACQUIRE) == generation) {
2200 ++count;
2201 }
2202 }
2203 return count;
2204}
2205
2206bool IrqHandlerRegistry::containsHandlerForTest(uint8_t irq, IrqHandlerBase* handler) {
2207 for (size_t i = 0; i < MaxHandlerSlots; ++i) {
2208 HandlerSlot& slot = m_Handlers[i];
2209 const size_t publication = __atomic_load_n(&slot.publication, __ATOMIC_SEQ_CST);
2210 if (modeOf(publication) != SlotMode::Empty && irqOf(publication) == irq &&
2211 __atomic_load_n(&slot.handler, __ATOMIC_ACQUIRE) == handler &&
2212 __atomic_load_n(&slot.publication, __ATOMIC_SEQ_CST) == publication) {
2213 return true;
2214 }
2215 }
2216 return false;
2217}
2218
2219size_t IrqHandlerRegistry::tombstoneCountForTest(uint8_t irq) const {
2220 size_t count = 0;
2221 for (size_t i = 0; i < MaxHandlerSlots; ++i) {
2222 const size_t publication = __atomic_load_n(&m_Handlers[i].publication, __ATOMIC_SEQ_CST);
2223 if (modeOf(publication) == SlotMode::Tombstone && irqOf(publication) == irq) {
2224 ++count;
2225 }
2226 }
2227 return count;
2228}
2229
2230size_t IrqHandlerRegistry::threadedActionMutationWriterCountForTest() const {
2231 return __atomic_load_n(&m_ThreadedActionMutationWriters, __ATOMIC_SEQ_CST);
2232}
2233
2234bool IrqHandlerRegistry::setThreadedActionLanesForTest(IrqHandlerBase* handler,
2235 size_t pendingGeneration,
2236 size_t claimedGeneration,
2237 size_t quiescedGeneration) {
2238 for (size_t i = 0; i < MaxHandlerSlots; ++i) {
2239 HandlerSlot& slot = m_Handlers[i];
2240 if (__atomic_load_n(&slot.handler, __ATOMIC_ACQUIRE) != handler) {
2241 continue;
2242 }
2243
2244 __atomic_store_n(&slot.pendingThreadedGeneration, pendingGeneration, __ATOMIC_RELEASE);
2245 __atomic_store_n(&slot.claimedThreadedGeneration, claimedGeneration, __ATOMIC_RELEASE);
2246 for (size_t lane = 0; lane < QuiescedLaneCount; ++lane) {
2247 __atomic_store_n(&slot.quiescedThreadedGenerations[lane], static_cast<size_t>(0),
2248 __ATOMIC_RELEASE);
2249 }
2250 __atomic_store_n(quiescedLane(slot, QuiescedLane::Controller), quiescedGeneration,
2251 __ATOMIC_RELEASE);
2252 return true;
2253 }
2254 return false;
2255}
2256
2257bool IrqHandlerRegistry::consumeThreadedQuiescedForTest(IrqHandlerBase* handler,
2258 size_t generation) {
2259 for (size_t i = 0; i < MaxHandlerSlots; ++i) {
2260 HandlerSlot& slot = m_Handlers[i];
2261 if (__atomic_load_n(&slot.handler, __ATOMIC_ACQUIRE) != handler) {
2262 continue;
2263 }
2264
2265 for (size_t lane = 0; lane < QuiescedLaneCount; ++lane) {
2266 size_t exact = generation;
2267 if (__atomic_compare_exchange_n(&slot.quiescedThreadedGenerations[lane], &exact,
2268 static_cast<size_t>(0), false, __ATOMIC_ACQ_REL,
2269 __ATOMIC_ACQUIRE)) {
2270 return true;
2271 }
2272 }
2273 return false;
2274 }
2275 return false;
2276}
2277
2278bool IrqHandlerRegistry::publishControllerQuiescedForTest(IrqHandlerBase* handler, uint8_t irq,
2279 size_t generation) {
2280 for (size_t i = 0; i < MaxHandlerSlots; ++i) {
2281 HandlerSlot& slot = m_Handlers[i];
2282 if (__atomic_load_n(&slot.handler, __ATOMIC_ACQUIRE) != handler) {
2283 continue;
2284 }
2285
2286 publishSlotQuiesced(slot, irq, generation, QuiescedLane::Controller);
2287 return true;
2288 }
2289 return false;
2290}
2291#endif
static void restoreDepth(size_t previousDepth)
Definition Processor.cc:169
void invalidateThreadedGenerationFromInterrupt(uint8_t irq, size_t throughGeneration)
bool snapshotLineConfiguration(uint8_t irq, LineConfiguration &configuration) const
void releaseAdmissionCutoff(AdmissionCutoff admissionCutoff)
bool dispatchThreaded(uint8_t irq, size_t dispatchGeneration, ThreadedDispatchResult &result, IrqHandler *onlyHandler=nullptr)
bool captureMixedAdmissionCutoffs(uint8_t irq, MixedAdmissionCutoffs &cutoffs)
size_t threadedDispatchState(uint8_t irq, uintptr_t &exactHandlerIdentity) const
bool publishThreadedDispatch(uint8_t irq, size_t dispatchGeneration)
bool captureAdmissionCutoff(uint8_t irq, AdmissionCutoff &cutoff)
bool registerThreadedHandler(uint8_t irq, IrqHandler *handler)
UnregisterResult unregisterHandler(uint8_t irq, IrqHandlerBase *handler)
void invalidateThreadedLine(uint8_t irq, size_t throughGeneration)
size_t m_HardHandoffEpochs[GraceBucketCount]
bool hardLineQuarantined(uint8_t irq) const
bool dispatchHard(uint8_t irq, InterruptState &state, HardIrqDisposition &disposition, HardIrqHandler *onlyHandler=nullptr, size_t dispatchGeneration=0)
size_t hardDispatchState(uint8_t irq, size_t &exactGeneration) const
bool registerHardHandler(uint8_t irq, HardIrqHandler *handler)
LineMode lineMode(uint8_t irq)
static bool getInterrupts()
static ProcessorInformation & information()
static bool inDeviceHardIrq()
Definition Processor.h:559
static void setInterrupts(bool bEnable)
static Scheduler & instance()
Definition Scheduler.h:96
void yield()
Definition Scheduler.cc:226
void release()
Definition Spinlock.cc:161
bool acquire(bool recurse=false, bool safe=true)
Definition Spinlock.cc:35
void setDebugState(DebugState state, uintptr_t address)
Definition Thread.h:589
DebugState
Definition Thread.h:176
DebugState getDebugState(uintptr_t &address)
Definition Thread.h:570
IrqDisposition
Definition IrqHandler.h:31
HardIrqDisposition
Definition IrqHandler.h:44