The Pedigree Project 0.1
PciIoApicInterrupts.cc
1/* Copyright (c) 2026, Pedigree Developers. SPDX-License-Identifier: ISC */
2
3#include <config.h>
4
5#if MULTIPROCESSOR && ACPI
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/Pci.h"
12#include "pedigree/kernel/processor/InterruptManager.h"
13
14#include "Acpi.h"
15#include "LocalApic.h"
16#include "Pc.h"
17#include "PciIoApicInterrupts.h"
18#include "Pic.h"
19
20PciIoApicInterrupts::PciIoApicInterrupts()
21 : m_Lock(false),
22 m_Handlers(),
23 m_Dispatcher(MakeConstantString("PCI INTx"), VectorCount, dispatchThreaded, this),
24 m_Controllers(),
25 m_ControllerCount(0),
26 m_Lines(),
27 m_DestinationApicId(0),
28 m_Ready(false),
29 m_ShuttingDown(false) {}
30
31bool PciIoApicInterrupts::initialise() {
32 const auto& controllers = Acpi::instance().getIoApicList();
33 for (size_t i = 0; i < controllers.size() && m_ControllerCount < 4; ++i) {
34 const auto* info = controllers[i];
35 if (info && m_Controllers[m_ControllerCount].initialise(info->address, info->gsiBase)) {
36 ++m_ControllerCount;
37 }
38 }
39 if (!m_ControllerCount) {
40 return false;
41 }
43 size_t installed = 0;
44 for (; installed < VectorCount; ++installed) {
45 if (!manager.registerInterruptHandler(FirstVector + installed, this)) {
46 for (size_t i = 0; i < installed; ++i) {
47 manager.registerInterruptHandler(FirstVector + i, nullptr);
48 }
49 return false;
50 }
51 }
52 m_DestinationApicId = Pc::instance().getLocalApic().getId();
53 m_Ready = true;
54 return true;
55}
56
57bool PciIoApicInterrupts::initialiseThreaded() {
58 return m_Ready && m_Dispatcher.initialise();
59}
60
61bool PciIoApicInterrupts::containsGsi(uint32_t gsi) const {
62 for (size_t i = 0; m_Ready && i < m_ControllerCount; ++i) {
63 if (m_Controllers[i].contains(gsi)) {
64 return true;
65 }
66 }
67 return false;
68}
69
70bool PciIoApicInterrupts::reserveGsi(uint32_t gsi, bool activeLow) {
71 LockGuard<Spinlock> guard(m_Lock);
72 if (!m_Ready || m_ShuttingDown) {
73 return false;
74 }
75 size_t slot = VectorCount;
76 for (size_t i = 0; i < VectorCount; ++i) {
77 const Line& line = m_Lines[i];
78 if (line.used && line.gsi == gsi) {
79 return line.activeLow == activeLow && !line.quarantined && !line.removing;
80 }
81 if (!line.used && slot == VectorCount) {
82 slot = i;
83 }
84 }
85 if (slot == VectorCount) {
86 return false;
87 }
88 for (size_t i = 0; i < m_ControllerCount; ++i) {
89 if (m_Controllers[i].contains(gsi)) {
90 Line& line = m_Lines[slot];
91 line.controller = &m_Controllers[i];
92 line.gsi = gsi;
93 line.activeLow = activeLow;
94 line.used = true;
95 line.quarantined =
96 !line.controller->route(gsi, FirstVector + slot, m_DestinationApicId, activeLow);
97 return !line.quarantined;
98 }
99 }
100 return false;
101}
102
103bool PciIoApicInterrupts::shutdownThreaded() {
104 if (!m_Ready) {
105 return true;
106 }
107 bool masked = true;
108 {
109 LockGuard<Spinlock> guard(m_Lock);
110 m_ShuttingDown = true;
111 for (Line& line : m_Lines) {
112 if (line.used && line.controller && !line.controller->mask(line.gsi, true)) {
113 masked = false;
114 }
115 line.enabled = false;
116 }
117 }
118 return masked && (!m_Dispatcher.isInitialised() || m_Dispatcher.shutdown());
119}
120
121bool PciIoApicInterrupts::findRoute(Device* device, uint32_t& gsi, bool& activeLow) {
122 if (!device) {
123 return false;
124 }
125 uint8_t pin = 0;
126 if (!PciBus::instance().readConfig8(device, 0x3d, pin) || pin < 1 || pin > 4) {
127 return false;
128 }
129 AcpiPciRouting::Route route = {};
130 if (!Acpi::instance().pciInterruptRoute(device, pin, route)) {
131 return false;
132 }
133 gsi = route.gsi;
134 activeLow = route.activeLow;
135 return true;
136}
137
138irq_id_t PciIoApicInterrupts::registerThreaded(Device* device, IrqHandler* handler,
139 const IrqPolicy& policy, bool& routed) {
140 return registerHandler(device, handler, policy, Mode::Threaded, routed);
141}
142
143irq_id_t PciIoApicInterrupts::registerHard(Device* device, HardIrqHandler* handler,
144 const IrqPolicy& policy, bool& routed) {
145 return registerHandler(device, handler, policy, Mode::Hard, routed);
146}
147
148irq_id_t PciIoApicInterrupts::registerHandler(Device* device, IrqHandlerBase* handler,
149 const IrqPolicy& policy, Mode mode, bool& routed) {
150 routed = false;
151 if (!m_Ready || !device || !handler || policy.trigger() != IrqTrigger::Level ||
152 (mode == Mode::Threaded ? (!policy.validForThreaded() || !m_Dispatcher.isInitialised())
153 : !policy.validForHard())) {
154 return 0;
155 }
156 // Once AML selected APIC mode, PCI InterruptLine may describe the old PIC route.
157 routed = Acpi::instance().hasPciInterruptRouter();
158 uint32_t gsi = 0;
159 bool activeLow = false;
160 if (!findRoute(device, gsi, activeLow)) {
161 return 0;
162 }
163 IoApic* controller = nullptr;
164 for (size_t i = 0; i < m_ControllerCount; ++i) {
165 if (m_Controllers[i].contains(gsi)) {
166 controller = &m_Controllers[i];
167 break;
168 }
169 }
170 if (!controller) {
171 return 0;
172 }
173 routed = true;
174 if (!Pic::instance().reservePciGsi(gsi, activeLow)) {
175 return 0;
176 }
177
178 LockGuard<Spinlock> guard(m_Lock);
179 if (m_ShuttingDown) {
180 return 0;
181 }
182 size_t slot = VectorCount;
183 for (size_t i = 0; i < VectorCount; ++i) {
184 if (m_Lines[i].used && m_Lines[i].gsi == gsi) {
185 slot = i;
186 break;
187 }
188 }
189 if (slot == VectorCount) {
190 return 0;
191 }
192 Line& line = m_Lines[slot];
193 if (line.removing || line.quarantined || line.controller != controller ||
194 line.activeLow != activeLow || (line.handlers && line.mode != mode)) {
195 return 0;
196 }
197 const uint8_t vector = FirstVector + slot;
198 const bool registered =
199 mode == Mode::Threaded
200 ? m_Handlers.registerThreadedHandler(vector, static_cast<IrqHandler*>(handler), policy)
201 : m_Handlers.registerHardHandler(vector, static_cast<HardIrqHandler*>(handler), policy);
202 if (!registered) {
203 return 0;
204 }
205 line.controller = controller;
206 line.gsi = gsi;
207 line.activeLow = activeLow;
208 line.mode = mode;
209 line.used = true;
210 ++line.handlers;
211 line.enabled = true;
212 if (!line.inFlight && !controller->mask(gsi, false)) {
213 line.quarantined = true;
214 ERROR("PCI INTx: could not enable IOAPIC GSI " << Dec << gsi);
215 const auto result = m_Handlers.unregisterHandler(vector, handler);
216 if (result == IrqHandlerRegistry::UnregisterResult::Completed) {
217 --line.handlers;
218 if (!line.handlers) {
219 line.mode = Mode::None;
220 line.enabled = false;
221 }
222 }
223 return 0;
224 }
225 NOTICE("PCI INTx: IOAPIC GSI " << Dec << gsi << " -> vector " << Hex << vector);
226 return vector;
227}
228
229bool PciIoApicInterrupts::unregisterHandler(irq_id_t id, IrqHandlerBase* handler) {
230 if (!contains(id) || !handler) {
231 return false;
232 }
233 const uint8_t slot = id - FirstVector;
234 size_t cookie = 0;
235 bool last = false;
236 {
237 LockGuard<Spinlock> guard(m_Lock);
238 Line& line = m_Lines[slot];
239 if (!line.handlers || line.removing || !line.controller ||
240 !m_Handlers.containsHandler(id, handler) || !line.controller->mask(line.gsi, true)) {
241 return false;
242 }
243 line.removing = true;
244 last = line.handlers == 1;
245 if (last) {
246 cookie = ++line.cookie;
247 }
248 }
249 if (last) {
250 m_Handlers.invalidateThreadedLine(id, cookie);
251 }
252 const auto result = m_Handlers.unregisterHandler(id, handler);
253 {
254 LockGuard<Spinlock> guard(m_Lock);
255 Line& line = m_Lines[slot];
256 if (last) {
257 line.inFlight = false;
258 }
259 if ((result == IrqHandlerRegistry::UnregisterResult::Completed ||
260 result == IrqHandlerRegistry::UnregisterResult::Deferred) &&
261 line.handlers) {
262 --line.handlers;
263 if (!line.handlers) {
264 line.mode = Mode::None;
265 line.enabled = false;
266 // A delivered vector can remain pending in the LAPIC after the GSI is masked.
267 // Keep its vector bound to this GSI so a late delivery cannot reach a new device.
268 }
269 } else if (result != IrqHandlerRegistry::UnregisterResult::Completed) {
270 line.quarantined = true;
271 }
272 line.removing = false;
273 if (line.handlers && line.enabled && !line.inFlight && !line.quarantined &&
274 !line.controller->mask(line.gsi, false)) {
275 line.quarantined = true;
276 }
277 }
278 return result == IrqHandlerRegistry::UnregisterResult::Completed;
279}
280
281void PciIoApicInterrupts::enable(irq_id_t id, bool enabled) {
282 if (!contains(id)) {
283 return;
284 }
285 LockGuard<Spinlock> guard(m_Lock);
286 Line& line = m_Lines[id - FirstVector];
287 if (!line.handlers || line.removing || line.quarantined || m_ShuttingDown) {
288 return;
289 }
290 line.enabled = enabled;
291 if (!line.controller->mask(line.gsi, !enabled || line.inFlight)) {
292 line.quarantined = true;
293 }
294}
295
296void PciIoApicInterrupts::interrupt(size_t vector, InterruptState& state) {
297 if (!contains(vector)) {
298 return;
299 }
300 const uint8_t slot = vector - FirstVector;
301 Mode mode = Mode::None;
302 size_t cookie = 0;
303 bool published = false;
305 {
306 LockGuard<Spinlock> guard(m_Lock);
307 Line& line = m_Lines[slot];
308 if (line.controller) {
309 if (!line.controller->mask(line.gsi, true)) {
310 line.quarantined = true;
311 }
312 if (line.handlers && line.enabled && !line.removing && !line.inFlight && !line.quarantined &&
313 !m_ShuttingDown) {
314 mode = line.mode;
315 line.inFlight = true;
316 cookie = ++line.cookie;
317 if (!cookie) {
318 cookie = ++line.cookie;
319 }
320 if (m_Handlers.captureAdmissionCutoff(vector, cutoff)) {
321 if (mode == Mode::Threaded) {
322 published = m_Handlers.publishThreadedDispatch(vector, cookie, cutoff);
323 if (published && !m_Dispatcher.publishFromInterrupt(slot, cookie)) {
324 m_Handlers.invalidateThreadedGenerationFromInterrupt(vector, cookie);
325 published = false;
326 }
327 } else {
328 published = true;
329 }
330 }
331 if (!published) {
332 line.quarantined = true;
333 }
334 }
335 }
336 }
337 if (mode != Mode::Hard || !published) {
338 Pc::instance().getLocalApic().ack();
339 return;
340 }
341 HardIrqDisposition disposition = HardIrqDisposition::NotHandled;
342 const bool admitted =
343 m_Handlers.dispatchHard(vector, state, disposition, nullptr, cookie, cutoff);
344 Pc::instance().getLocalApic().ack();
345 LockGuard<Spinlock> guard(m_Lock);
346 Line& line = m_Lines[slot];
347 if (line.cookie != cookie) {
348 return;
349 }
350 line.inFlight = false;
351 if (!admitted || disposition != HardIrqDisposition::Handled) {
352 line.quarantined = true;
353 }
354 if (line.enabled && !line.removing && !line.quarantined &&
355 !line.controller->mask(line.gsi, false)) {
356 line.quarantined = true;
357 }
358}
359
360void PciIoApicInterrupts::dispatchThreaded(void* context, uint8_t slot, size_t cookie) {
361 auto& self = *static_cast<PciIoApicInterrupts*>(context);
362 if (slot >= VectorCount) {
363 return;
364 }
365 const uint8_t vector = FirstVector + slot;
367 const bool admitted = self.m_Handlers.dispatchThreaded(vector, cookie, result);
368 LockGuard<Spinlock> guard(self.m_Lock);
369 Line& line = self.m_Lines[slot];
370 if (line.cookie != cookie) {
371 return;
372 }
373 line.inFlight = false;
374 if (!admitted || !result.allowRearm) {
375 line.quarantined = true;
376 }
377 if (line.enabled && !line.removing && !line.quarantined &&
378 !line.controller->mask(line.gsi, false)) {
379 line.quarantined = true;
380 }
381}
382
383#endif
Handles interrupts and interrupt registrations from kernel components.
static EXPORTED_PUBLIC InterruptManager & instance()
virtual bool registerInterruptHandler(size_t nInterruptNumber, InterruptHandler *pHandler)=0
virtual void initialise() INITIALISATION_ONLY
Definition Pc.cc:162
static Pic & instance()
Definition Pic.h:56
@ Dec
Definition Log.h:126
@ Hex
Definition Log.h:124
HardIrqDisposition
Definition IrqHandler.h:44