The Pedigree Project 0.1
PciePorts.cc
1/* Copyright (c) 2026, Pedigree Developers. SPDX-License-Identifier: ISC */
2#include "PciePorts.h"
3#include "pedigree/kernel/LockGuard.h"
4#include "pedigree/kernel/Log.h"
5#include "pedigree/kernel/machine/Device.h"
6#include "pedigree/kernel/machine/IrqHandler.h"
7#include "pedigree/kernel/machine/IrqManager.h"
8#include "pedigree/kernel/machine/Machine.h"
9#include "pedigree/kernel/machine/Pci.h"
10#include "pedigree/kernel/machine/PciAer.h"
11#include "pedigree/kernel/machine/PciExpress.h"
12#include "pedigree/kernel/machine/PciFirmware.h"
13#include "pedigree/kernel/panic.h"
14#include "pedigree/kernel/process/Mutex.h"
15#include "pedigree/kernel/time/Time.h"
16#include "pedigree/kernel/utilities/RequestQueue.h"
17
18#include "Enumeration.h"
19
20namespace {
21constexpr size_t MaxPorts = 16;
22constexpr uint32_t RootReporting = 7;
23constexpr uint32_t RootStatusBits = 0x7f;
24constexpr uint16_t SlotInterrupts = 0x103f;
25constexpr uint16_t SlotEvents = 0x10f;
26
27void waitFor(Time::Timestamp duration) {
28 const auto deadline = Time::getTicks() + duration;
29 for (auto now = Time::getTicks(); now < deadline; now = Time::getTicks()) {
30 Time::delay(deadline - now);
31 }
32}
33
34class Port;
35bool slotWorkAvailable();
36void scheduleSlot(Port* port);
37
38struct Config {
39 Device* device;
40 bool read8(uint16_t offset, uint8_t& value) {
41 return PciBus::instance().readConfig8(device, offset, value);
42 }
43 bool read16(uint16_t offset, uint16_t& value) {
44 return PciBus::instance().readConfig16(device, offset, value);
45 }
46 bool read32(uint16_t offset, uint32_t& value) {
47 return PciBus::instance().readConfig32(device, offset, value);
48 }
49 bool write16(uint16_t offset, uint16_t value) {
50 return PciBus::instance().writeConfig16(device, offset, value);
51 }
52 bool write32(uint16_t offset, uint32_t value) {
53 return PciBus::instance().writeConfig32(device, offset, value);
54 }
55};
56
57uint16_t requester(Device* device) {
58 return (device->getPciBusPosition() << 8) | (device->getPciDevicePosition() << 3) |
59 device->getPciFunctionNumber();
60}
61
62bool pciFunction(Device* device) {
63 return device->getPciVendorId() && device->getPciVendorId() != 0xffff &&
64 device->getPciBusPosition() <= 255 && device->getPciDevicePosition() < 32 &&
65 device->getPciFunctionNumber() < 8;
66}
67
68bool enableReporting(Device* device) {
69 Config config{device};
70 PciExpress::State express;
71 const auto found = PciExpress::find(config, express);
72 if (found == PciExpress::FindResult::Absent) {
73 return true;
74 }
75 if (found != PciExpress::FindResult::Found ||
76 !PciFunctionState::writeVerified16(config, express.offset + 8,
77 express.deviceControl | 0xfU)) {
78 return false;
79 }
80 uint8_t header = 0;
81 if (!config.read8(0x0e, header)) {
82 return false;
83 }
84 if ((header & 0x7fU) == 1) {
85 uint16_t control = 0, actual = 0;
86 // PCIe 2.1 table 7-6: SERR Enable forwards all three error message
87 // classes across a bridge. Discard Timer Status is an unrelated W1C bit.
88 if (!config.read16(0x3e, control) || !config.write16(0x3e, (control | 2U) & ~0x400U) ||
89 !config.read16(0x3e, actual) || ((actual ^ (control | 2U)) & ~0x400U)) {
90 return false;
91 }
92 }
93 return true;
94}
95
96class Port : public IrqHandler {
97 public:
98 void initialize(Device* device) {
99 Config config{device};
101 uint32_t status = 0, buses = 0, reporting = 0;
102 uint16_t rootControl = 0;
103 auto& pci = PciBus::instance();
104 if (PciExpress::find(config, m_Express) != PciExpress::FindResult::Found ||
105 m_Express.type != PciExpress::Type::RootPort) {
106 return;
107 }
108 m_Device = device;
109 const auto aer = pci.findExtendedCapability(device, PciAer::CapabilityId, m_Aer);
110 if (aer != PciExtendedCapabilities::FindResult::Found &&
111 aer != PciExtendedCapabilities::FindResult::Absent) {
112 return;
113 }
114 bool aerUsable = aer == PciExtendedCapabilities::FindResult::Found;
115 if (aerUsable && (m_Aer.offset > 0x1000 - 0x38 ||
116 (m_Aer.next >= m_Aer.offset && m_Aer.next < m_Aer.offset + 0x38) ||
117 !config.read32(m_Aer.offset + 0x30, status) ||
118 !config.read32(m_Aer.offset + 0x2c, reporting))) {
119 return;
120 }
121 if (!config.read32(0x18, buses) || !config.read16(m_Express.offset + 0x1c, rootControl) ||
122 !pci.inspectFunction(device, function, false) || (rootControl & 8U)) {
123 WARNING("PCIe: root " << Hex << requester(device)
124 << " has unusable capabilities or PME users");
125 return;
126 }
127 m_FirstBus = buses >> 8;
128 m_LastBus = buses >> 16;
129 const auto* root = PciFirmware::rootForBus(device->getPciBusPosition());
130 if (!root || uint8_t(buses) != device->getPciBusPosition() ||
131 m_FirstBus <= device->getPciBusPosition() || m_LastBus < m_FirstBus ||
132 m_LastBus > root->lastBus || (!function.msi && !function.msix)) {
133 return;
134 }
135 // Both services must use message zero; PME remains owned by firmware.
136 aerUsable = aerUsable && !((status >> 27) & 0x1fU) &&
137 PciFirmware::requestNativeControl(
138 device->getPciBusPosition(),
139 PciFirmware::NativeAer | PciFirmware::NativePcieCapability);
140 bool slotUsable = m_Express.slotImplemented && !m_Express.interruptMessage &&
141 (m_Express.slotCapabilities & (1U << 6)) && slotWorkAvailable() &&
142 PciFirmware::requestNativeControl(
143 device->getPciBusPosition(),
144 PciFirmware::NativeHotplug | PciFirmware::NativePcieCapability);
145 if ((!aerUsable && !slotUsable) || (!aerUsable && (reporting & RootReporting)) ||
146 (!slotUsable && (m_Express.slotControl & SlotInterrupts))) {
147 WARNING("PCIe: root " << Hex << requester(device) << " lacks exclusive native ownership");
148 return;
149 }
150 if (aerUsable && !setReporting(0)) {
151 panic("PCIe AER could not quiesce root reporting");
152 }
153 if (slotUsable && !slotCommand(SlotInterrupts, 0)) {
154 return;
155 }
156 if (!pci.disableMessageInterrupts(device, function) || !pci.updateCommand(device, 0, 4U)) {
157 return;
158 }
159 auto* irqManager = Machine::instance().getIrqManager();
160 const irq_id_t id =
161 irqManager->registerPciMessageIrqHandler(this, device, IrqPolicy::pciIntxThreaded());
162 uint16_t msiControl = 0, msixControl = 0;
163 const bool messageEnabled =
164 (function.msi && config.read16(function.msi + 2, msiControl) && (msiControl & 1U)) ||
165 (function.msix && config.read16(function.msix + 2, msixControl) && (msixControl & 0x8000U));
166 const bool selectedMessage =
167 !aerUsable || (config.read32(m_Aer.offset + 0x30, status) && !((status >> 27) & 0x1fU));
168 if (id && messageEnabled && selectedMessage) {
169 if (aerUsable && enableReporting(device)) {
170 auto report = [&](Device* child) -> Device* {
171 if (pciFunction(child) && !enableReporting(child)) {
172 WARNING("PCIe AER: could not enable reporting at " << Hex << requester(child));
173 }
174 return child;
175 };
176 auto callback = pedigree_std::make_callable(report);
177 Device::foreach (callback, device);
178 LockGuard<Mutex> guard(m_Lock);
179 m_AerReady = setReporting(RootReporting);
180 if (!m_AerReady) {
181 stopReporting();
182 }
183 }
184 if (slotUsable) {
185 uint16_t slotStatus = 0;
186 if (!config.read16(m_Express.offset + 0x1a, slotStatus) ||
187 !config.write16(m_Express.offset + 0x1a, slotStatus & SlotEvents)) {
188 panic("PCIe: could not acknowledge inherited slot events");
189 }
190 uint16_t enables = (1U << 3) | (1U << 5);
191 if (m_Express.slotCapabilities & 1U) {
192 enables |= 1U;
193 }
194 if (m_Express.slotCapabilities & 2U) {
195 enables |= 2U;
196 }
197 if (m_Express.slotCapabilities & 4U) {
198 enables |= 4U;
199 }
200 if (m_Express.linkCapabilities & (1U << 20)) {
201 enables |= 1U << 12;
202 }
203 // Some ports expose live link status without a link-change interrupt.
204 // Presence notifications remain required; lifecycle operations also
205 // check the link directly. Power-fault detection is optional too.
206 if (slotCommand(SlotInterrupts, enables, enables & ((1U << 12) | 2U))) {
207 uint16_t enabled = 0;
208 if ((enables & (1U << 12)) && config.read16(m_Express.offset + 0x18, enabled) &&
209 !(enabled & (1U << 12))) {
210 WARNING("PCIe hotplug: root " << Hex << requester(device)
211 << " link-change notifications unavailable");
212 }
213 LockGuard<Mutex> guard(m_Lock);
214 m_SlotReady = true;
215 } else if (!slotCommand(SlotInterrupts, 0)) {
216 panic("PCIe: could not disable an unavailable slot service");
217 }
218 }
219 }
220 if (!m_AerReady && !m_SlotReady) {
221 if (id && !irqManager->unregisterHandler(id, this)) {
222 panic("PCIe: could not remove an inactive port interrupt handler");
223 }
224 return;
225 }
226 if (m_AerReady) {
227 NOTICE("PCIe AER: root " << Hex << requester(device) << " native reporting on IRQ " << id);
228 }
229 if (m_SlotReady) {
230 NOTICE("PCIe hotplug: root " << Hex << requester(device) << " native slot service on IRQ "
231 << id);
232 }
233 irq(id);
234 }
235
236 IrqDisposition irq(irq_id_t) override {
237 LockGuard<Mutex> guard(m_Lock);
238 bool handled = slotInterrupt();
239 if (!m_AerReady) {
240 return handled ? IrqDisposition::Handled : IrqDisposition::NotHandled;
241 }
242 Config config{m_Device};
243 for (unsigned pass = 0; pass < 8; ++pass) {
244 uint32_t status = 0, source = 0;
245 if (!config.read32(m_Aer.offset + 0x30, status) ||
246 !config.read32(m_Aer.offset + 0x34, source)) {
247 stopReporting();
248 return IrqDisposition::Handled;
249 }
250 if (!(status & RootStatusBits)) {
251 return handled ? IrqDisposition::Handled : IrqDisposition::NotHandled;
252 }
253 handled = true;
254 if (status & 4U) {
255 m_SlotFault = true;
256 const uint16_t id = source >> 16;
257 const bool contained = contain(id);
258 ERROR("PCIe AER: " << ((status & 0x40U) ? "fatal" : "nonfatal") << " source " << Dec
259 << (id >> 8) << ":" << ((id >> 3) & 31U) << ":" << (id & 7U) << Hex
260 << " root status=" << status << " bus master stopped=" << contained
261 << "; driver recovery required");
262 if (!contained || (status & 8U) || id == requester(m_Device)) {
263 containBranch();
264 }
265 acknowledge(id, false);
266 if (status & 8U) {
267 acknowledgeBranch(false);
268 }
269 }
270 if (status & 1U) {
271 const uint16_t id = source;
272 NOTICE("PCIe AER: correctable source " << Dec << (id >> 8) << ":" << ((id >> 3) & 31U)
273 << ":" << (id & 7U) << Hex
274 << " root status=" << status);
275 acknowledge(id, true);
276 if (status & 2U) {
277 acknowledgeBranch(true);
278 }
279 }
280 // Only the observed W1C status bits are written, never the selector.
281 if (!config.write32(m_Aer.offset + 0x30, status & RootStatusBits)) {
282 stopReporting();
283 return IrqDisposition::Handled;
284 }
285 }
286 WARNING("PCIe AER: root " << Hex << requester(m_Device) << " reporting did not drain");
287 stopReporting();
288 return IrqDisposition::Handled;
289 }
290
291 void serviceSlot() {
292 for (;;) {
293 uint16_t events = 0;
294 {
295 LockGuard<Mutex> guard(m_Lock);
296 events = m_SlotEvents;
297 m_SlotEvents = 0;
298 if (!events || m_SlotFault) {
299 m_SlotQueued = false;
300 return;
301 }
302 }
303 processSlot(events);
304 }
305 }
306
307 private:
308 bool slotInterrupt() {
309 if (!m_SlotReady) {
310 return false;
311 }
312 Config config{m_Device};
313 uint16_t status = 0;
314 if (!config.read16(m_Express.offset + 0x1a, status)) {
315 panic("PCIe: slot status became unavailable");
316 }
317 const uint16_t events = status & SlotEvents;
318 if (!events) {
319 return false;
320 }
321 if (!config.write16(m_Express.offset + 0x1a, events)) {
322 panic("PCIe: could not acknowledge slot events");
323 }
324 if (!m_SlotFault) {
325 m_SlotEvents |= events;
326 if (!m_SlotQueued) {
327 m_SlotQueued = true;
328 scheduleSlot(this);
329 }
330 }
331 return true;
332 }
333
334 bool slotCommand(uint16_t clear, uint16_t set, uint16_t optional = 0) {
335 Config config{m_Device};
336 bool verified = false;
337 {
338 LockGuard<Mutex> guard(m_Lock);
339 uint16_t control = 0, status = 0;
340 if (!config.read16(m_Express.offset + 0x18, control) ||
341 !config.read16(m_Express.offset + 0x1a, status)) {
342 return false;
343 }
344 // Interlock Control is a write-one toggle, not a state to preserve.
345 const uint16_t desired = (control & ~(clear | 0x800U)) | set;
346 if (!(set & 0x800U) && desired == control) {
347 return true;
348 }
349 if ((status & 0x10U) && !config.write16(m_Express.offset + 0x1a, 0x10U)) {
350 return false;
351 }
352 const bool written = config.write16(m_Express.offset + 0x18, desired);
353 uint16_t actual = 0;
354 verified = config.read16(m_Express.offset + 0x18, actual) && written &&
355 !((actual ^ desired) & ~(0x800U | optional));
356 }
357 if (m_Express.slotCapabilities & (1U << 18)) {
358 return verified;
359 }
360 // PCIe 2.1 6.7.3.2: serialize all Slot Control writes through completion.
361 // CCIE stays disabled so this polling does not depend on the shared IRQ.
362 const auto deadline = Time::getTicks() + Time::Multiplier::Second;
363 do {
364 {
365 LockGuard<Mutex> guard(m_Lock);
366 uint16_t status = 0;
367 if (!config.read16(m_Express.offset + 0x1a, status)) {
368 verified = false;
369 } else if (status & 0x10U) {
370 return config.write16(m_Express.offset + 0x1a, 0x10U) && verified;
371 }
372 }
373 Time::delay(10 * Time::Multiplier::Millisecond);
374 } while (Time::getTicks() < deadline);
375 WARNING("PCIe hotplug: root " << Hex << requester(m_Device) << " command completion timeout");
376 return false;
377 }
378
379 bool powerIndicator(uint16_t state) {
380 return !(m_Express.slotCapabilities & (1U << 4)) || slotCommand(3U << 8, state << 8);
381 }
382
383 bool interlock(bool engaged) {
384 if (!(m_Express.slotCapabilities & (1U << 17))) {
385 return true;
386 }
387 uint16_t status = 0, link = 0;
388 if (!slotState(status, link)) {
389 return false;
390 }
391 if (bool(status & (1U << 7)) == engaged) {
392 return true;
393 }
394 if (!slotCommand(0, 1U << 11)) {
395 return false;
396 }
397 const auto deadline = Time::getTicks() + Time::Multiplier::Second;
398 do {
399 if (!slotState(status, link)) {
400 return false;
401 }
402 if (bool(status & (1U << 7)) == engaged) {
403 return true;
404 }
405 Time::delay(10 * Time::Multiplier::Millisecond);
406 } while (Time::getTicks() < deadline);
407 return false;
408 }
409
410 bool slotState(uint16_t& status, uint16_t& link) {
411 Config config{m_Device};
412 return config.read16(m_Express.offset + 0x1a, status) &&
413 config.read16(m_Express.offset + 0x12, link);
414 }
415
416 bool occupied(uint16_t status) const {
417 return (status & (1U << 6)) &&
418 (!(m_Express.slotCapabilities & (1U << 2)) || !(status & (1U << 5)));
419 }
420
421 bool linkActive(uint16_t link) const {
422 return (m_Express.linkCapabilities & (1U << 20)) ? bool(link & (1U << 13))
423 : !(link & (1U << 11)) && (link & 0x3f0U);
424 }
425
426 bool slotFault() {
427 LockGuard<Mutex> guard(m_Lock);
428 return m_SlotFault || (m_SlotEvents & 2U);
429 }
430
431 void retainSlot() {
432 {
433 LockGuard<Mutex> guard(m_Lock);
434 m_SlotFault = true;
435 }
436 // No callback may access vanished MMIO, and no DMA mapping can be freed.
437 if (!PciBus::instance().updateCommand(m_Device, 4U, 0)) {
438 panic("PCIe: could not contain a lost slot");
439 }
440 ERROR("PCIe hotplug: root "
441 << Hex << requester(m_Device)
442 << " lost its device or power; retaining resources, recovery required");
443 }
444
445 bool settleSlot() {
446 uint16_t status = 0, link = 0;
447 const auto deadline = Time::getTicks() + Time::Multiplier::Second;
448 do {
449 if (!slotState(status, link) || !occupied(status) || slotFault()) {
450 return false;
451 }
452 if (linkActive(link)) {
453 break;
454 }
455 Time::delay(10 * Time::Multiplier::Millisecond);
456 } while (Time::getTicks() < deadline);
457 if (!linkActive(link)) {
458 return false;
459 }
460 // Link-active is not permission to access configuration immediately.
461 waitFor(100 * Time::Multiplier::Millisecond);
462 Device endpoint;
463 endpoint.setPciPosition(m_FirstBus, 0, 0);
464 const auto configDeadline = Time::getTicks() + Time::Multiplier::Second;
465 do {
466 uint32_t identity = 0;
467 if (!slotState(status, link) || !occupied(status) || !linkActive(link) || slotFault()) {
468 return false;
469 }
470 if (PciBus::instance().readConfig32(&endpoint, 0, identity) && (identity & 0xffffU) > 1 &&
471 (identity & 0xffffU) != 0xffffU) {
472 return true;
473 }
474 Time::delay(10 * Time::Multiplier::Millisecond);
475 } while (Time::getTicks() < configDeadline);
476 return false;
477 }
478
479 bool attentionDelay() {
480 if (!powerIndicator(2)) {
481 return false;
482 }
483 const auto deadline = Time::getTicks() + 5 * Time::Multiplier::Second;
484 do {
485 {
486 LockGuard<Mutex> guard(m_Lock);
487 if (m_SlotEvents & 1U) {
488 m_SlotEvents &= ~1U;
489 NOTICE("PCIe hotplug: root " << Hex << requester(m_Device) << " eject cancelled");
490 return false;
491 }
492 if (m_SlotFault || (m_SlotEvents & 2U)) {
493 return false;
494 }
495 }
496 uint16_t status = 0, link = 0;
497 if (!slotState(status, link) || !occupied(status) || !linkActive(link)) {
498 retainSlot();
499 return false;
500 }
501 Time::delay(50 * Time::Multiplier::Millisecond);
502 } while (Time::getTicks() < deadline);
503 return true;
504 }
505
506 bool populated() const {
508 for (size_t i = 0; i < m_Device->getNumChildren(); ++i) {
509 Device* child = m_Device->getChild(i);
510 if (child->getSpecificType() == "pci" && child->getNumChildren()) {
511 return true;
512 }
513 }
514 return false;
515 }
516
517 void processSlot(uint16_t events) {
518 uint16_t status = 0, link = 0;
519 if (!slotState(status, link)) {
520 retainSlot();
521 return;
522 }
523 if ((events & 2U) || (populated() && (!occupied(status) || !linkActive(link)))) {
524 retainSlot();
525 return;
526 }
527 if (m_Ejected) {
528 if (!(status & (1U << 6))) {
529 m_Ejected = false;
530 }
531 return;
532 }
533 if ((events & 1U) && populated()) {
534 if (!(m_Express.slotCapabilities & 2U)) {
535 WARNING("PCIe hotplug: root " << Hex << requester(m_Device) << " has no power controller");
536 return;
537 }
538 if (!attentionDelay()) {
539 if (!slotFault()) {
540 (void)powerIndicator(1);
541 }
542 return;
543 }
544 if (!PciEnumeration::prepareRemoveSlot(m_Device)) {
545 PciEnumeration::cancelRemoveSlot(m_Device);
546 (void)powerIndicator(1);
547 NOTICE("PCIe hotplug: root " << Hex << requester(m_Device) << " eject denied: device busy");
548 return;
549 }
550 if (slotFault() || !slotState(status, link) || !occupied(status) || !linkActive(link)) {
551 PciEnumeration::cancelRemoveSlot(m_Device);
552 retainSlot();
553 return;
554 }
555 if (!PciEnumeration::removeSlot(m_Device)) {
556 PciEnumeration::cancelRemoveSlot(m_Device);
557 (void)powerIndicator(1);
558 WARNING("PCIe hotplug: root " << Hex << requester(m_Device) << " driver detach failed");
559 return;
560 }
561 m_Ejected = true;
562 if (!slotCommand(1U << 10, 1U << 10)) {
563 retainSlot();
564 return;
565 }
566 waitFor(Time::Multiplier::Second);
567 if (!interlock(false)) {
568 retainSlot();
569 return;
570 }
571 (void)powerIndicator(3);
572 NOTICE("PCIe hotplug: root " << Hex << requester(m_Device) << " orderly eject complete");
573 return;
574 }
575 if (!occupied(status) || populated()) {
576 return;
577 }
578 waitFor(100 * Time::Multiplier::Millisecond);
579 if (!slotState(status, link) || !occupied(status) || slotFault()) {
580 return;
581 }
582 if ((m_Express.slotCapabilities & 2U) && !slotCommand(1U << 10, 0)) {
583 return;
584 }
585 if (!settleSlot() || !PciEnumeration::probeSlot(m_Device)) {
586 WARNING("PCIe hotplug: root " << Hex << requester(m_Device) << " insertion unavailable");
587 return;
588 }
589 if (slotFault()) {
590 retainSlot();
591 return;
592 }
593 bool aerReady = false;
594 {
595 LockGuard<Mutex> guard(m_Lock);
596 aerReady = m_AerReady;
597 }
598 if (aerReady) {
599 auto report = [&](Device* child) -> Device* {
600 if (pciFunction(child) && !enableReporting(child)) {
601 WARNING("PCIe AER: could not enable reporting at " << Hex << requester(child));
602 }
603 return child;
604 };
605 auto callback = pedigree_std::make_callable(report);
606 Device::foreach (callback, m_Device);
607 }
608 (void)powerIndicator(1);
609 NOTICE("PCIe hotplug: root " << Hex << requester(m_Device) << " insertion complete");
610 }
611
612 bool inBranch(uint16_t id) const {
613 return id == requester(m_Device) || ((id >> 8) >= m_FirstBus && (id >> 8) <= m_LastBus);
614 }
615
616 bool setReporting(uint32_t bits) {
617 Config config{m_Device};
618 uint32_t command = 0;
619 return config.read32(m_Aer.offset + 0x2c, command) &&
620 PciFunctionState::writeVerified32(config, m_Aer.offset + 0x2c,
621 (command & ~RootReporting) | bits);
622 }
623
624 void stopReporting() {
625 if (!setReporting(0)) {
626 panic("PCIe AER could not disable an unserviceable interrupt source");
627 }
628 m_AerReady = false;
629 }
630
631 bool contain(uint16_t id) {
632 if (!inBranch(id)) {
633 return false;
634 }
635 Device source;
636 source.setPciPosition(id >> 8, (id >> 3) & 31U, id & 7U);
637 Config config{&source};
638 uint32_t identity = 0;
639 return config.read32(0, identity) && (identity & 0xffffU) && (identity & 0xffffU) != 0xffffU &&
640 PciBus::instance().updateCommand(&source, 4U, 0);
641 }
642
643 void containBranch() {
644 uint16_t slotStatus = 0, link = 0;
645 if (m_SlotReady && slotState(slotStatus, link) &&
646 (!occupied(slotStatus) || !linkActive(link))) {
647 if (!contain(requester(m_Device))) {
648 panic("PCIe AER could not contain a lost slot");
649 }
650 ERROR("PCIe AER: root " << Hex << requester(m_Device)
651 << " lost its link; retaining downstream resources");
652 return;
653 }
654 bool stopped = contain(requester(m_Device));
655 auto containChild = [&](Device* child) -> Device* {
656 if (pciFunction(child) && !contain(requester(child))) {
657 stopped = false;
658 }
659 return child;
660 };
661 auto callback = pedigree_std::make_callable(containChild);
662 Device::foreach (callback, m_Device);
663 if (!stopped) {
664 panic("PCIe AER could not contain an uncorrectable error");
665 }
666 ERROR("PCIe AER: root " << Hex << requester(m_Device)
667 << " stopped bus mastering throughout its branch");
668 }
669
670 void acknowledgeBranch(bool correctable) {
671 acknowledge(requester(m_Device), correctable);
672 auto clearChild = [&](Device* child) -> Device* {
673 if (pciFunction(child)) {
674 acknowledge(requester(child), correctable);
675 }
676 return child;
677 };
678 auto callback = pedigree_std::make_callable(clearChild);
679 Device::foreach (callback, m_Device);
680 }
681
682 void acknowledge(uint16_t id, bool correctable) {
683 if (!inBranch(id)) {
684 WARNING("PCIe AER: source outside root bus range " << Hex << id);
685 return;
686 }
687 Device source;
688 source.setPciPosition(id >> 8, (id >> 3) & 31U, id & 7U);
689 Config config{&source};
691 PciAer::Status status;
692 if (PciBus::instance().findExtendedCapability(&source, PciAer::CapabilityId, aer) ==
693 PciExtendedCapabilities::FindResult::Found &&
694 PciAer::read(config, aer.offset, aer.next, status)) {
695 const uint32_t bits = correctable ? status.correctable : status.uncorrectable;
696 if (bits) {
697 NOTICE("PCIe AER: requester "
698 << Hex << id << " status=" << bits
699 << " mask=" << (correctable ? status.correctableMask : status.uncorrectableMask));
700 if (!config.write32(aer.offset + (correctable ? 0x10 : 4), bits)) {
701 WARNING("PCIe AER: requester status could not be acknowledged " << Hex << id);
702 }
703 }
704 }
705 PciExpress::State express;
706 if (PciExpress::find(config, express) == PciExpress::FindResult::Found) {
707 const uint16_t bits = express.deviceStatus & (correctable ? 1U : 0xeU);
708 if (bits && !config.write16(express.offset + 0x0a, bits)) {
709 WARNING("PCIe AER: device status could not be acknowledged " << Hex << id);
710 }
711 }
712 }
713
714 Device* m_Device = nullptr;
715 PciExpress::State m_Express;
717 uint8_t m_FirstBus = 0, m_LastBus = 0;
718 bool m_AerReady = false;
719 bool m_SlotReady = false;
720 bool m_SlotFault = false;
721 bool m_SlotQueued = false;
722 bool m_Ejected = false;
723 uint16_t m_SlotEvents = 0;
724 Mutex m_Lock;
725};
726
727class SlotQueue : public RequestQueue {
728 public:
729 SlotQueue() : RequestQueue(MakeConstantString("PCIe slots")) {}
730 ~SlotQueue() override {
731 destroy();
732 }
733
734 protected:
735 uint64_t executeRequest(uint64_t port, uint64_t, uint64_t, uint64_t, uint64_t, uint64_t, uint64_t,
736 uint64_t) override {
737 reinterpret_cast<Port*>(static_cast<uintptr_t>(port))->serviceSlot();
738 return 0;
739 }
740};
741
742SlotQueue slotWork;
743bool slotWorkAvailable() {
744 return slotWork.getLifecycleState() == RequestQueue::LifecycleState::Accepting;
745}
746void scheduleSlot(Port* port) {
747 if (!slotWork.publishAsyncRequest(0, reinterpret_cast<uintptr_t>(port))) {
748 panic("PCIe: slot work queue unavailable");
749 }
750}
751
752Port ports[MaxPorts];
753bool initialized = false;
754} // namespace
755
756void PciePorts::initialize() {
757 if (initialized) {
758 return;
759 }
760 initialized = true;
761 slotWork.initialise();
762 Device* candidates[MaxPorts] = {};
763 size_t count = 0;
764 auto collect = [&](Device* device) -> Device* {
765 if (pciFunction(device) && device->getPciClassCode() == 6 &&
766 device->getPciSubclassCode() == 4) {
767 Config config{device};
768 PciExpress::State express;
769 if (PciExpress::find(config, express) == PciExpress::FindResult::Found &&
770 express.type == PciExpress::Type::RootPort) {
771 if (count < MaxPorts) {
772 candidates[count++] = device;
773 } else {
774 WARNING("PCIe: root service capacity exhausted");
775 }
776 }
777 }
778 return device;
779 };
780 auto callback = pedigree_std::make_callable(collect);
781 Device::foreach (callback, nullptr);
782 for (size_t i = 0; i < count; ++i) {
783 ports[i].initialize(candidates[i]);
784 }
785}
size_t getNumChildren()
Definition Device.cc:143
Device * getChild(size_t n)
Definition Device.cc:139
static void foreach(Callback callback, Device *root=0)
Definition Device.cc:107
virtual const String & getSpecificType()
Definition Device.h:183
void setPciPosition(uint32_t bus, uint32_t device, uint32_t func)
Definition Device.h:192
virtual IrqDisposition irq(irq_id_t number)=0
virtual irq_id_t registerPciMessageIrqHandler(IrqHandler *handler, Device *pDevice, const IrqPolicy &intxFallbackPolicy)
Definition IrqManager.cc:25
Definition Mutex.h:56
virtual void destroy()
virtual uint64_t executeRequest(uint64_t p1, uint64_t p2, uint64_t p3, uint64_t p4, uint64_t p5, uint64_t p6, uint64_t p7, uint64_t p8)=0
void EXPORTED_PUBLIC panic(const char *msg) NORETURN
Definition panic.cc:118
@ Dec
Definition Log.h:126
@ Hex
Definition Log.h:124
IrqDisposition
Definition IrqHandler.h:31