The Pedigree Project 0.1
SplitIrqHandler.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/LockGuard.h"
9#include "pedigree/kernel/Log.h"
10#include "pedigree/kernel/machine/Device.h"
11#include "pedigree/kernel/machine/IrqManager.h"
12#include "pedigree/kernel/machine/SplitIrqHandler.h"
13#include "pedigree/kernel/process/TerminationDeferral.h"
14#include "pedigree/kernel/process/Thread.h"
15#include "pedigree/kernel/processor/Processor.h"
16#include "pedigree/kernel/processor/ProcessorInformation.h"
17#include "pedigree/kernel/utilities/String.h"
18
19namespace {
20class SplitLifecycleGuard {
21 public:
22 explicit SplitLifecycleGuard(Atomic<size_t>& busy)
23 : m_Busy(busy), m_Owned(m_Busy.compareAndSwap(0, 1)) {}
24
25 ~SplitLifecycleGuard() {
26 if (m_Owned) {
27 m_Busy = 0;
28 }
29 }
30
31 bool owned() const {
32 return m_Owned;
33 }
34
35 private:
36 Atomic<size_t>& m_Busy;
37 bool m_Owned;
38};
39} // namespace
40
41SplitIrqHandler::SplitIrqHandler(const String& name)
43 m_Registrations(),
44 m_RegistrationCount(0),
45 m_LifecycleBusy(0),
46 m_AcceptingRegistrations(0),
47 m_Dispatcher(name, 1, dispatchThreaded, this),
48 m_StateLock(false),
49 m_Quiescing(true),
50 m_Stopping(1),
51 m_PublicationFailures(0),
52 m_DeferredIrqs(0),
53 m_CompletedBatches(0),
54 m_PendingWork(0),
55 m_Started(false)
56#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
57 ,
58 m_RegistrationPublishedHook(nullptr)
59#endif
60{
61}
62
63SplitIrqHandler::~SplitIrqHandler() {
64 if (m_Started || m_RegistrationCount || m_LifecycleBusy || m_AcceptingRegistrations ||
65 m_Dispatcher.isInitialised() || __atomic_load_n(&m_PendingWork, __ATOMIC_ACQUIRE)) {
66 FATAL(
67 "A split IRQ handler reached its base destructor while "
68 "active; the most-derived destructor must disable its device "
69 "source and call shutdownSplitIrq().");
70 }
71}
72
74#if THREADS
75 TerminationDeferral lifecycleTermination;
76 SplitLifecycleGuard lifecycle(m_LifecycleBusy);
77 if (!lifecycle.owned()) {
78 return false;
79 }
80 if (m_Started || m_RegistrationCount) {
81 return false;
82 }
83
84 __atomic_store_n(&m_PendingWork, static_cast<size_t>(0), __ATOMIC_RELEASE);
85 m_PublicationFailures = 0;
86 m_DeferredIrqs = 0;
87 m_CompletedBatches = 0;
88 m_Stopping = 1;
89 if (!m_Dispatcher.initialise()) {
90 return false;
91 }
92 {
93 LockGuard<Spinlock> guard(m_StateLock);
94 m_Quiescing = false;
95 }
96 m_Stopping = 0;
97 m_Started = true;
98 m_AcceptingRegistrations = 1;
99 return true;
100#else
101 return false;
102#endif
103}
104
106 const IrqPolicy& policy) {
107 TerminationDeferral lifecycleTermination;
108 SplitLifecycleGuard lifecycle(m_LifecycleBusy);
109 if (!lifecycle.owned() || !m_AcceptingRegistrations || !m_Started || m_Stopping ||
110 m_RegistrationCount >= MaxRegistrations) {
111 return 0;
112 }
113
114 const irq_id_t id = manager.registerHardIsaIrqHandler(irq, this, policy);
115 if (!id) {
116 return 0;
117 }
118
119#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
120 if (m_RegistrationPublishedHook) {
121 m_RegistrationPublishedHook(this);
122 }
123#endif
124
125 Registration& registration = m_Registrations[m_RegistrationCount++];
126 registration.manager = &manager;
127 registration.id = id;
128 return id;
129}
130
131irq_id_t SplitIrqHandler::registerPciSplitIrq(IrqManager& manager, Device& device,
132 const IrqPolicy& policy) {
133 TerminationDeferral lifecycleTermination;
134 SplitLifecycleGuard lifecycle(m_LifecycleBusy);
135 if (!lifecycle.owned() || !m_AcceptingRegistrations || !m_Started || m_Stopping ||
136 m_RegistrationCount >= MaxRegistrations) {
137 return 0;
138 }
139
140 const irq_id_t id = manager.registerHardPciIrqHandler(this, &device, policy);
141 if (!id) {
142 return 0;
143 }
144
145#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
146 if (m_RegistrationPublishedHook) {
147 m_RegistrationPublishedHook(this);
148 }
149#endif
150
151 Registration& registration = m_Registrations[m_RegistrationCount++];
152 registration.manager = &manager;
153 registration.id = id;
154 return id;
155}
156
158#if THREADS
159 Thread* current = Processor::information().getCurrentThread();
160 if (!current || !Processor::getInterrupts()) {
161 return false;
162 }
163#if HOSTED
164 if (current->getHostedSignalDepth()) {
165 return false;
166 }
167#endif
168
169 // Derived quiesce operations and callback drains may reach interruptible
170 // waits. Keep teardown alive until the lifecycle token has been released;
171 // declaration order makes the guard unwind before termination is enabled.
172 TerminationDeferral lifecycleTermination;
173 SplitLifecycleGuard lifecycle(m_LifecycleBusy);
174 if (!lifecycle.owned()) {
175 return false;
176 }
177
178 if (!m_Started) {
179 return m_RegistrationCount == 0 && !m_AcceptingRegistrations && !m_Dispatcher.isInitialised() &&
180 !__atomic_load_n(&m_PendingWork, __ATOMIC_ACQUIRE);
181 }
182
183 if (m_Dispatcher.isCurrentWorker()) {
184 return false;
185 }
186
187 // Once teardown has won lifecycle ownership, failed retries may continue
188 // but no later registration can reopen a source on the draining worker.
189 m_AcceptingRegistrations = 0;
190#endif
191
192 // A bottom half which wins m_StateLock first may rearm, but hardware
193 // quiescence then masks it. A bottom half which loses observes the state
194 // transition and cannot rearm after the hardware has been masked.
195 {
196 LockGuard<Spinlock> guard(m_StateLock);
197 m_Quiescing = true;
198 }
199 if (!quiesceIrqSources()) {
200 return false;
201 }
202
203 // Keep dispatcher admission open until unregister has drained hard
204 // callbacks admitted before device quiescence became visible.
205 while (m_RegistrationCount) {
206 Registration& registration = m_Registrations[m_RegistrationCount - 1];
207 if (!registration.manager || !registration.id ||
208 !registration.manager->unregisterHandler(registration.id, this)) {
209 // A retry remains safe. Successfully removed registrations stay
210 // closed, while any remaining admitted callback can still publish.
211 return false;
212 }
213
214 registration.manager = nullptr;
215 registration.id = 0;
216 --m_RegistrationCount;
217 }
218
219 m_Stopping = 1;
220 if (!m_Dispatcher.shutdown()) {
221 return false;
222 }
223
224 // A failed hard publication records work before its doorbell is rejected.
225 // Once hardware is quiesced and every hard callback is drained, no new
226 // producer can race this ordinary-context orphan drain.
227 if (__atomic_load_n(&m_PendingWork, __ATOMIC_ACQUIRE)) {
228 dispatchThreaded();
229 }
230
231 // An admitted hard callback may have run after the first quiesce while
232 // unregister waited for its callback pin. Reassert the device boundary
233 // after all work published by those callbacks has drained.
234 if (!quiesceIrqSources()) {
235 return false;
236 }
237
238 if (__atomic_load_n(&m_PendingWork, __ATOMIC_ACQUIRE)) {
239 return false;
240 }
241
242 m_Started = false;
243 return true;
244}
245
246HardIrqDisposition SplitIrqHandler::irq(irq_id_t number, InterruptState& state) {
247 size_t work = 0;
248 const HardStageDisposition disposition = hardIrq(number, state, work);
249 if (disposition == HardStageDisposition::NotHandled) {
250 return HardIrqDisposition::NotHandled;
251 }
252 if (disposition == HardStageDisposition::Deferred) {
253 return publishWork(work ? work : 1) ? HardIrqDisposition::Handled
255 }
256 return HardIrqDisposition::Handled;
257}
258
259void SplitIrqHandler::dispatchThreaded(void* context, uint8_t, size_t) {
260 reinterpret_cast<SplitIrqHandler*>(context)->dispatchThreaded();
261}
262
263void SplitIrqHandler::dispatchThreaded() {
264 const size_t work = __atomic_exchange_n(&m_PendingWork, static_cast<size_t>(0), __ATOMIC_ACQ_REL);
265 if (work) {
266 threadedIrq(work);
267 {
268 LockGuard<Spinlock> guard(m_StateLock);
269 if (!m_Quiescing) {
270 rearmIrqSources(work);
271 }
272 }
273 m_CompletedBatches += 1;
274 }
275}
276
277bool SplitIrqHandler::publishWork(size_t work) {
278 __atomic_or_fetch(&m_PendingWork, work, __ATOMIC_ACQ_REL);
279 m_DeferredIrqs += 1;
280 if (m_Stopping || !m_Dispatcher.publishFromInterrupt(0, 1)) {
281 m_PublicationFailures += 1;
282 return false;
283 }
284 return true;
285}
286
287#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
288HardIrqHandler* SplitIrqHandler::hardHandlerForTest() {
289 return this;
290}
291
292void SplitIrqHandler::setRegistrationPublishedHookForTest(RegistrationPublishedHook hook) {
293 m_RegistrationPublishedHook = hook;
294}
295
296size_t SplitIrqHandler::publicationFailuresForTest() const {
297 return m_PublicationFailures;
298}
299
300size_t SplitIrqHandler::pendingWorkForTest() const {
301 return __atomic_load_n(&m_PendingWork, __ATOMIC_ACQUIRE);
302}
303
304size_t SplitIrqHandler::deferredIrqsForTest() const {
305 return m_DeferredIrqs;
306}
307
308size_t SplitIrqHandler::completedBatchesForTest() const {
309 return m_CompletedBatches;
310}
311
312bool SplitIrqHandler::publishWorkForTest(size_t work) {
313 return publishWork(work);
314}
315
316void SplitIrqHandler::rejectNextPublicationForTest() {
317 m_Dispatcher.rejectNextPublicationForTest();
318}
319#endif
virtual irq_id_t registerHardPciIrqHandler(HardIrqHandler *handler, Device *pDevice, const IrqPolicy &policy)=0
virtual bool unregisterHandler(irq_id_t Id, IrqHandlerBase *handler)=0
virtual irq_id_t registerHardIsaIrqHandler(uint8_t irq, HardIrqHandler *handler, const IrqPolicy &policy)=0
static bool getInterrupts()
static ProcessorInformation & information()
virtual void rearmIrqSources(size_t work)=0
virtual bool quiesceIrqSources()=0
::HardIrqDisposition irq(irq_id_t number, InterruptState &state) final
virtual HardStageDisposition hardIrq(irq_id_t number, InterruptState &state, size_t &work)=0
irq_id_t registerIsaSplitIrq(IrqManager &manager, uint8_t irq, const IrqPolicy &policy)
virtual void threadedIrq(size_t work)=0
bool publishFromInterrupt(uint8_t line, size_t cookie)
HardIrqDisposition
Definition IrqHandler.h:44