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