The Pedigree Project 0.1
PicIrqState.h
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#ifndef PEDIGREE_KERNEL_MACHINE_MACH_PC_PICIRQSTATE_H
9#define PEDIGREE_KERNEL_MACHINE_MACH_PC_PICIRQSTATE_H
10#include "pedigree/kernel/machine/IrqHandler.h"
11#include "pedigree/kernel/machine/IrqManager.h"
12#include "pedigree/kernel/processor/types.h"
13#include "pedigree/kernel/utilities/assert.h"
14
15#include <config.h>
16
17#include "PicElcr.h"
18
29 public:
30 static constexpr size_t LineCount = 16;
31 static constexpr size_t CascadeIrq = 2;
32
33 enum class TriggerMode : uint8_t {
34 Unconfigured,
35 Level,
36 Edge,
37 };
38
39 PicIrqState() : m_Mask(0) {
40 for (size_t i = 0; i < LineCount; ++i) {
41 m_TriggerModes[i] = TriggerMode::Unconfigured;
42 m_ControllerAck[i] = IrqControllerAck::AfterHardStage;
43 m_HardHandlerCounts[i] = 0;
44 m_ThreadedHandlerCounts[i] = 0;
45 m_DispatchGenerations[i] = 0;
46 m_AcknowledgedGenerations[i] = 0;
47 m_AcknowledgementPending[i] = false;
48 m_ThreadedPending[i] = false;
49 m_TransitionPending[i] = false;
50 m_RequestedEnabled[i] = true;
51 m_SchedulerOwned[i] = false;
52 m_PciRouteReserved[i] = false;
53 }
54 }
55
56 bool canRegister(size_t irq, const IrqPolicy& policy, IrqDelivery delivery) const {
57 if (irq >= LineCount || irq == CascadeIrq || m_SchedulerOwned[irq] ||
58 (delivery != IrqDelivery::Hard && delivery != IrqDelivery::Threaded)) {
59 return false;
60 }
61
62 if ((delivery == IrqDelivery::Hard && !policy.validForHard()) ||
63 (delivery == IrqDelivery::Threaded && !policy.validForThreaded())) {
64 return false;
65 }
66
67 const TriggerMode requested =
68 policy.trigger() == IrqTrigger::Edge ? TriggerMode::Edge : TriggerMode::Level;
69 return m_TriggerModes[irq] == TriggerMode::Unconfigured ||
70 (m_TriggerModes[irq] == requested && m_ControllerAck[irq] == policy.controllerAck());
71 }
72
73 bool canRegister(size_t irq, const IrqPolicy& policy) const {
74 return canRegister(irq, policy, legacyDelivery(policy));
75 }
76
77 bool canReservePciRoute(size_t irq) const {
78 return irq < LineCount && (PicElcrProgrammable & bit(irq)) &&
79 canRegister(irq, IrqPolicy::pciIntxThreaded(), IrqDelivery::Threaded);
80 }
81
83 void reservePciRoute(size_t irq) {
84 assert(canReservePciRoute(irq));
85 m_PciRouteReserved[irq] = true;
86 m_TriggerModes[irq] = TriggerMode::Level;
87 m_ControllerAck[irq] = IrqControllerAck::AfterHardStage;
88 if (!handlerCount(irq))
89 m_RequestedEnabled[irq] = false;
90 rebuildMask();
91 }
92
93 bool pciRouteReserved(size_t irq) const {
94 assert(irq < LineCount);
95 return m_PciRouteReserved[irq];
96 }
97
98 bool canRegisterScheduler(size_t irq, const IrqPolicy& policy) const {
99 return irq == 0 && !m_SchedulerOwned[irq] && !handlerCount(irq) &&
100 policy == IrqPolicy::edgeHard();
101 }
102
103 void schedulerRegistered(size_t irq, const IrqPolicy& policy) {
104 assert(canRegisterScheduler(irq, policy));
105 handlerRegistered(irq, policy, IrqDelivery::Hard);
106 m_SchedulerOwned[irq] = true;
107 }
108
109 void schedulerUnregistered(size_t irq) {
110 assert(irq < LineCount && m_SchedulerOwned[irq]);
111 m_SchedulerOwned[irq] = false;
112 handlerUnregistered(irq, IrqDelivery::Hard);
113 }
114
115 bool schedulerRegistered(size_t irq) const {
116 assert(irq < LineCount);
117 return m_SchedulerOwned[irq];
118 }
119
120 void handlerRegistered(size_t irq, const IrqPolicy& policy, IrqDelivery delivery) {
121 assert(canRegister(irq, policy, delivery));
122 const bool firstHandler = handlerCount(irq) == 0;
123 if (m_TriggerModes[irq] == TriggerMode::Unconfigured) {
124 m_TriggerModes[irq] =
125 policy.trigger() == IrqTrigger::Edge ? TriggerMode::Edge : TriggerMode::Level;
126 m_ControllerAck[irq] = policy.controllerAck();
127 }
128 if (delivery == IrqDelivery::Hard) {
129 ++m_HardHandlerCounts[irq];
130 } else {
131 ++m_ThreadedHandlerCounts[irq];
132 }
133 if (firstHandler) {
134 m_AcknowledgementPending[irq] = false;
135 m_ThreadedPending[irq] = false;
136 m_AcknowledgedGenerations[irq] = m_DispatchGenerations[irq];
137 m_RequestedEnabled[irq] = true;
138 rebuildMask();
139 }
140 }
141
142 void handlerRegistered(size_t irq, const IrqPolicy& policy) {
143 handlerRegistered(irq, policy, legacyDelivery(policy));
144 }
145
146 void handlerUnregistered(size_t irq, IrqDelivery delivery) {
147 assert(irq < LineCount);
148 assert(delivery == IrqDelivery::Hard || delivery == IrqDelivery::Threaded);
149 if (delivery == IrqDelivery::Hard) {
150 assert(m_HardHandlerCounts[irq]);
151 --m_HardHandlerCounts[irq];
152 } else {
153 assert(m_ThreadedHandlerCounts[irq]);
154 --m_ThreadedHandlerCounts[irq];
155 if (!m_ThreadedHandlerCounts[irq] && m_ThreadedPending[irq]) {
156 m_ThreadedPending[irq] = false;
157 rebuildMask();
158 }
159 }
160
161 if (!handlerCount(irq)) {
162 m_AcknowledgementPending[irq] = false;
163 m_ThreadedPending[irq] = false;
164 m_AcknowledgedGenerations[irq] = m_DispatchGenerations[irq];
165 m_RequestedEnabled[irq] = false;
166 m_TriggerModes[irq] =
167 m_PciRouteReserved[irq] ? TriggerMode::Level : TriggerMode::Unconfigured;
168 m_ControllerAck[irq] = IrqControllerAck::AfterHardStage;
169 rebuildMask();
170 }
171 }
172
173 void handlerUnregistered(size_t irq) {
174 assert(irq < LineCount);
175 assert(!m_HardHandlerCounts[irq] || !m_ThreadedHandlerCounts[irq]);
176 handlerUnregistered(irq,
177 m_ThreadedHandlerCounts[irq] ? IrqDelivery::Threaded : IrqDelivery::Hard);
178 }
179
180 size_t beginDispatch(size_t irq) {
181 assert(irq < LineCount);
182 size_t generation = ++m_DispatchGenerations[irq];
183 if (!generation) {
184 generation = ++m_DispatchGenerations[irq];
185 }
186 return generation;
187 }
188
194 void completeDispatch(size_t irq, size_t dispatchGeneration, bool needsAcknowledgement) {
195 assert(irq < LineCount);
196 if (!needsAcknowledgement || !handlerCount(irq) ||
197 generationReached(m_AcknowledgedGenerations[irq], dispatchGeneration)) {
198 return;
199 }
200
201 m_AcknowledgementPending[irq] = true;
202 rebuildMask();
203 }
204
205 bool acknowledge(size_t irq) {
206 assert(irq < LineCount);
207 if (!handlerCount(irq)) {
208 return false;
209 }
210
211 m_AcknowledgedGenerations[irq] = m_DispatchGenerations[irq];
212 if (m_AcknowledgementPending[irq]) {
213 m_AcknowledgementPending[irq] = false;
214 rebuildMask();
215 }
216 return true;
217 }
218
220 void beginThreadedDispatch(size_t irq) {
221 assert(irq < LineCount);
222 if (lineRelease(irq) == IrqLineRelease::AfterThreadedCompletion) {
223 m_ThreadedPending[irq] = true;
224 rebuildMask();
225 }
226 }
227
232 bool completeThreadedDispatch(size_t irq, size_t dispatchGeneration, bool allowRearm) {
233 assert(irq < LineCount);
234 if (m_DispatchGenerations[irq] != dispatchGeneration) {
235 return false;
236 }
237
238 if (lineRelease(irq) == IrqLineRelease::AfterThreadedCompletion &&
239 (allowRearm || !handlerCount(irq))) {
240 m_ThreadedPending[irq] = false;
241 rebuildMask();
242 }
243 return true;
244 }
245
246 bool threadedPending(size_t irq) const {
247 assert(irq < LineCount);
248 return m_ThreadedPending[irq];
249 }
250
251 bool acknowledgementPending(size_t irq) const {
252 assert(irq < LineCount);
253 return m_AcknowledgementPending[irq];
254 }
255
256 void beginLineTransition(size_t irq) {
257 assert(irq < LineCount);
258 m_TransitionPending[irq] = true;
259 rebuildMask();
260 }
261
262 void finishLineTransition(size_t irq) {
263 assert(irq < LineCount);
264 m_TransitionPending[irq] = false;
265 rebuildMask();
266 }
267
268 bool lineTransitionPending(size_t irq) const {
269 assert(irq < LineCount);
270 return m_TransitionPending[irq];
271 }
272
273 size_t handlerCount(size_t irq) const {
274 assert(irq < LineCount);
275 return m_HardHandlerCounts[irq] + m_ThreadedHandlerCounts[irq];
276 }
277
278 size_t hardHandlerCount(size_t irq) const {
279 assert(irq < LineCount);
280 return m_HardHandlerCounts[irq];
281 }
282
283 size_t threadedHandlerCount(size_t irq) const {
284 assert(irq < LineCount);
285 return m_ThreadedHandlerCounts[irq];
286 }
287
288 IrqDelivery delivery(size_t irq) const {
289 assert(irq < LineCount);
290 const bool hard = m_HardHandlerCounts[irq] != 0;
291 const bool threaded = m_ThreadedHandlerCounts[irq] != 0;
292 if (hard && threaded) {
293 return IrqDelivery::Mixed;
294 }
295 if (hard) {
296 return IrqDelivery::Hard;
297 }
298 if (threaded) {
299 return IrqDelivery::Threaded;
300 }
301 return IrqDelivery::None;
302 }
303
304 bool edgeTriggered(size_t irq) const {
305 assert(irq < LineCount);
306 return m_TriggerModes[irq] == TriggerMode::Edge;
307 }
308
309 IrqTrigger trigger(size_t irq) const {
310 assert(irq < LineCount);
311 return m_TriggerModes[irq] == TriggerMode::Edge ? IrqTrigger::Edge : IrqTrigger::Level;
312 }
313
314 IrqControllerAck controllerAck(size_t irq) const {
315 assert(irq < LineCount);
316 return m_ControllerAck[irq];
317 }
318
319 IrqLineRelease lineRelease(size_t irq) const {
320 assert(irq < LineCount);
321 return m_TriggerModes[irq] == TriggerMode::Level && m_ThreadedHandlerCounts[irq]
322 ? IrqLineRelease::AfterThreadedCompletion
323 : IrqLineRelease::AfterHardStage;
324 }
325
326 bool enabled(size_t irq) const {
327 assert(irq < LineCount);
328 return (m_Mask & bit(irq)) == 0;
329 }
330
331 bool requestedEnabled(size_t irq) const {
332 assert(irq < LineCount);
333 return m_RequestedEnabled[irq];
334 }
335
336 size_t dispatchGeneration(size_t irq) const {
337 assert(irq < LineCount);
338 return m_DispatchGenerations[irq];
339 }
340
341 size_t acknowledgedGeneration(size_t irq) const {
342 assert(irq < LineCount);
343 return m_AcknowledgedGenerations[irq];
344 }
345
346 void setEnabled(size_t irq, bool enabled) {
347 assert(irq < LineCount);
348 m_RequestedEnabled[irq] = enabled;
349 rebuildMask();
350 }
351
352 void setAllEnabled(bool enabled) {
353 for (size_t i = 0; i < LineCount; ++i) {
354 m_RequestedEnabled[i] = enabled;
355 }
356
357 // IRQ2 is the cascade input and must remain available when device
358 // lines are masked, or every slave IRQ becomes unreachable.
359 if (!enabled) {
360 m_RequestedEnabled[2] = true;
361 }
362 rebuildMask();
363 }
364
365 uint16_t mask() const {
366 return m_Mask;
367 }
368
369 uint8_t masterMask() const {
370 return static_cast<uint8_t>(m_Mask & 0xFF);
371 }
372
373 uint8_t slaveMask() const {
374 return static_cast<uint8_t>(m_Mask >> 8);
375 }
376
377 private:
378 static IrqDelivery legacyDelivery(const IrqPolicy& policy) {
379 if (policy.lineRelease() == IrqLineRelease::AfterThreadedCompletion ||
380 (policy.trigger() == IrqTrigger::Edge &&
381 policy.controllerAck() == IrqControllerAck::AfterHardStage)) {
382 return IrqDelivery::Threaded;
383 }
384 return IrqDelivery::Hard;
385 }
386
387 static uint16_t bit(size_t irq) {
388 return static_cast<uint16_t>(static_cast<uint16_t>(1U) << irq);
389 }
390
391 static bool generationReached(size_t current, size_t target) {
392 return static_cast<intptr_t>(current - target) >= 0;
393 }
394
395 void rebuildMask() {
396 uint16_t mask = 0;
397 for (size_t i = 0; i < LineCount; ++i) {
398 if (!m_RequestedEnabled[i] || m_AcknowledgementPending[i] || m_ThreadedPending[i] ||
399 m_TransitionPending[i] || (m_PciRouteReserved[i] && !handlerCount(i))) {
400 mask |= bit(i);
401 }
402 }
403
404 // The master IRQ2 bit represents both a direct IRQ2 source and every
405 // slave line, so a live slave always wins over a direct-line mask.
406 if ((mask & static_cast<uint16_t>(0xFF00)) != static_cast<uint16_t>(0xFF00)) {
407 mask &= static_cast<uint16_t>(~bit(2));
408 }
409 m_Mask = mask;
410 }
411
412 uint16_t m_Mask;
413 TriggerMode m_TriggerModes[LineCount];
414 IrqControllerAck m_ControllerAck[LineCount];
415 size_t m_HardHandlerCounts[LineCount];
416 size_t m_ThreadedHandlerCounts[LineCount];
417 size_t m_DispatchGenerations[LineCount];
418 size_t m_AcknowledgedGenerations[LineCount];
419 bool m_AcknowledgementPending[LineCount];
420 bool m_ThreadedPending[LineCount];
421 bool m_TransitionPending[LineCount];
422 bool m_RequestedEnabled[LineCount];
423 bool m_SchedulerOwned[LineCount];
424 bool m_PciRouteReserved[LineCount];
425};
426
428 bool terminalWork;
429 bool schedulerDrop;
430 bool quarantine;
431 bool invalidateThreaded;
432 bool maskChanged;
433 size_t threadedOccurrences;
434 size_t unhandledOccurrences;
435};
436
438inline PicContentionLineResult resolvePicContentionLine(PicIrqState& state, size_t irq,
439 size_t realEntries, size_t deferredTails,
440 size_t deferredTailEois) {
441 assert(irq < PicIrqState::LineCount);
442
443 PicContentionLineResult result = {};
444 const uint16_t maskBefore = state.mask();
445 const bool hasTailAction = deferredTails || deferredTailEois;
446 result.terminalWork = realEntries || hasTailAction;
447 if (!result.terminalWork) {
448 return result;
449 }
450
451 if (irq == 0 && state.schedulerRegistered(0) && !hasTailAction) {
452 result.schedulerDrop = true;
453 result.unhandledOccurrences = realEntries;
454 for (size_t occurrence = 0; occurrence < realEntries; ++occurrence) {
455 const size_t generation = state.beginDispatch(irq);
456 state.acknowledge(irq);
457 state.completeDispatch(irq, generation, false);
458 }
459 result.maskChanged = state.mask() != maskBefore;
460 return result;
461 }
462
463 const IrqDelivery delivery = state.delivery(irq);
464 if (realEntries && !hasTailAction && delivery == IrqDelivery::Threaded) {
465 // The gate retained a current-lifetime occurrence, not an interrupted
466 // frame. A threaded-only line can therefore run its ordinary
467 // admission/publication sequence when the owner drains this batch.
468 result.threadedOccurrences = realEntries;
469 return result;
470 }
471
472 result.quarantine = true;
473 result.unhandledOccurrences = realEntries + deferredTails;
474 result.invalidateThreaded = delivery == IrqDelivery::Threaded || delivery == IrqDelivery::Mixed;
475 const size_t generation = state.beginDispatch(irq);
476 state.completeDispatch(irq, generation, true);
477 result.maskChanged = state.mask() != maskBefore;
478 return result;
479}
480
490 public:
491 static constexpr size_t LineCount = PicIrqState::LineCount;
492 static constexpr size_t CountBits = 12;
493 static constexpr size_t CountMask = (static_cast<size_t>(1) << CountBits) - 1;
494 static constexpr size_t LifetimeMask = static_cast<size_t>(~static_cast<size_t>(0)) >> CountBits;
495 static constexpr size_t TransitionLifetime = LifetimeMask;
496
497 static_assert(sizeof(size_t) >= sizeof(uint64_t),
498 "PIC lifetime-qualified handoff requires lock-free 64-bit words");
499 static_assert(__atomic_always_lock_free(sizeof(size_t), nullptr),
500 "PIC controller handoff words must be lock-free");
501
504 clear();
505 }
506
507 void clear() {
508 for (size_t i = 0; i < LineCount; ++i) {
509 entry[i] = 0;
510 tail[i] = 0;
511 tailEoi[i] = 0;
512 staleEntry[i] = 0;
513 staleTail[i] = 0;
514 staleTailEoi[i] = 0;
515 }
516 }
517
518 bool empty() const {
519 for (size_t i = 0; i < LineCount; ++i) {
520 if (entry[i] || tail[i] || tailEoi[i] || staleEntry[i] || staleTail[i] || staleTailEoi[i]) {
521 return false;
522 }
523 }
524 return true;
525 }
526
527 size_t entry[LineCount];
528 size_t tail[LineCount];
529 size_t tailEoi[LineCount];
530 size_t staleEntry[LineCount];
531 size_t staleTail[LineCount];
532 size_t staleTailEoi[LineCount];
533 };
534
535 PicControllerStateGate() : m_State(Clean) {
536 for (size_t i = 0; i < LineCount; ++i) {
537 m_LifetimeGenerations[i] = 1;
538 m_LineLifetimes[i] = 1;
539 m_EntryCounts[i] = tagged(1, 0);
540 m_TailNoEoiCounts[i] = tagged(1, 0);
541 m_TailEoiCounts[i] = tagged(1, 0);
542 m_StaleEntryCounts[i] = 0;
543 m_StaleTailNoEoiCounts[i] = 0;
544 m_StaleTailEoiCounts[i] = 0;
545 }
546 }
547
550 size_t expected = Clean;
551 return __atomic_compare_exchange_n(&m_State, &expected, Owner, false, __ATOMIC_ACQUIRE,
552 __ATOMIC_RELAXED);
553 }
554
556 bool tryClaim() {
557 const size_t state = __atomic_load_n(&m_State, __ATOMIC_ACQUIRE);
558 if (state & Owner) {
559 return false;
560 }
561
562 size_t expected = state;
563 return __atomic_compare_exchange_n(&m_State, &expected, state | Owner, false, __ATOMIC_ACQ_REL,
564 __ATOMIC_RELAXED);
565 }
566
567 size_t currentLifetime(size_t irq) const {
568 assert(irq < LineCount);
569 return __atomic_load_n(&m_LineLifetimes[irq], __ATOMIC_ACQUIRE);
570 }
571
573 void beginLineTransition(size_t irq) {
574 assert(irq < LineCount);
575 replaceLifetime(irq, TransitionLifetime);
576 }
577
579 size_t finishLineTransition(size_t irq) {
580 assert(irq < LineCount);
581 size_t lifetime = ++m_LifetimeGenerations[irq];
582 lifetime &= LifetimeMask;
583 if (!lifetime || lifetime == TransitionLifetime) {
584 lifetime = 1;
585 m_LifetimeGenerations[irq] = lifetime;
586 }
587 replaceLifetime(irq, lifetime);
588 return lifetime;
589 }
590
592 bool queueEntry(size_t irq, size_t lifetime) {
593 assert(irq < LineCount);
594 queueTagged(m_EntryCounts[irq], m_StaleEntryCounts[irq], lifetime);
595 publishPending(irq == 0);
596 return tryClaim();
597 }
598
600 bool queueTail(size_t irq, size_t lifetime, bool owesEoi) {
601 assert(irq < LineCount);
602 if (owesEoi) {
603 queueTagged(m_TailEoiCounts[irq], m_StaleTailEoiCounts[irq], lifetime);
604 } else {
605 queueTagged(m_TailNoEoiCounts[irq], m_StaleTailNoEoiCounts[irq], lifetime);
606 }
607 publishPending(irq == 0);
608 return tryClaim();
609 }
610
612 bool queueTailRecord(size_t irq) {
613 assert(irq < LineCount);
614 publishPending(irq == 0);
615 return tryClaim();
616 }
617
620 actions.clear();
621 size_t expected = __atomic_load_n(&m_State, __ATOMIC_ACQUIRE);
622 do {
623 if ((expected & (Owner | Pending)) != (Owner | Pending)) {
624 return false;
625 }
626 } while (!__atomic_compare_exchange_n(&m_State, &expected, Owner, false, __ATOMIC_ACQ_REL,
627 __ATOMIC_ACQUIRE));
628
629 for (size_t i = 0; i < LineCount; ++i) {
630 const size_t lifetime = currentLifetime(i);
631 takeTagged(m_EntryCounts[i], m_StaleEntryCounts[i], lifetime, actions.entry[i],
632 actions.staleEntry[i]);
633
634 size_t tailNoEoi = 0;
635 size_t staleTailNoEoi = 0;
636 takeTagged(m_TailNoEoiCounts[i], m_StaleTailNoEoiCounts[i], lifetime, tailNoEoi,
637 staleTailNoEoi);
638 takeTagged(m_TailEoiCounts[i], m_StaleTailEoiCounts[i], lifetime, actions.tailEoi[i],
639 actions.staleTailEoi[i]);
640 actions.tail[i] = tailNoEoi + actions.tailEoi[i];
641 actions.staleTail[i] = staleTailNoEoi + actions.staleTailEoi[i];
642 }
643 return true;
644 }
645
648 size_t expected = Owner;
649 return __atomic_compare_exchange_n(&m_State, &expected, Clean, false, __ATOMIC_RELEASE,
650 __ATOMIC_RELAXED);
651 }
652
653 bool hasPending() const {
654 return (__atomic_load_n(&m_State, __ATOMIC_ACQUIRE) & Pending) != 0;
655 }
656
657 bool urgentPending() const {
658 return (__atomic_load_n(&m_State, __ATOMIC_ACQUIRE) & Urgent) != 0;
659 }
660
663 publishPending(false);
664 }
665
668 size_t expected = Owner | Pending;
669 return __atomic_compare_exchange_n(&m_State, &expected, Pending, false, __ATOMIC_RELEASE,
670 __ATOMIC_ACQUIRE);
671 }
672
673 private:
674 static constexpr size_t Clean = 0;
675 static constexpr size_t Owner = 1U << 0;
676 static constexpr size_t Pending = 1U << 1;
677 static constexpr size_t Urgent = 1U << 2;
678
679 static size_t tagged(size_t lifetime, size_t count) {
680 return ((lifetime & LifetimeMask) << CountBits) | (count & CountMask);
681 }
682
683 static size_t taggedLifetime(size_t value) {
684 return value >> CountBits;
685 }
686
687 static size_t taggedCount(size_t value) {
688 return value & CountMask;
689 }
690
691 static void addStale(size_t& counter, size_t count) {
692 if (count) {
693 __atomic_fetch_add(&counter, count, __ATOMIC_RELAXED);
694 }
695 }
696
697 static void queueTagged(size_t& currentCounter, size_t& staleCounter, size_t lifetime) {
698 const size_t qualifiedLifetime = lifetime & LifetimeMask;
699 if (!qualifiedLifetime || qualifiedLifetime == TransitionLifetime) {
700 addStale(staleCounter, 1);
701 return;
702 }
703
704 size_t expected = __atomic_load_n(&currentCounter, __ATOMIC_ACQUIRE);
705 const size_t count = taggedCount(expected);
706 if (taggedLifetime(expected) != qualifiedLifetime || count == CountMask ||
707 !__atomic_compare_exchange_n(&currentCounter, &expected,
708 tagged(qualifiedLifetime, count + 1), false, __ATOMIC_ACQ_REL,
709 __ATOMIC_ACQUIRE)) {
710 // A lifecycle boundary or competing publication wins over
711 // software quarantine. The stale path still preserves hardware
712 // mask/EOI obligations without touching the replacement lifetime.
713 addStale(staleCounter, 1);
714 }
715 }
716
717 static void takeTagged(size_t& currentCounter, size_t& staleCounter, size_t lifetime,
718 size_t& current, size_t& stale) {
719 const size_t value =
720 __atomic_exchange_n(&currentCounter, tagged(lifetime, 0), __ATOMIC_ACQ_REL);
721 const size_t count = taggedCount(value);
722 if (taggedLifetime(value) == lifetime) {
723 current = count;
724 } else {
725 stale = count;
726 }
727 stale += __atomic_exchange_n(&staleCounter, static_cast<size_t>(0), __ATOMIC_ACQ_REL);
728 }
729
730 void replaceLifetime(size_t irq, size_t lifetime) {
731 size_t moved = 0;
732 size_t value = __atomic_exchange_n(&m_EntryCounts[irq], tagged(lifetime, 0), __ATOMIC_ACQ_REL);
733 moved += taggedCount(value);
734 addStale(m_StaleEntryCounts[irq], taggedCount(value));
735
736 value = __atomic_exchange_n(&m_TailNoEoiCounts[irq], tagged(lifetime, 0), __ATOMIC_ACQ_REL);
737 moved += taggedCount(value);
738 addStale(m_StaleTailNoEoiCounts[irq], taggedCount(value));
739
740 value = __atomic_exchange_n(&m_TailEoiCounts[irq], tagged(lifetime, 0), __ATOMIC_ACQ_REL);
741 moved += taggedCount(value);
742 addStale(m_StaleTailEoiCounts[irq], taggedCount(value));
743
744 __atomic_store_n(&m_LineLifetimes[irq], lifetime, __ATOMIC_RELEASE);
745 if (moved) {
746 publishPending(irq == 0);
747 }
748 }
749
750 void publishPending(bool urgent) {
751 __atomic_fetch_or(&m_State, Pending | (urgent ? Urgent : 0), __ATOMIC_RELEASE);
752 }
753
754 size_t m_State;
755 size_t m_LifetimeGenerations[LineCount];
756 size_t m_LineLifetimes[LineCount];
757 size_t m_EntryCounts[LineCount];
758 size_t m_TailNoEoiCounts[LineCount];
759 size_t m_TailEoiCounts[LineCount];
760 size_t m_StaleEntryCounts[LineCount];
761 size_t m_StaleTailNoEoiCounts[LineCount];
762 size_t m_StaleTailEoiCounts[LineCount];
763
765 PicControllerStateGate& operator=(const PicControllerStateGate&) = delete;
766};
767
770 uint8_t irq;
771 size_t controllerLifetime;
772 size_t dispatchGeneration;
773 size_t hardStageGeneration;
774 size_t threadedCookie;
775 IrqControllerAck controllerAck;
776 HardIrqDisposition hardDisposition;
777 bool hasThreadedStage;
778 bool threadedPublished;
779 bool admitted;
780};
781
784 size_t controllerLifetime;
785 size_t dispatchGeneration;
786 size_t hardStageGeneration;
787 size_t threadedCookie;
788 size_t threadedDispatchGeneration;
789 size_t hardHandlerCount;
790 IrqDelivery delivery;
791 bool hardLineQuarantined;
792};
793
794enum class PicHardTailThreadedAction : uint8_t {
795 None,
796 Publish,
797 MissingPublication,
798 Quiesced,
799 Stale,
800};
801
804 bool controllerLifetimeCurrent;
805 bool hardStageLifetimeCurrent;
806 bool threadedLifetimeCurrent;
807 HardIrqDisposition effectiveHardDisposition;
808 PicHardTailThreadedAction threadedAction;
809};
810
811inline PicHardTailPlan resolvePicHardTail(const PicHardTailRecord& record,
812 const PicHardTailCurrentState& current) {
813 PicHardTailPlan plan = {};
814 plan.controllerLifetimeCurrent = record.controllerLifetime == current.controllerLifetime;
815 plan.hardStageLifetimeCurrent = record.hardStageGeneration == current.hardStageGeneration;
816 plan.threadedLifetimeCurrent =
817 record.hasThreadedStage && record.threadedCookie == current.threadedCookie &&
818 record.dispatchGeneration == current.threadedDispatchGeneration &&
819 (current.delivery == IrqDelivery::Threaded || current.delivery == IrqDelivery::Mixed);
820 plan.effectiveHardDisposition =
821 !plan.hardStageLifetimeCurrent ||
822 (record.hasThreadedStage && current.delivery == IrqDelivery::Threaded) ||
823 (record.hardDisposition == HardIrqDisposition::KeepMasked &&
824 !current.hardLineQuarantined)
825 ? HardIrqDisposition::Handled
826 : record.hardDisposition;
827
828 if (!record.hasThreadedStage) {
829 plan.threadedAction = PicHardTailThreadedAction::None;
830 } else if (plan.threadedLifetimeCurrent) {
831 plan.threadedAction = record.threadedPublished ? PicHardTailThreadedAction::Publish
832 : PicHardTailThreadedAction::MissingPublication;
833 } else if (record.dispatchGeneration == current.dispatchGeneration && current.hardHandlerCount &&
834 (current.delivery == IrqDelivery::Hard || current.delivery == IrqDelivery::Mixed)) {
835 plan.threadedAction = PicHardTailThreadedAction::Quiesced;
836 } else {
837 plan.threadedAction = PicHardTailThreadedAction::Stale;
838 }
839 return plan;
840}
841
843 bool published;
844 bool invalidateStagedDispatch;
845 bool quarantine;
846 bool completeDispatch;
847};
848
850inline PicHardTailDoorbellResult resolvePicHardTailDoorbell(bool staged, bool accepted) {
851 PicHardTailDoorbellResult result = {};
852 result.published = staged && accepted;
853 result.invalidateStagedDispatch = staged && !accepted;
854 result.quarantine = result.invalidateStagedDispatch;
855 result.completeDispatch = !result.published;
856 return result;
857}
858
861 public:
862 PicHardTailTerminalSequence(bool temporaryMaskRequired, IrqControllerAck controllerAck)
863 : m_TemporaryMaskRequired(temporaryMaskRequired),
864 m_TemporaryMaskApplied(false),
865 m_ControllerAck(controllerAck),
866 m_Acknowledged(false) {}
867
868 template <typename Sink>
869 void applyTemporaryMask(Sink&& sink) {
870 if (!m_TemporaryMaskRequired) {
871 return;
872 }
873 sink();
874 m_TemporaryMaskApplied = true;
875 }
876
877 template <typename Sink>
878 void acknowledge(Sink&& sink) {
879 assert(!m_Acknowledged);
880 if (m_ControllerAck == IrqControllerAck::AfterHardStage) {
881 assert(!m_TemporaryMaskRequired || m_TemporaryMaskApplied);
882 sink();
883 }
884 m_Acknowledged = true;
885 }
886
887 private:
888 bool m_TemporaryMaskRequired;
889 bool m_TemporaryMaskApplied;
890 IrqControllerAck m_ControllerAck;
891 bool m_Acknowledged;
892};
893
904 public:
905 static constexpr size_t LineCount = PicIrqState::LineCount;
906
907 PicHardTailQueue() : m_States(), m_Records() {
908 for (size_t irq = 0; irq < LineCount; ++irq) {
909 __atomic_store_n(&m_States[irq], Empty, __ATOMIC_RELAXED);
910 }
911 }
912
913 bool publish(uint8_t irq, const PicHardTailRecord& record) {
914 if (irq >= LineCount || record.irq != irq) {
915 return false;
916 }
917
918 size_t expected = Empty;
919 if (!__atomic_compare_exchange_n(&m_States[irq], &expected, Claimed, false, __ATOMIC_ACQ_REL,
920 __ATOMIC_ACQUIRE)) {
921 return false;
922 }
923 m_Records[irq] = record;
924 __atomic_store_n(&m_States[irq], Ready, __ATOMIC_RELEASE);
925 return true;
926 }
927
928 bool take(uint8_t irq, PicHardTailRecord& record) {
929 if (irq >= LineCount) {
930 return false;
931 }
932
933 size_t expected = Ready;
934 if (!__atomic_compare_exchange_n(&m_States[irq], &expected, Claimed, false, __ATOMIC_ACQUIRE,
935 __ATOMIC_RELAXED)) {
936 return false;
937 }
938 record = m_Records[irq];
939 return true;
940 }
941
942 void complete(uint8_t irq) {
943 assert(irq < LineCount);
944 size_t expected = Claimed;
945 const bool completed = __atomic_compare_exchange_n(&m_States[irq], &expected, Empty, false,
946 __ATOMIC_RELEASE, __ATOMIC_RELAXED);
947 assert(completed);
948 }
949
951 template <typename Finalizer>
952 bool consume(uint8_t irq, Finalizer&& finalizer) {
953 PicHardTailRecord record = {};
954 if (!take(irq, record)) {
955 return false;
956 }
957 finalizer(record);
958 complete(irq);
959 return true;
960 }
961
962 bool pending(uint8_t irq) const {
963 return irq < LineCount && __atomic_load_n(&m_States[irq], __ATOMIC_ACQUIRE) == Ready;
964 }
965
966 private:
967 static constexpr size_t Empty = 0;
968 static constexpr size_t Claimed = 1;
969 static constexpr size_t Ready = 2;
970
971 size_t m_States[LineCount];
972 PicHardTailRecord m_Records[LineCount];
973
974 PicHardTailQueue(const PicHardTailQueue&) = delete;
975 PicHardTailQueue& operator=(const PicHardTailQueue&) = delete;
976};
977
978inline uint16_t effectivePicMask(uint16_t canonical, uint16_t temporary) {
979 return canonical | temporary;
980}
981
983inline uint16_t preserveUnsafePicUnmasks(uint16_t applied, uint16_t canonical, uint16_t temporary,
984 uint16_t deferredTransitions,
985 bool canPublishBeforeRelease) {
986 if (!canPublishBeforeRelease) {
987 const uint16_t newlyUnmasked =
988 static_cast<uint16_t>(applied & ~effectivePicMask(canonical, temporary));
989 temporary |= static_cast<uint16_t>(newlyUnmasked & deferredTransitions);
990 }
991 return temporary;
992}
993
995inline uint16_t restorablePicTemporaryMask(uint16_t temporary, uint16_t deferredTransitions) {
996 return static_cast<uint16_t>(temporary & ~deferredTransitions);
997}
998
999inline uint16_t cancelDeferredPicLineTransition(uint16_t deferred, size_t irq) {
1000 assert(irq < PicIrqState::LineCount);
1001 return static_cast<uint16_t>(deferred & ~static_cast<uint16_t>(1U << irq));
1002}
1003
1005inline uint16_t temporaryPicMaskForDeferredWork(
1006 uint16_t existing, const size_t (&realEntries)[PicIrqState::LineCount],
1008 const size_t (&spuriousEntries)[PicIrqState::LineCount]) {
1009 uint16_t result = existing;
1010 for (size_t irq = 0; irq < PicIrqState::LineCount; ++irq) {
1011 if (realEntries[irq] || actions.tailEoi[irq] || spuriousEntries[irq]) {
1012 result |= static_cast<uint16_t>(1U << irq);
1013 }
1014 }
1015 return result;
1016}
1017
1018enum class PicControllerWriteTarget : uint8_t {
1019 MasterCommand,
1020 MasterMask,
1021 SlaveCommand,
1022 SlaveMask,
1023};
1024
1025template <typename Sink>
1026void emitPicMaskWrites(uint16_t mask, Sink&& sink) {
1027 sink(PicControllerWriteTarget::MasterMask, static_cast<uint8_t>(mask & 0xFF));
1028 sink(PicControllerWriteTarget::SlaveMask, static_cast<uint8_t>(mask >> 8));
1029}
1030
1032template <typename Sink>
1033void emitPicContentionWrites(const PicIrqState& state, bool writeMask,
1034 const size_t (&realEntries)[PicIrqState::LineCount],
1036 size_t spuriousCascadeEois, Sink&& sink) {
1037 if (writeMask) {
1038 sink(PicControllerWriteTarget::MasterMask, state.masterMask());
1039 sink(PicControllerWriteTarget::SlaveMask, state.slaveMask());
1040 }
1041
1042 for (size_t irq = 0; irq < PicIrqState::LineCount; ++irq) {
1043 const size_t owedEois = realEntries[irq] + pending.tailEoi[irq];
1044 for (size_t occurrence = 0; occurrence < owedEois; ++occurrence) {
1045 if (irq > 7) {
1046 sink(PicControllerWriteTarget::SlaveCommand, static_cast<uint8_t>(0x60 + (irq - 8)));
1047 sink(PicControllerWriteTarget::MasterCommand, 0x62);
1048 } else {
1049 sink(PicControllerWriteTarget::MasterCommand, static_cast<uint8_t>(0x60 + irq));
1050 }
1051 }
1052 }
1053
1054 for (size_t occurrence = 0; occurrence < spuriousCascadeEois; ++occurrence) {
1055 sink(PicControllerWriteTarget::MasterCommand, 0x62);
1056 }
1057}
1058
1059#endif
bool relinquishOwnerForContinuation()
bool queueEntry(size_t irq, size_t lifetime)
size_t finishLineTransition(size_t irq)
bool takePending(PendingActions &actions)
void beginLineTransition(size_t irq)
bool queueTail(size_t irq, size_t lifetime, bool owesEoi)
bool queueTailRecord(size_t irq)
bool consume(uint8_t irq, Finalizer &&finalizer)
void beginThreadedDispatch(size_t irq)
bool completeThreadedDispatch(size_t irq, size_t dispatchGeneration, bool allowRearm)
void reservePciRoute(size_t irq)
Definition PicIrqState.h:83
void completeDispatch(size_t irq, size_t dispatchGeneration, bool needsAcknowledgement)
HardIrqDisposition
Definition IrqHandler.h:44