The Pedigree Project 0.1
Qm67.cc
1/* Copyright (c) 2026, Pedigree Developers. SPDX-License-Identifier: ISC */
2#include "Qm67.h"
3#include "pedigree/kernel/Log.h"
4#include "pedigree/kernel/machine/Device.h"
5#include "pedigree/kernel/machine/Pci.h"
6#include "pedigree/kernel/panic.h"
7#include "pedigree/kernel/processor/MemoryRegion.h"
8#include "pedigree/kernel/processor/PhysicalMemoryManager.h"
9#include "pedigree/kernel/processor/VirtualAddressSpace.h"
10
11#include "IntelPirq.h"
12
13namespace {
14struct LpcConfig {
15 Device* device;
16 bool read8(uint16_t offset, uint8_t& value) {
17 return PciBus::instance().readConfig8(device, offset, value);
18 }
19 bool write8(uint16_t offset, uint8_t value) {
20 return PciBus::instance().writeConfig8(device, offset, value);
21 }
22};
23uint16_t hpetExclusions(uint32_t hptc) {
24 // AE gates register decoding, not the timers. With no readable registers,
25 // leave IRQ11 alone: HPET timer 2 can use it independently of the PIC drivers.
26 if (hptc == 0xffffffff || !(hptc & 0x80))
27 return 1U << 11;
28 MemoryRegion hpet("QM67 HPET interrupt audit");
29 if (!PhysicalMemoryManager::instance().allocateRegion(
30 hpet, 1,
34 0xfed00000U + ((hptc & 3) << 12)))
35 return 1U << 11;
36 auto* registers = static_cast<volatile uint32_t*>(hpet.virtualAddress());
37 const uint32_t capabilities = registers[0];
38 const uint32_t general = registers[0x10 / 4];
39 const uint32_t pending = registers[0x20 / 4];
40 const unsigned lastTimer = (capabilities >> 8) & 31;
41 if ((capabilities >> 16) != 0x8086 || lastTimer < 2 || general == 0xffffffff ||
42 pending == 0xffffffff)
43 return 1U << 11;
44 uint16_t excluded = 0;
45 for (unsigned timer = 0; timer <= lastTimer; ++timer)
46 excluded |= IntelPirq::hpetExclusion(registers[(0x100 + 0x20 * timer) / 4], pending, timer,
47 general & 2);
48 return excluded;
49}
50} // namespace
51
52void Qm67::routeInterrupts(const Vector<Device*>& devices) {
53 auto& pci = PciBus::instance();
54 Device* lpc = nullptr;
55 for (size_t i = 0; i < devices.size(); ++i) {
56 auto* device = devices[i];
57 const auto header = device->getPciConfigHeader();
58 if (device->getPciBusPosition() == 0 && device->getPciDevicePosition() == 31 &&
59 device->getPciFunctionNumber() == 0 && header.vendor == 0x8086 && header.device == 0x1c4f &&
60 header.class_code == 6 && header.subclass == 1)
61 lpc = device;
62 }
63 if (!lpc)
64 return;
65
66 uint32_t rcba = 0;
67 uint8_t acpiControl = 0;
68 if (!pci.readConfig32(lpc, 0xf0, rcba) || rcba == 0xffffffff || !(rcba & 1) ||
69 !(rcba & 0xffffc000U) || !pci.readConfig8(lpc, 0x44, acpiControl) || acpiControl == 0xff ||
70 (acpiControl & 7) == 3) {
71 ERROR("PCI: QM67 routing unavailable: invalid RCBA or SCI selection");
72 return;
73 }
74 const uint8_t sci = (acpiControl & 7) < 3 ? 9 + (acpiControl & 7) : 16 + (acpiControl & 7);
75 uint16_t excluded = 0;
76
77 MemoryRegion registers("QM67 interrupt routing");
78 const auto pageSize = PhysicalMemoryManager::getPageSize();
79 if (pageSize != 4096 ||
80 !PhysicalMemoryManager::instance().allocateRegion(
81 registers, 1,
85 (rcba & 0xffffc000U) + 0x3000)) {
86 ERROR("PCI: cannot map QM67 interrupt routing registers");
87 return;
88 }
89 auto* mapped = static_cast<volatile uint8_t*>(registers.virtualAddress());
90 // Chapter 10 requires dword accesses even for its byte/word-sized fields.
91 auto read32 = [mapped](uint16_t offset) {
92 return *reinterpret_cast<volatile uint32_t*>(mapped + ((offset & ~3U) - 0x3000));
93 };
94 auto read16 = [&read32](uint16_t offset) {
95 return static_cast<uint16_t>(read32(offset) >> (8 * (offset & 2)));
96 };
97 const uint8_t tcoControl = read32(0x3000);
98 if (!IntelPirq::chipsetExclusions(acpiControl, tcoControl, excluded)) {
99 ERROR("PCI: QM67 SCI/TCO routing state invalid");
100 return;
101 }
102 excluded |= hpetExclusions(read32(0x3404));
103 // IRQ12 is the fixed PS/2 mouse line. If firmware put a PIRQ there, move the
104 // shared PCI route to a reserved level line before registering PCI handlers.
105 excluded |= uint16_t{1} << 12;
106 NOTICE("PCI: QM67 native INTx routing: RCBA=" << Hex << rcba << " OIC=" << read16(0x31fe)
107 << " SCI=" << Dec << sci << " excluded=" << Hex
108 << excluded);
109
110 static const char* reasons[] = {"ready",
111 "cannot read route",
112 "invalid or reserved IRQ",
113 "no compatible PIC line",
114 "write failed; original restored",
115 "route restoration failed"};
116 LpcConfig config{lpc};
117 uint8_t routes[8] = {};
118 bool attempted[8] = {};
119 bool ready[8] = {};
120 bool downstream = false;
121 for (size_t i = 0; i < devices.size(); ++i) {
122 auto* device = devices[i];
123 auto header = device->getPciConfigHeader();
124 if (!header.interrupt_pin)
125 continue;
126 if (device->getPciBusPosition() != 0) {
127 downstream |= header.interrupt_line == 0xff;
128 continue;
129 }
130 const uint8_t slot = device->getPciDevicePosition();
131 const uint8_t function = device->getPciFunctionNumber();
132 const uint16_t offset = IntelPirq::routeOffset(slot);
133 if (header.vendor != 0x8086 || !offset || function > 7)
134 continue;
135 uint8_t pirq = 0;
136 // No driver may bind to a stale firmware IRQ after an attempted route repair.
137 device->setInterruptNumber(0xff);
138 auto unpublished = header;
139 unpublished.interrupt_line = 0xff;
140 device->setPciConfigHeader(unpublished);
141 const uint32_t pins = read32(IntelPirq::pinOffset(slot));
142 if (!IntelPirq::nativePinMatches(pins, function, header.interrupt_pin) ||
143 !IntelPirq::decode(read16(offset), header.interrupt_pin, pirq)) {
144 ERROR("PCI: QM67 pin/route mismatch for 0:" << Dec << slot << ":" << function);
145 continue;
146 }
147 if (!attempted[pirq]) {
148 attempted[pirq] = true;
149 uint8_t before = 0xff;
150 config.read8(IntelPirq::configOffset(pirq), before);
151 const auto result = IntelPirq::establish(
152 config, [&pci](uint8_t irq) { return pci.reserveLegacyInterrupt(irq); }, pirq, excluded,
153 routes[pirq], true);
154 if (result == IntelPirq::Result::RestoreFailed)
155 panic("PCI: cannot restore QM67 PIRQ route after failed write");
156 ready[pirq] = result == IntelPirq::Result::Ready;
157 if (ready[pirq])
158 NOTICE("PCI: QM67 PIRQ" << char('A' + pirq) << "=" << Hex << before << " -> PIC IRQ " << Dec
159 << routes[pirq]);
160 else
161 ERROR("PCI: QM67 PIRQ" << char('A' + pirq) << "=" << Hex << before
162 << " unavailable: " << reasons[static_cast<unsigned>(result)]);
163 }
164 if (!ready[pirq])
165 continue;
166 uint8_t actual = 0, currentPin = 0;
167 if (!pci.readConfig8(device, 0x3d, currentPin) || currentPin != header.interrupt_pin ||
168 !pci.writeConfig8(device, 0x3c, routes[pirq]) || !pci.readConfig8(device, 0x3c, actual) ||
169 actual != routes[pirq]) {
170 ERROR("PCI: cannot publish QM67 IRQ for 0:" << Dec << slot << ":" << function);
171 continue;
172 }
173 NOTICE("PCI: 0:" << Dec << slot << ":" << function << " " << Hex << header.vendor << ":"
174 << header.device << " INT" << char('A' + header.interrupt_pin - 1)
175 << " -> PIRQ" << char('A' + pirq) << " -> IRQ " << Dec << actual);
176 header.interrupt_line = actual;
177 device->setPciConfigHeader(header);
178 device->setInterruptNumber(actual);
179 }
180 if (downstream)
181 NOTICE("PCI: unassigned downstream INTx routes require ACPI bridge routing");
182}
Special memory entity in the kernel's virtual address space.
static PhysicalMemoryManager & instance()
A vector / dynamic array.
Definition Vector.h:33
void EXPORTED_PUBLIC panic(const char *msg) NORETURN
Definition panic.cc:118
@ Dec
Definition Log.h:126
@ Hex
Definition Log.h:124
size_t size() const
Definition Vector.h:265