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