The Pedigree Project 0.1
mach_virt/Pci.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/Spinlock.h"
11#include "pedigree/kernel/TargetInfo.h"
12#include "pedigree/kernel/machine/Device.h"
13#include "pedigree/kernel/machine/Pci.h"
14#include "pedigree/kernel/machine/PciMessageBar.h"
15#include "pedigree/kernel/processor/MemoryMappedIo.h"
16#include "pedigree/kernel/processor/PhysicalMemoryManager.h"
17#include "pedigree/kernel/processor/VirtualAddressSpace.h"
18
19#include "DeviceTree.h"
20
21namespace {
22MemoryMappedIo ecam("PCI ECAM");
23Spinlock configLock(false);
24VirtPciHost host = {};
25bool configAvailable = false;
26VirtPciWindow windows[8] = {};
27uint64_t nextAddress[8] = {};
28size_t windowCount = 0;
29
30struct FunctionConfig {
31 Device* device;
32 bool read8(uint16_t offset, uint8_t& value) {
33 return PciBus::instance().readConfig8(device, offset, value);
34 }
35 bool read16(uint16_t offset, uint16_t& value) {
36 return PciBus::instance().readConfig16(device, offset, value);
37 }
38 bool read32(uint16_t offset, uint32_t& value) {
39 return PciBus::instance().readConfig32(device, offset, value);
40 }
41 bool write16(uint16_t offset, uint16_t value) {
42 return PciBus::instance().writeConfig16(device, offset, value);
43 }
44 bool write32(uint16_t offset, uint32_t value) {
45 return PciBus::instance().writeConfig32(device, offset, value);
46 }
47};
48
49bool valid(uint8_t bus, uint8_t device, uint8_t function, uint16_t offset, uint8_t width) {
50 return configAvailable && bus >= host.firstBus && bus <= host.lastBus && device < 32 &&
51 function < 8 && (width == 1 || width == 2 || width == 4) && offset <= 4096 - width &&
52 !(offset & (width - 1));
53}
54
55size_t configOffset(uint8_t bus, uint8_t device, uint8_t function, uint16_t offset) {
56 return (size_t(bus - host.firstBus) << 20) | (size_t(device) << 15) | (size_t(function) << 12) |
57 offset;
58}
59
60bool read(uint8_t bus, uint8_t device, uint8_t function, uint16_t offset, uint8_t width,
61 uint32_t& value) {
62 if (!valid(bus, device, function, offset, width)) {
63 return false;
64 }
65 const size_t position = configOffset(bus, device, function, offset);
66 if (position > ecam.size() - width) {
67 return false;
68 }
69 if (width == 1) {
70 value = ecam.read8(position);
71 } else if (width == 2) {
72 value = ecam.read16(position);
73 } else {
74 value = ecam.read32(position);
75 }
76 return true;
77}
78
79bool write(uint8_t bus, uint8_t device, uint8_t function, uint16_t offset, uint8_t width,
80 uint32_t value) {
81 if (!valid(bus, device, function, offset, width)) {
82 return false;
83 }
84 const size_t position = configOffset(bus, device, function, offset);
85 if (position > ecam.size() - width) {
86 return false;
87 }
88 if (width == 1) {
89 ecam.write8(static_cast<uint8_t>(value), position);
90 } else if (width == 2) {
91 ecam.write16(static_cast<uint16_t>(value), position);
92 } else {
93 ecam.write32(value, position);
94 }
95 return true;
96}
97
98bool readFunction(Device* device, uint16_t offset, uint8_t width, uint32_t& value) {
99 return device && read(device->getPciBusPosition(), device->getPciDevicePosition(),
100 device->getPciFunctionNumber(), offset, width, value);
101}
102
103bool writeFunction(Device* device, uint16_t offset, uint8_t width, uint32_t value) {
104 return device && write(device->getPciBusPosition(), device->getPciDevicePosition(),
105 device->getPciFunctionNumber(), offset, width, value);
106}
107
108struct UpstreamBridge {
109 uint8_t bus;
110 uint8_t device;
111 uint8_t function;
112};
113
114struct BridgeWindow {
115 uint64_t base = 0;
116 uint64_t limit = 0;
117 bool open = false;
118
119 bool contains(uint64_t address, uint64_t bytes) const {
120 return open && address >= base && address <= limit && bytes - 1 <= limit - address;
121 }
122};
123
124struct BridgeWindows {
125 BridgeWindow io;
126 BridgeWindow memory;
127 BridgeWindow prefetch;
128};
129
130bool findUpstreamBridge(uint8_t childBus, UpstreamBridge& upstream) {
131 bool found = false;
132 for (uint32_t bus = host.firstBus; bus < childBus; ++bus) {
133 for (uint8_t device = 0; device < 32; ++device) {
134 uint32_t identity = 0;
135 if (!read(bus, device, 0, 0, 4, identity) || (identity & 0xffffU) == 0xffffU ||
136 !(identity & 0xffffU)) {
137 continue;
138 }
139 uint32_t header = 0;
140 if (!read(bus, device, 0, 0x0c, 4, header)) {
141 continue;
142 }
143 const uint8_t functions = (header & 0x00800000U) ? 8 : 1;
144 for (uint8_t function = 0; function < functions; ++function) {
145 if (function && (!read(bus, device, function, 0, 4, identity) ||
146 (identity & 0xffffU) == 0xffffU || !(identity & 0xffffU))) {
147 continue;
148 }
149 uint32_t classCode = 0;
150 uint32_t busNumbers = 0;
151 if (!read(bus, device, function, 0x08, 4, classCode) || (classCode >> 16) != 0x0604U ||
152 !read(bus, device, function, 0x18, 4, busNumbers) || (busNumbers & 0xffU) != bus ||
153 ((busNumbers >> 8) & 0xffU) != childBus || ((busNumbers >> 16) & 0xffU) < childBus) {
154 continue;
155 }
156 if (found) {
157 return false;
158 }
159 upstream = {static_cast<uint8_t>(bus), device, function};
160 found = true;
161 }
162 }
163 }
164 return found;
165}
166
167bool readBridgeWindows(const UpstreamBridge& bridge, BridgeWindows& windows) {
168 uint32_t header = 0, command = 0, io = 0, memory = 0, prefetch = 0;
169 uint32_t ioUpper = 0, prefetchBaseUpper = 0, prefetchLimitUpper = 0;
170 if (!read(bridge.bus, bridge.device, bridge.function, 0x0e, 1, header) || (header & 0x7f) != 1 ||
171 !read(bridge.bus, bridge.device, bridge.function, 0x04, 2, command) ||
172 !read(bridge.bus, bridge.device, bridge.function, 0x1c, 4, io) ||
173 !read(bridge.bus, bridge.device, bridge.function, 0x20, 4, memory) ||
174 !read(bridge.bus, bridge.device, bridge.function, 0x24, 4, prefetch) ||
175 !read(bridge.bus, bridge.device, bridge.function, 0x28, 4, prefetchBaseUpper) ||
176 !read(bridge.bus, bridge.device, bridge.function, 0x2c, 4, prefetchLimitUpper) ||
177 !read(bridge.bus, bridge.device, bridge.function, 0x30, 4, ioUpper)) {
178 return false;
179 }
180
181 const uint8_t ioBase = io & 0xff;
182 const uint8_t ioLimit = (io >> 8) & 0xff;
183 const uint8_t ioType = ioBase & 0x0f;
184 if ((command & 1U) && ioType == (ioLimit & 0x0f) && ioType <= 1) {
185 windows.io.base = uint64_t(ioBase & 0xf0) << 8;
186 windows.io.limit = (uint64_t(ioLimit & 0xf0) << 8) | 0xfff;
187 if (ioType == 1) {
188 windows.io.base |= uint64_t(ioUpper & 0xffff) << 16;
189 windows.io.limit |= uint64_t(ioUpper >> 16) << 16;
190 }
191 windows.io.open = windows.io.base <= windows.io.limit;
192 }
193
194 if (command & 2U) {
195 windows.memory.base = uint64_t(memory & 0xfff0) << 16;
196 windows.memory.limit = (uint64_t((memory >> 16) & 0xfff0) << 16) | 0xfffff;
197 windows.memory.open = windows.memory.base <= windows.memory.limit;
198
199 const uint16_t prefetchBase = prefetch & 0xffff;
200 const uint16_t prefetchLimit = prefetch >> 16;
201 const uint8_t prefetchType = prefetchBase & 0x0f;
202 if (prefetchType == (prefetchLimit & 0x0f) && prefetchType <= 1) {
203 windows.prefetch.base = uint64_t(prefetchBase & 0xfff0) << 16;
204 windows.prefetch.limit = (uint64_t(prefetchLimit & 0xfff0) << 16) | 0xfffff;
205 if (prefetchType == 1) {
206 windows.prefetch.base |= uint64_t(prefetchBaseUpper) << 32;
207 windows.prefetch.limit |= uint64_t(prefetchLimitUpper) << 32;
208 }
209 windows.prefetch.open = windows.prefetch.base <= windows.prefetch.limit;
210 }
211 }
212 return true;
213}
214
215bool bridgesForward(uint8_t bus, uint64_t address, uint64_t bytes, bool io, bool prefetch) {
216 while (bus != host.firstBus) {
217 UpstreamBridge bridge = {};
218 BridgeWindows windows;
219 if (!findUpstreamBridge(bus, bridge) || !readBridgeWindows(bridge, windows)) {
220 return false;
221 }
222 bool forwarded = false;
223 if (io) {
224 forwarded = windows.io.contains(address, bytes);
225 } else if (prefetch) {
226 forwarded =
227 windows.prefetch.contains(address, bytes) || windows.memory.contains(address, bytes);
228 } else {
229 forwarded = windows.memory.contains(address, bytes);
230 }
231 if (!forwarded) {
232 return false;
233 }
234 bus = bridge.bus;
235 }
236 return true;
237}
238} // namespace
239
240PciBus PciBus::m_Instance;
241
242PciBus::PciBus() {}
243
244PciBus::~PciBus() {}
245
246void PciBus::initialise() {
247 if (!VirtDeviceTree::pciHost(host)) {
248 return;
249 }
250 const size_t pages = host.size / PhysicalMemoryManager::getPageSize();
251 if (!PhysicalMemoryManager::instance().allocateRegion(
255 host.base)) {
256 ERROR("PCI: could not map ECAM host at " << Hex << host.base);
257 return;
258 }
259 configAvailable = true;
260 VirtPciWindow window;
261 while (windowCount < 8 && VirtDeviceTree::pciWindow(windowCount, window)) {
262 windows[windowCount] = window;
263 nextAddress[windowCount] = window.pciBase;
264 ++windowCount;
265 }
266 NOTICE("PCI: ECAM host " << Hex << host.base << ".." << host.base + host.size << Dec << " buses "
267 << host.firstBus << ".." << host.lastBus);
268}
269
270uint32_t PciBus::readConfigSpace(Device* device, uint8_t offset) {
271 uint32_t value = 0xffffffffU;
272 readConfig32(device, uint16_t(offset) * 4, value);
273 return value;
274}
275
276uint32_t PciBus::readConfigSpace(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset) {
277 uint32_t value = 0xffffffffU;
278 read(bus, device, function, uint16_t(offset) * 4, 4, value);
279 return value;
280}
281
282void PciBus::writeConfigSpace(Device* device, uint8_t offset, uint32_t value) {
283 writeConfig32(device, uint16_t(offset) * 4, value);
284}
285
286void PciBus::writeConfigSpace(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset,
287 uint32_t value) {
288 write(bus, device, function, uint16_t(offset) * 4, 4, value);
289}
290
291bool PciBus::readConfig8(Device* device, uint16_t offset, uint8_t& value) {
292 uint32_t data = 0;
293 if (!readFunction(device, offset, 1, data)) {
294 return false;
295 }
296 value = data;
297 return true;
298}
299
300bool PciBus::readConfig16(Device* device, uint16_t offset, uint16_t& value) {
301 uint32_t data = 0;
302 if (!readFunction(device, offset, 2, data)) {
303 return false;
304 }
305 value = data;
306 return true;
307}
308
309bool PciBus::readConfig32(Device* device, uint16_t offset, uint32_t& value) {
310 return readFunction(device, offset, 4, value);
311}
312
313bool PciBus::writeConfig8(Device* device, uint16_t offset, uint8_t value) {
314 return writeFunction(device, offset, 1, value);
315}
316
317bool PciBus::writeConfig16(Device* device, uint16_t offset, uint16_t value) {
318 return writeFunction(device, offset, 2, value);
319}
320
321bool PciBus::writeConfig32(Device* device, uint16_t offset, uint32_t value) {
322 return writeFunction(device, offset, 4, value);
323}
324
326 return false;
327}
328
329bool PciBus::hasDmaRemapping(Device*) const {
330 return false;
331}
332
334 return false;
335}
336
338 return false;
339}
340
341bool PciBus::isolatedDmaIdle(Device*) const {
342 return false;
343}
344
346 return false;
347}
348
349bool PciBus::hasDmaIsolation(Device*) const {
350 return false;
351}
352
353bool PciBus::mapDmaPage(Device* device, physical_uintptr_t physical, size_t bytes,
354 DmaMapping& mapping) {
355 if (!device || mapping.m_Device || !physical || !bytes || bytes > TargetInfo::getPageSize() ||
356 (physical & (TargetInfo::getPageSize() - 1)) ||
357 uint64_t{physical} > 0xffffffffULL - (bytes - 1)) {
358 return false;
359 }
360 mapping.m_Device = device;
361 mapping.m_Address = static_cast<uint32_t>(physical);
362 return true;
363}
364
365void PciBus::unmapDmaPage(Device*, uint16_t) {}
366
367bool PciBus::reserveLegacyInterrupt(uint8_t) {
368 return false;
369}
370
371bool PciBus::updateCommand(Device* device, uint16_t clearBits, uint16_t setBits) {
372 if (!device) {
373 return false;
374 }
375 LockGuard<Spinlock> guard(configLock);
376 uint16_t command = 0;
377 if (!readConfig16(device, 4, command)) {
378 return false;
379 }
380 const uint16_t desired = (command & ~clearBits) | setBits;
381 return writeConfig16(device, 4, desired) && readConfig16(device, 4, command) &&
382 command == desired;
383}
384
385bool PciBus::inspectFunction(Device* device, PciFunctionState::State& state,
386 bool requireLegacyInterrupt) {
387 FunctionConfig function{device};
388 if (!device ||
389 !PciFunctionState::inspect(function, state, false, device->getPhysicalFunction())) {
390 return false;
391 }
392 return !requireLegacyInterrupt || state.interruptPin;
393}
394
395bool PciBus::disableMessageInterrupts(Device* device, const PciFunctionState::State& state) {
396 FunctionConfig function{device};
397 return PciFunctionState::disableMessageInterrupts(function, state);
398}
399bool PciBus::enableMsi(Device* device, uint64_t address, uint16_t data) {
400 FunctionConfig function{device};
402 return device &&
403 PciFunctionState::inspect(function, state, false, device->getPhysicalFunction()) &&
404 PciFunctionState::enableMsi(function, state, address, data);
405}
406bool PciBus::disableMsi(Device* device) {
407 FunctionConfig function{device};
409 return device &&
410 PciFunctionState::inspect(function, state, false, device->getPhysicalFunction()) &&
411 PciFunctionState::disableMsi(function, state);
412}
413bool PciBus::enableMsix(Device* device, uint64_t address, uint32_t data) {
414 return enableMsixVectors(device, address, &data, 1);
415}
416bool PciBus::enableMsixVectors(Device* device, uint64_t address, const uint32_t* data, size_t count,
417 bool* touched, const uint64_t* addresses) {
418 if (touched) {
419 *touched = false;
420 }
421 FunctionConfig function{device};
424 if (!device || !data || !count ||
425 !PciFunctionState::inspect(function, state, false, device->getPhysicalFunction()) ||
426 !PciFunctionState::msixTable(function, state, table) || count > table.vectors) {
427 return false;
428 }
429 IoBase* io = PciFunctionState::msixTableIo(device, state, table, true);
430 if (touched && io) {
431 *touched = true;
432 }
433 return io && PciFunctionState::enableMsixVectors(function, state, *io, table.offset, address,
434 data, count, addresses);
435}
436bool PciBus::setMsixVectorMask(Device* device, size_t index, bool masked) {
437 FunctionConfig function{device};
440 if (!device ||
441 !PciFunctionState::inspect(function, state, false, device->getPhysicalFunction()) ||
442 !PciFunctionState::msixTable(function, state, table) || index >= table.vectors) {
443 return false;
444 }
445 IoBase* io = PciFunctionState::msixTableIo(device, state, table, false);
446 return io &&
447 PciFunctionState::setMsixVectorMask(function, state, *io, table.offset, index, masked);
448}
449bool PciBus::disableMsix(Device* device) {
450 FunctionConfig function{device};
453 if (!device ||
454 !PciFunctionState::inspect(function, state, false, device->getPhysicalFunction()) ||
455 !PciFunctionState::msixTable(function, state, table)) {
456 return false;
457 }
458 IoBase* io = PciFunctionState::msixTableIo(device, state, table, false);
459 return io && PciFunctionState::disableMsix(function, state, *io, table.offset);
460}
461
462bool PciBus::resourcesUnchanged(Device* device, const PciFunctionState::State& state) {
463 FunctionConfig function{device};
464 return device &&
465 PciFunctionState::resourcesUnchanged(function, state, device->getPhysicalFunction());
466}
467
468bool PciBus::translateAddress(uint64_t pciAddress, uint64_t bytes, bool io, uint64_t& cpuPhysical) {
469 return VirtDeviceTree::pciTranslate(pciAddress, bytes, io, cpuPhysical);
470}
471
472bool PciBus::busRange(uint8_t& first, uint8_t& last) {
473 if (!configAvailable) {
474 return false;
475 }
476 first = static_cast<uint8_t>(host.firstBus);
477 last = static_cast<uint8_t>(host.lastBus);
478 return true;
479}
480
481uint32_t PciBus::interruptRoute(uint8_t bus, uint8_t device, uint8_t function, uint8_t pin) {
482 if (!configAvailable || bus < host.firstBus || bus > host.lastBus || device >= 32 ||
483 function >= 8 || pin < 1 || pin > 4) {
484 return 0;
485 }
486 while (bus != host.firstBus) {
487 UpstreamBridge bridge = {};
488 if (!findUpstreamBridge(bus, bridge)) {
489 return 0;
490 }
491 pin = ((pin - 1 + device) & 3U) + 1;
492 bus = bridge.bus;
493 device = bridge.device;
494 function = bridge.function;
495 }
496 return VirtDeviceTree::pciInterrupt(bus, device, function, pin);
497}
498
499bool PciBus::assignBar(Device* device, uint8_t index, uint32_t low, uint32_t high, uint32_t maskLow,
500 uint32_t maskHigh) {
501 if (!device || index >= 6) {
502 return false;
503 }
504 const bool io = low & 1U;
505 const uint32_t type = (low >> 1) & 3U;
506 if (io && type) {
507 return false;
508 }
509 if (!io && type == 3) {
510 return false;
511 }
512 const bool wide = !io && type == 2;
513 if (wide && index == 5) {
514 return false;
515 }
516 const uint64_t width = io ? 0xffffULL
517 : type == 1 ? 0xfffffULL
518 : wide ? UINT64_MAX
519 : 0xffffffffULL;
520 const uint64_t addressMask = width & (io ? ~uint64_t{3} : ~uint64_t{15});
521 const uint64_t original = ((wide ? uint64_t(high) << 32 : 0) | low) & addressMask;
522 const uint64_t mask = ((wide ? uint64_t(maskHigh) << 32 : 0) | maskLow) & addressMask;
523 const uint64_t bytes = ((~mask) & width) + 1;
524 if (!bytes || (bytes & (bytes - 1))) {
525 return false;
526 }
527 const uint8_t bus = device->getPciBusPosition();
528 const bool behindBridge = bus != host.firstBus;
529 const bool prefetchable = !io && (low & 8U);
530 if (behindBridge && !original) {
531 // The host cursor does not reserve resources for sibling bridge windows.
532 return false;
533 }
534
535 LockGuard<Spinlock> guard(configLock);
536 for (size_t i = 0; i < windowCount; ++i) {
537 VirtPciWindow& window = windows[i];
538 if ((window.space == 0x01000000) != io || original < window.pciBase ||
539 original - window.pciBase >= window.size ||
540 bytes > window.size - (original - window.pciBase) ||
541 (behindBridge && !io && !prefetchable && window.space == 0x03000000) ||
542 (behindBridge && original && !bridgesForward(bus, original, bytes, io, prefetchable))) {
543 continue;
544 }
545 if (original) {
546 const uint64_t end = original + bytes;
547 if (nextAddress[i] < end) {
548 nextAddress[i] = end;
549 }
550 return true;
551 }
552 }
553 if (original) {
554 return false;
555 }
556
557 for (size_t i = 0; i < windowCount; ++i) {
558 const VirtPciWindow& window = windows[i];
559 if ((window.space == 0x01000000) != io) {
560 continue;
561 }
562 const uint64_t cursor = nextAddress[i] ? nextAddress[i] : bytes;
563 const uint64_t candidate = (cursor + bytes - 1) & ~(bytes - 1);
564 if (candidate < cursor || candidate < window.pciBase ||
565 candidate - window.pciBase >= window.size ||
566 bytes > window.size - (candidate - window.pciBase) || candidate > width ||
567 bytes - 1 > width - candidate) {
568 continue;
569 }
570 uint16_t command = 0;
571 if (!readConfig16(device, 4, command) || !writeConfig16(device, 4, command & ~3U)) {
572 return false;
573 }
574 const uint32_t assignedLow =
575 static_cast<uint32_t>((candidate & addressMask) | (low & ~uint32_t(addressMask)));
576 const uint16_t offset = 0x10 + index * 4;
577 uint32_t actualLow = 0;
578 uint32_t actualHigh = 0;
579 const bool assigned =
580 writeConfig32(device, offset, assignedLow) &&
581 (!wide || writeConfig32(device, offset + 4, candidate >> 32)) &&
582 readConfig32(device, offset, actualLow) &&
583 (!wide || readConfig32(device, offset + 4, actualHigh)) &&
584 (actualLow & uint32_t(addressMask)) == (assignedLow & uint32_t(addressMask)) &&
585 (!wide || actualHigh == static_cast<uint32_t>(candidate >> 32));
586 if (!assigned) {
587 writeConfig32(device, offset, low);
588 if (wide) {
589 writeConfig32(device, offset + 4, high);
590 }
591 }
592 if (!writeConfig16(device, 4, command)) {
593 return false;
594 }
595 if (!assigned) {
596 return false;
597 }
598 nextAddress[i] = candidate + bytes;
599 return true;
600 }
601 return false;
602}
Abstrace base class for hardware I/O capabilities.
Definition IoBase.h:32
Memory mapped I/O range.
Definition Pci.h:31
uint32_t readConfigSpace(Device *pDev, uint8_t offset)
bool isolatedDmaIdle(Device *device) const
bool translateAddress(uint64_t pciAddress, uint64_t bytes, bool io, uint64_t &cpuPhysical)
bool attachIsolatedDma(Device *device)
bool busRange(uint8_t &first, uint8_t &last)
void writeConfigSpace(Device *pDev, uint8_t offset, uint32_t data)
bool detachDisabledIsolatedDma(Device *device)
bool attachDmaRemapping(Device *device)
bool assignBar(Device *device, uint8_t index, uint32_t low, uint32_t high, uint32_t maskLow, uint32_t maskHigh)
void initialise()
bool detachIsolatedDma(Device *device)
bool mapDmaPage(Device *device, physical_uintptr_t physical, size_t bytes, DmaMapping &mapping)
static PhysicalMemoryManager & instance()
static constexpr size_t getPageSize() noexcept
Definition TargetInfo.h:40
@ Dec
Definition Log.h:126
@ Hex
Definition Log.h:124