The Pedigree Project 0.1
PciMessageInterrupts.cc
1/* Copyright (c) 2026, Pedigree Developers. SPDX-License-Identifier: ISC */
2
3#include <config.h>
4
5#if MULTIPROCESSOR
6
7#include "pedigree/kernel/LockGuard.h"
8#include "pedigree/kernel/Log.h"
9#include "pedigree/kernel/machine/Device.h"
10#include "pedigree/kernel/machine/IrqHandler.h"
11#include "pedigree/kernel/machine/IrqManager.h"
12#include "pedigree/kernel/machine/Pci.h"
13#include "pedigree/kernel/processor/InterruptManager.h"
14#include "pedigree/kernel/processor/Processor.h"
15#include "pedigree/kernel/processor/ProcessorInformation.h"
16
17#include "LocalApic.h"
18#include "Pc.h"
19#include "PciMessageInterrupts.h"
20
21PciMessageInterrupts::PciMessageInterrupts()
22 : m_RegistrationLock(),
23 m_Lock(false),
24 m_Handlers(),
25 m_Dispatcher(MakeConstantString("PCI message IRQ"), VectorCount, dispatchThreaded, this),
26 m_Lines(),
27 m_DestinationApicId(0),
28 m_Initialised(false),
29 m_ShuttingDown(false) {}
30
31bool PciMessageInterrupts::initialise() {
33 size_t installed = 0;
34 for (; installed < VectorCount; ++installed) {
35 if (!manager.registerInterruptHandler(FirstVector + installed, this)) {
36 for (size_t i = 0; i < installed; ++i) {
37 manager.registerInterruptHandler(FirstVector + i, nullptr);
38 }
39 return false;
40 }
41 }
42 m_DestinationApicId = Pc::instance().getLocalApic().getId();
43 m_Initialised = true;
44 return true;
45}
46
47bool PciMessageInterrupts::initialiseThreaded() {
48 LockGuard<Mutex> registration(m_RegistrationLock);
49 return m_Initialised && m_Dispatcher.initialise(true);
50}
51
52bool PciMessageInterrupts::shutdownThreaded() {
53 if (!m_Dispatcher.canShutdown()) {
54 return false;
55 }
56 LockGuard<Mutex> registration(m_RegistrationLock);
57 if (!m_Initialised) {
58 return true;
59 }
60 bool disabled = true;
61 {
62 LockGuard<Spinlock> guard(m_Lock);
63 m_ShuttingDown = true;
64 for (Line& line : m_Lines) {
65 line.enabled = false;
66 if (line.device && !(line.msix ? PciBus::instance().disableMsix(line.device)
67 : PciBus::instance().disableMsi(line.device))) {
68 disabled = false;
69 }
70 }
71 }
72 return disabled && (!m_Dispatcher.isInitialised() || m_Dispatcher.shutdown());
73}
74
75irq_id_t PciMessageInterrupts::registerThreaded(Device* device, IrqHandler* handler,
76 bool& fallbackSafe) {
77 return registerHandler(device, handler, Mode::Threaded, fallbackSafe);
78}
79
80bool PciMessageInterrupts::registerThreadedVectors(Device* device, IrqHandler* const* handlers,
81 size_t count, irq_id_t* ids, bool& fallbackSafe,
82 const size_t* processors) {
83 fallbackSafe = true;
84 if (!device || !handlers || !ids || !count || count > VectorCount || !m_Initialised ||
85 !m_Dispatcher.isInitialised()) {
86 return false;
87 }
88 if (Processor::executionContext() != ExecutionContext::WaitableThread ||
89 m_Dispatcher.isCurrentWorker()) {
90 return false;
91 }
92 LockGuard<Mutex> registration(m_RegistrationLock);
93 for (size_t i = 0; i < count; ++i) {
94 ids[i] = 0;
95 if (!handlers[i] || (processors && !Processor::informationAt(processors[i]))) {
96 return false;
97 }
98 }
99
100 PciBus& pci = PciBus::instance();
102 if (!pci.inspectFunction(device, state, false) || !state.msix) {
103 return false;
104 }
105 uint16_t control = 0;
106 if (!pci.readConfig16(device, state.msix + 2, control) || count > (control & 0x7ffU) + 1) {
107 return false;
108 }
109
110 uint8_t slots[VectorCount] = {};
111 uint32_t data[VectorCount] = {};
112 uint64_t addresses[VectorCount] = {};
113 bool selected[VectorCount] = {};
114 bool reused[VectorCount] = {};
115 {
116 LockGuard<Spinlock> guard(m_Lock);
117 if (m_ShuttingDown) {
118 return false;
119 }
120 for (const Line& line : m_Lines) {
121 if (line.device == device) {
122 fallbackSafe = false;
123 return false;
124 }
125 }
126 for (size_t i = 0; i < count; ++i) {
127 const size_t cpu = processors ? processors[i] : 0;
128 const bool spuriousSafe = handlers[i]->acceptsSpuriousInterrupts();
129 size_t slot = 0;
130 for (; slot < VectorCount; ++slot) {
131 const Line& line = m_Lines[slot];
132 if (!selected[slot] && !line.device && (!line.used || (line.reusable && spuriousSafe)) &&
133 (!(line.used || line.workerPrepared) || line.processor == cpu)) {
134 break;
135 }
136 }
137 if (slot == VectorCount) {
138 return false;
139 }
140 slots[i] = static_cast<uint8_t>(slot);
141 selected[slot] = true;
142 reused[i] = m_Lines[slot].used;
143 }
144 }
145 // Worker creation may allocate and schedule; registration serialization keeps
146 // these unpublished slots reserved without holding an interrupt spinlock.
147 for (size_t i = 0; i < count; ++i) {
148 const size_t cpu = processors ? processors[i] : 0;
149 if (!m_Dispatcher.prepareLine(slots[i], cpu)) {
150 return false;
151 }
152 LockGuard<Spinlock> guard(m_Lock);
153 Line& line = m_Lines[slots[i]];
154 line.workerPrepared = true;
155 line.processor = cpu;
156 addresses[i] = 0xFEE00000ULL | (uint64_t{Processor::informationAt(cpu)->localApicId()} << 12);
157 }
158 {
159 LockGuard<Spinlock> guard(m_Lock);
160 for (size_t i = 0; i < count; ++i) {
161 const uint8_t vector = FirstVector + slots[i];
162 if (!m_Handlers.registerThreadedHandler(vector, handlers[i], IrqPolicy::edgeThreaded())) {
163 for (size_t j = 0; j < i; ++j) {
164 const uint8_t previous = FirstVector + slots[j];
165 if (m_Handlers.unregisterHandler(previous, handlers[j]) !=
166 IrqHandlerRegistry::UnregisterResult::Completed) {
167 FATAL("PCI message registration could not drain its unpublished handler");
168 }
169 Line& previousLine = m_Lines[slots[j]];
170 previousLine.device = nullptr;
171 previousLine.handler = nullptr;
172 previousLine.mode = Mode::None;
173 previousLine.enabled = false;
174 }
175 return false;
176 }
177 Line& line = m_Lines[slots[i]];
178 line.device = device;
179 line.handler = handlers[i];
180 line.mode = Mode::Threaded;
181 advanceCookie(line);
182 line.spuriousSafe = handlers[i]->acceptsSpuriousInterrupts();
183 line.removing = false;
184 line.deferred = false;
185 line.unhandled = 0;
186 line.enabled = false;
187 line.msix = true;
188 line.msixIndex = static_cast<uint8_t>(i);
189 data[i] = vector;
190 }
191 for (size_t i = 0; i < count; ++i) {
192 m_Lines[slots[i]].enabled = true;
193 }
194 }
195
196 bool touched = false;
197 if (pci.enableMsixVectors(device, addresses[0], data, count, &touched, addresses)) {
198 {
199 LockGuard<Spinlock> guard(m_Lock);
200 for (size_t i = 0; i < count; ++i) {
201 m_Lines[slots[i]].used = true;
202 m_Lines[slots[i]].reusable = false;
203 ids[i] = static_cast<irq_id_t>(data[i]);
204 }
205 }
206 for (size_t i = 0; i < count; ++i) {
207 NOTICE("PCI MSI-X: vector " << Dec << data[i] << " CPU " << m_Lines[slots[i]].processor
208 << (reused[i] ? " reused" : " allocated") << Hex);
209 }
210 return true;
211 }
212
213 uint16_t command = 0;
214 bool disabled = pci.disableMessageInterrupts(device, state);
215 if (touched) {
216 for (size_t i = 0; i < count; ++i) {
217 const bool masked = pci.setMsixVectorMask(device, i, true);
218 disabled = masked && disabled;
219 }
220 }
221 disabled = pci.updateCommand(device, 0x400U, state.command & 0x400U) && disabled;
222 disabled =
223 pci.readConfig16(device, 4, command) && ((command ^ state.command) & 0x400U) == 0 && disabled;
224 size_t cookies[VectorCount] = {};
225 {
226 LockGuard<Spinlock> guard(m_Lock);
227 for (size_t i = 0; i < count; ++i) {
228 Line& line = m_Lines[slots[i]];
229 line.enabled = false;
230 line.removing = true;
231 cookies[i] = advanceCookie(line);
232 }
233 }
234 for (size_t i = 0; i < count; ++i) {
235 const uint8_t vector = FirstVector + slots[i];
236 m_Handlers.invalidateThreadedLine(vector, cookies[i]);
237 if (m_Handlers.unregisterHandler(vector, handlers[i]) !=
238 IrqHandlerRegistry::UnregisterResult::Completed) {
239 FATAL("PCI message registration could not drain its unpublished handler");
240 }
241 }
242 {
243 LockGuard<Spinlock> guard(m_Lock);
244 for (size_t i = 0; i < count; ++i) {
245 Line& line = m_Lines[slots[i]];
246 line.device = nullptr;
247 line.handler = nullptr;
248 line.mode = Mode::None;
249 line.used = line.used || touched || !disabled;
250 line.reusable = disabled;
251 }
252 }
253 if (!disabled) {
254 fallbackSafe = false;
255 ERROR("PCI MSI-X setup failed without verified source shutdown");
256 }
257 return false;
258}
259
260irq_id_t PciMessageInterrupts::registerHard(Device* device, HardIrqHandler* handler,
261 bool& fallbackSafe) {
262 return registerHandler(device, handler, Mode::Hard, fallbackSafe);
263}
264
265irq_id_t PciMessageInterrupts::registerHandler(Device* device, IrqHandlerBase* handler, Mode mode,
266 bool& fallbackSafe) {
267 fallbackSafe = true;
268 if (!device || !handler || !m_Initialised ||
269 (mode == Mode::Threaded && !m_Dispatcher.isInitialised())) {
270 return 0;
271 }
272 if (Processor::executionContext() != ExecutionContext::WaitableThread ||
273 m_Dispatcher.isCurrentWorker()) {
274 return 0;
275 }
276 LockGuard<Mutex> registration(m_RegistrationLock);
277 const bool spuriousSafe =
278 mode == Mode::Threaded && static_cast<IrqHandler*>(handler)->acceptsSpuriousInterrupts();
279 PciBus& pci = PciBus::instance();
281 if (!pci.inspectFunction(device, state, false) || (!state.msi && !state.msix)) {
282 return 0;
283 }
284
285 size_t slot = VectorCount;
286 {
287 LockGuard<Spinlock> guard(m_Lock);
288 if (m_ShuttingDown) {
289 return 0;
290 }
291 for (size_t i = 0; i < VectorCount; ++i) {
292 if (m_Lines[i].device == device) {
293 fallbackSafe = false;
294 return 0;
295 }
296 const Line& candidate = m_Lines[i];
297 if (slot == VectorCount && !candidate.device &&
298 (!candidate.used || (candidate.reusable && spuriousSafe)) &&
299 (!(candidate.used || candidate.workerPrepared) || !candidate.processor)) {
300 slot = i;
301 }
302 }
303 if (slot == VectorCount) {
304 return 0;
305 }
306 }
307 if (mode == Mode::Threaded && !m_Dispatcher.prepareLine(slot, 0)) {
308 return 0;
309 }
310 {
311 LockGuard<Spinlock> guard(m_Lock);
312 m_Lines[slot].processor = 0;
313 m_Lines[slot].workerPrepared |= mode == Mode::Threaded;
314 const uint8_t vector = FirstVector + slot;
315 const bool registered =
316 mode == Mode::Threaded
317 ? m_Handlers.registerThreadedHandler(vector, static_cast<IrqHandler*>(handler),
318 IrqPolicy::edgeThreaded())
319 : m_Handlers.registerHardHandler(
320 vector, static_cast<HardIrqHandler*>(handler),
321 IrqPolicy(IrqTrigger::Edge, IrqControllerAck::AfterHardStage,
322 IrqLineRelease::AfterHardStage));
323 if (!registered) {
324 return 0;
325 }
326 Line& line = m_Lines[slot];
327 line.device = device;
328 line.handler = handler;
329 line.mode = mode;
330 advanceCookie(line);
331 line.spuriousSafe = spuriousSafe;
332 line.reusable = false;
333 line.removing = false;
334 line.unhandled = 0;
335 line.used = true;
336 line.enabled = true;
337 line.deferred = false;
338 line.msix = true;
339 line.msixIndex = 0;
340 }
341
342 const uint8_t vector = FirstVector + slot;
343 const uint64_t address = 0xFEE00000ULL | (uint64_t{m_DestinationApicId} << 12);
344 if (state.msix && pci.enableMsix(device, address, vector)) {
345 return vector;
346 }
347 {
348 LockGuard<Spinlock> guard(m_Lock);
349 m_Lines[slot].msix = false;
350 }
351 if (state.msi && pci.enableMsi(device, address, vector)) {
352 return vector;
353 }
354
355 // A failed readback may have followed a partial device write. Do not let
356 // the PIC claim INTx until both message sources and the command bit match
357 // the pre-attempt state.
358 uint16_t command = 0;
359 fallbackSafe = pci.disableMessageInterrupts(device, state) &&
360 pci.updateCommand(device, 0x400U, state.command & 0x400U) &&
361 pci.readConfig16(device, 4, command) && ((command ^ state.command) & 0x400U) == 0;
362
363 {
364 LockGuard<Spinlock> guard(m_Lock);
365 m_Lines[slot].enabled = false;
366 m_Lines[slot].removing = true;
367 }
368 size_t cookie = 0;
369 {
370 LockGuard<Spinlock> guard(m_Lock);
371 cookie = advanceCookie(m_Lines[slot]);
372 }
373 m_Handlers.invalidateThreadedLine(vector, cookie);
374 if (m_Handlers.unregisterHandler(vector, handler) !=
375 IrqHandlerRegistry::UnregisterResult::Completed) {
376 FATAL("PCI message registration could not drain its unpublished handler");
377 }
378 {
379 LockGuard<Spinlock> guard(m_Lock);
380 m_Lines[slot].device = nullptr;
381 m_Lines[slot].handler = nullptr;
382 m_Lines[slot].mode = Mode::None;
383 m_Lines[slot].reusable = fallbackSafe;
384 }
385 if (!fallbackSafe) {
386 ERROR("PCI message setup failed without a safe INTx fallback");
387 }
388 return 0;
389}
390
391bool PciMessageInterrupts::disableSource(Device* device, bool msix, uint8_t msixIndex) {
392 return msix ? PciBus::instance().setMsixVectorMask(device, msixIndex, true)
393 : PciBus::instance().disableMsi(device);
394}
395
396bool PciMessageInterrupts::unregisterHandler(irq_id_t id, IrqHandlerBase* handler) {
397 if (!contains(id) || !handler) {
398 return false;
399 }
400 if (Processor::executionContext() != ExecutionContext::WaitableThread ||
401 m_Dispatcher.isCurrentWorker()) {
402 return false;
403 }
404 LockGuard<Mutex> registration(m_RegistrationLock);
405 const uint8_t slot = id - FirstVector;
406 {
407 LockGuard<Spinlock> guard(m_Lock);
408 Line& line = m_Lines[slot];
409 if (line.handler != handler || !line.device) {
410 return false;
411 }
412 line.removing = true;
413 line.enabled = false;
414 if (!disableSource(line.device, line.msix, line.msixIndex)) {
415 return false;
416 }
417 }
418 size_t cookie = 0;
419 {
420 LockGuard<Spinlock> guard(m_Lock);
421 cookie = advanceCookie(m_Lines[slot]);
422 }
423 m_Handlers.invalidateThreadedLine(id, cookie);
424 const IrqHandlerRegistry::UnregisterResult result = m_Handlers.unregisterHandler(id, handler);
425 if (result == IrqHandlerRegistry::UnregisterResult::Deferred) {
426 LockGuard<Spinlock> guard(m_Lock);
427 m_Lines[slot].deferred = true;
428 return false;
429 }
430 if (result == IrqHandlerRegistry::UnregisterResult::NotFound) {
431 bool deferred = false;
432 {
433 LockGuard<Spinlock> guard(m_Lock);
434 deferred = m_Lines[slot].deferred;
435 }
436 size_t generation = 0;
437 uintptr_t identity = 0;
438 if (!deferred || m_Handlers.handlerCount(id) || m_Handlers.hardDispatchState(id, generation) ||
439 m_Handlers.threadedDispatchState(id, identity)) {
440 return false;
441 }
442 } else if (result != IrqHandlerRegistry::UnregisterResult::Completed) {
443 return false;
444 }
445 {
446 LockGuard<Spinlock> guard(m_Lock);
447 Line& line = m_Lines[slot];
448 bool last = true;
449 for (const Line& other : m_Lines) {
450 if (&other != &line && other.device == line.device) {
451 last = false;
452 break;
453 }
454 }
455 if (last && line.msix && !PciBus::instance().disableMsix(line.device)) {
456 line.deferred = true;
457 return false;
458 }
459 line.device = nullptr;
460 line.handler = nullptr;
461 line.mode = Mode::None;
462 line.reusable = true;
463 }
464 return true;
465}
466
467void PciMessageInterrupts::enable(irq_id_t id, bool enabled) {
468 if (!contains(id)) {
469 return;
470 }
471 const uint8_t slot = id - FirstVector;
472 bool success = false;
473 {
474 LockGuard<Spinlock> guard(m_Lock);
475 Line& line = m_Lines[slot];
476 if (!line.device || line.removing || m_ShuttingDown || line.enabled == enabled) {
477 return;
478 }
479 const uint64_t address = 0xFEE00000ULL | (uint64_t{m_DestinationApicId} << 12);
480 success =
481 enabled
482 ? (line.msix ? PciBus::instance().setMsixVectorMask(line.device, line.msixIndex, false)
483 : PciBus::instance().enableMsi(line.device, address, id))
484 : disableSource(line.device, line.msix, line.msixIndex);
485 line.enabled = enabled && success;
486 if (line.enabled) {
487 line.unhandled = 0;
488 }
489 }
490 if (!success) {
491 WARNING("PCI message IRQ " << Dec << id << " could not be "
492 << (enabled ? "enabled" : "disabled"));
493 }
494}
495
496void PciMessageInterrupts::quarantine(uint8_t slot, size_t expectedCookie) {
497 bool masked = true;
498 {
499 LockGuard<Spinlock> guard(m_Lock);
500 Line& line = m_Lines[slot];
501 if (!line.enabled || line.removing || (expectedCookie && line.cookie != expectedCookie)) {
502 return;
503 }
504 line.enabled = false;
505 masked = disableSource(line.device, line.msix, line.msixIndex);
506 }
507 if (!masked) {
508 ERROR("PCI message IRQ " << Dec << FirstVector + slot << " could not be masked");
509 }
510}
511
512void PciMessageInterrupts::interrupt(size_t interruptNumber, InterruptState& state) {
513 if (interruptNumber < FirstVector || interruptNumber >= FirstVector + VectorCount) {
514 return;
515 }
516 const uint8_t slot = interruptNumber - FirstVector;
517
518 Mode mode = Mode::None;
519 size_t cookie = 0;
520 bool published = false;
522 {
523 LockGuard<Spinlock> guard(m_Lock);
524 Line& line = m_Lines[slot];
525 if (line.enabled && !line.removing && !m_ShuttingDown) {
526 mode = line.mode;
527 cookie = advanceCookie(line);
528 const uint8_t vector = FirstVector + slot;
529 if (m_Handlers.captureAdmissionCutoff(vector, cutoff)) {
530 if (mode == Mode::Threaded) {
531 published = m_Handlers.publishThreadedDispatch(vector, cookie, cutoff);
532 if (published && !m_Dispatcher.publishFromInterrupt(slot, cookie)) {
533 m_Handlers.invalidateThreadedGenerationFromInterrupt(vector, cookie);
534 published = false;
535 }
536 } else {
537 published = true;
538 }
539 }
540 }
541 }
542
543 if (mode == Mode::Hard && published) {
544 HardIrqDisposition result = HardIrqDisposition::NotHandled;
545 const bool admitted =
546 m_Handlers.dispatchHard(FirstVector + slot, state, result, nullptr, cookie, cutoff);
547 if (!admitted || result != HardIrqDisposition::Handled) {
548 quarantine(slot, cookie);
549 }
550 } else if (mode != Mode::None && !published) {
551 quarantine(slot, cookie);
552 }
553 Pc::instance().getLocalApic().ack();
554}
555
556void PciMessageInterrupts::dispatchThreaded(void* context, uint8_t slot, size_t cookie) {
557 PciMessageInterrupts* self = static_cast<PciMessageInterrupts*>(context);
558 if (slot >= VectorCount) {
559 return;
560 }
561 {
562 LockGuard<Spinlock> guard(self->m_Lock);
563 const Line& line = self->m_Lines[slot];
564 if (!line.enabled || line.removing || line.mode != Mode::Threaded || cookie != line.cookie) {
565 return;
566 }
567 }
569 const bool admitted = self->m_Handlers.dispatchThreaded(FirstVector + slot, cookie, result);
570 bool mask = !admitted;
571 if (admitted) {
572 LockGuard<Spinlock> guard(self->m_Lock);
573 Line& line = self->m_Lines[slot];
574 if (line.enabled && !line.removing && line.mode == Mode::Threaded && cookie == line.cookie) {
575 if (result.allowRearm) {
576 line.unhandled = 0;
577 } else if (!line.spuriousSafe) {
578 // Polling can consume a completion before its queued MSI worker runs.
579 // Tolerate a few empty edges, but mask a persistently unclaimed source.
580 if (line.unhandled < 8) {
581 ++line.unhandled;
582 }
583 mask = line.unhandled == 8;
584 }
585 }
586 }
587 if (mask) {
588 self->quarantine(slot, cookie);
589 }
590}
591
592#endif
Handles interrupts and interrupt registrations from kernel components.
static EXPORTED_PUBLIC InterruptManager & instance()
virtual bool registerInterruptHandler(size_t nInterruptNumber, InterruptHandler *pHandler)=0
virtual bool acceptsSpuriousInterrupts() const
Definition IrqHandler.h:83
Definition Pci.h:31
static ExecutionContext executionContext()
Definition Processor.cc:109
static ProcessorInformation * informationAt(size_t cpu)
Definition Processor.cc:39
@ Dec
Definition Log.h:126
@ Hex
Definition Log.h:124
HardIrqDisposition
Definition IrqHandler.h:44