The Pedigree Project 0.1
Sriov.cc
1/* Copyright (c) 2026, Pedigree Developers. SPDX-License-Identifier: ISC */
2#include "pedigree/kernel/LockGuard.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/machine/PciFirmware.h"
7#include "pedigree/kernel/machine/PciVirtualFunctions.h"
8#include "pedigree/kernel/panic.h"
9#include "pedigree/kernel/process/Mutex.h"
10#include "pedigree/kernel/time/Time.h"
11#include "pedigree/kernel/utilities/new"
12
13#include "Resources.h"
14
15namespace {
16Mutex g_Lock;
17
18struct Config {
19 Device* device;
20 bool read16(uint16_t offset, uint16_t& value) {
21 return PciBus::instance().readConfig16(device, offset, value);
22 }
23 bool read32(uint16_t offset, uint32_t& value) {
24 return PciBus::instance().readConfig32(device, offset, value);
25 }
26 bool write16(uint16_t offset, uint16_t value) {
27 return PciBus::instance().writeConfig16(device, offset, value);
28 }
29 bool write32(uint16_t offset, uint32_t value) {
30 return PciBus::instance().writeConfig32(device, offset, value);
31 }
32};
33
34void settle() {
35 const Time::Timestamp start = Time::getTicks();
36 constexpr Time::Timestamp delay = 100 * Time::Multiplier::Millisecond;
37 for (;;) {
38 const Time::Timestamp elapsed = Time::getTicks() - start;
39 if (elapsed >= delay) {
40 return;
41 }
42 (void)Time::delay(delay - elapsed);
43 }
44}
45
46bool readHeader(Device* device, PciBus::ConfigSpace& header) {
47 auto* bytes = reinterpret_cast<uint8_t*>(&header);
48 for (size_t offset = 0; offset < sizeof(header); offset += 4) {
49 uint32_t value = 0;
50 if (!PciBus::instance().readConfig32(device, offset, value)) {
51 return false;
52 }
53 for (unsigned byte = 0; byte < 4; ++byte) {
54 bytes[offset + byte] = value >> (byte * 8);
55 }
56 }
57 return true;
58}
59
60bool busReachable(Device* pf, uint8_t bus) {
61 auto& pci = PciBus::instance();
62 uint8_t first = 0, last = 0;
63 const auto* root = PciFirmware::rootForBus(pf->getPciBusPosition());
64 if (!pci.busRange(first, last) || bus < first || bus > last || !root ||
65 PciFirmware::rootForBus(bus) != root) {
66 return false;
67 }
68 for (Device* parent = pf->getParent(); parent; parent = parent->getParent()) {
69 if (parent->getPciClassCode() != 6 || parent->getPciSubclassCode() != 4 ||
70 (parent->getPciConfigHeader().header_type & 0x7f) != 1) {
71 continue;
72 }
73 uint32_t buses = 0;
74 if (!pci.readConfig32(parent, 0x18, buses) || uint8_t(buses) != parent->getPciBusPosition() ||
75 bus < uint8_t(buses >> 8) || bus > uint8_t(buses >> 16)) {
76 return false;
77 }
78 }
79 return true;
80}
81} // namespace
82
83PciVirtualFunctions::PciVirtualFunctions(Device* pf) : m_Pf(pf) {}
84
85PciVirtualFunctions::~PciVirtualFunctions() {
86 if (!disable()) {
87 panic("SR-IOV: VF group destroyed with active users");
88 }
89}
90
91bool PciVirtualFunctions::enable(size_t requested) {
92 LockGuard<Mutex> guard(g_Lock);
93 auto& pci = PciBus::instance();
94 if (!m_Pf || m_Count || m_PlannedCount || !requested || requested > MaxFunctions ||
95 !m_Pf->getParent() || m_Pf->getParent()->getSpecificType() != "pci" ||
96 m_Pf->getPhysicalFunction() || m_Pf->getPciBusPosition() > 255 ||
97 m_Pf->getPciDevicePosition() > 31 || m_Pf->getPciFunctionNumber() > 7) {
98 return false;
99 }
101 uint16_t command = 0;
102 Config config{m_Pf};
103 PciSriov::State state;
104 PciSriov::Plan plan;
105 if (!pci.readConfig16(m_Pf, 4, command) || (command & 4U) ||
106 pci.findExtendedCapability(m_Pf, PciSriov::CapabilityId, capability) !=
107 PciExtendedCapabilities::FindResult::Found ||
108 !PciSriov::read(config, capability.offset, capability.next, state) ||
109 !PciSriov::plan(m_Pf->getPciBusPosition(), m_Pf->getPciDevicePosition(),
110 m_Pf->getPciFunctionNumber(), state, requested, plan)) {
111 return false;
112 }
113 m_Original = state;
114 m_Offset = capability.offset;
115 m_NextCapability = capability.next;
116 state.numVfs = requested;
117 for (size_t i = 0; i < requested; ++i) {
118 uint8_t bus = 0, device = 0, function = 0;
119 if (!PciSriov::vfRoutingId(plan.pfBus, plan.pfDevice, plan.pfFunction, state, i, bus, device,
120 function) ||
121 !busReachable(m_Pf, bus)) {
122 reclaim();
123 return false;
124 }
125 Device* vf = new Device();
126 if (!vf) {
127 reclaim();
128 return false;
129 }
130 m_Functions[m_PlannedCount++] = vf;
131 vf->setPciPosition(bus, device, function);
132 vf->setParent(m_Pf->getParent());
133 vf->setVirtualFunction(m_Pf, i);
134 uint32_t identity = 0;
135 uint32_t header = 0;
136 // An enabled VF also returns FFFF:FFFF for its IDs, but has a real header.
137 if (!pci.readConfig32(vf, 0, identity) || !pci.readConfig32(vf, 0xc, header) ||
138 identity != 0xffffffffU || header != 0xffffffffU) {
139 reclaim();
140 return false;
141 }
142 }
143
144 // VF BAR sizes may depend on the selected system page size.
145 m_Touched = true;
146 PciSriov::Plan selected;
147 if (!PciSriov::writeVerified32(config, m_Offset + 0x20, PciSriov::PageSize4K) ||
148 !PciSriov::read(config, m_Offset, m_NextCapability, state) ||
149 !PciSriov::plan(plan.pfBus, plan.pfDevice, plan.pfFunction, state, requested, selected) ||
150 selected.firstRid != plan.firstRid || selected.lastRid != plan.lastRid ||
151 selected.firstVfOffset != plan.firstVfOffset || selected.vfStride != plan.vfStride ||
152 state.totalVfs != m_Original.totalVfs || state.vfDeviceId != m_Original.vfDeviceId ||
153 PciSriov::probeBars(config, m_Offset, m_NextCapability, state, m_Bars) !=
154 PciSriov::Result::Success) {
155 reclaim();
156 return false;
157 }
158 plan = selected;
159 for (size_t i = 0; i < 6; ++i) {
160 auto& bar = m_Bars.bars[i];
161 if (!bar.perVfBytes) {
162 continue;
163 }
164 if (bar.io) {
165 reclaim();
166 return false;
167 }
168 uint64_t base = bar.base;
169 if (!(base && PciResources::reserve(m_Pf, base, bar.apertureBytes, bar.prefetchable)) &&
170 !PciResources::allocate(m_Pf, bar.apertureBytes, bar.perVfBytes, bar.prefetchable,
171 bar.wide ? ~uint64_t{0} : 0xffffffffU, base)) {
172 WARNING("SR-IOV: no firmware aperture for VF BAR " << Dec << i);
173 reclaim();
174 return false;
175 }
176 m_Reserved[i] = true;
177 bar.base = base;
178 const uint32_t low = uint32_t(base) | (m_Original.bars[i] & 15U);
179 if (!PciSriov::writeVerified32(config, m_Offset + 0x24 + 4 * i, low) ||
180 (bar.wide &&
181 !PciSriov::writeVerified32(config, m_Offset + 0x28 + 4 * i, uint32_t(base >> 32)))) {
182 reclaim();
183 return false;
184 }
185 }
186 m_EnableAttempted = true;
187 if (PciSriov::enable(config, m_Offset, m_NextCapability, plan) != PciSriov::Result::Success) {
188 reclaim();
189 return false;
190 }
191 settle();
192
193 for (size_t i = 0; i < requested; ++i) {
194 Device* vf = m_Functions[i];
195 PciBus::ConfigSpace header{};
196 if (!readHeader(vf, header) || (header.header_type & 0x7f) ||
197 !((header.vendor == 0xffff && header.device == 0xffff) ||
198 (header.vendor == m_Pf->getPciVendorId() && header.device == m_Original.vfDeviceId))) {
199 WARNING("SR-IOV: VF " << Dec << i << " configuration unavailable after enable");
200 reclaim();
201 return false;
202 }
203 m_Present[i] = true;
204 uint16_t vfCommand = 0;
205 if (!pci.updateCommand(vf, 4U, 0) || !pci.readConfig16(vf, 4, vfCommand) || (vfCommand & 4U)) {
206 reclaim();
207 return false;
208 }
209 // SR-IOV 3.4.1.1/3.4.1.2 place the VF identity in its PF, not offset zero.
210 header.vendor = m_Pf->getPciVendorId();
211 header.device = m_Original.vfDeviceId;
212 header.command = vfCommand;
213 vf->setPciIdentifiers(header.class_code, header.subclass, header.vendor, header.device,
214 header.progif);
215 vf->setPciConfigHeader(header);
216 vf->setSpecificType(String("PCI virtual function"));
217 for (size_t bar = 0; bar < 6; ++bar) {
218 const auto& aperture = m_Bars.bars[bar];
219 if (!aperture.perVfBytes) {
220 continue;
221 }
222 const uint64_t base = aperture.base + i * aperture.perVfBytes;
223 uint64_t cpu = 0;
224 if (aperture.perVfBytes > ~size_t{0} ||
225 !pci.translateAddress(base, aperture.perVfBytes, false, cpu) || cpu > ~uintptr_t{0}) {
226 reclaim();
227 return false;
228 }
229 char name[] = {'b', 'a', 'r', static_cast<char>('0' + bar), 0};
230 auto* address = new Device::Address(String(name), static_cast<uintptr_t>(cpu),
231 static_cast<size_t>(aperture.perVfBytes), false);
232 if (!address || !vf->addresses().tryReserve(vf->addresses().count() + 1)) {
233 delete address;
234 reclaim();
235 return false;
236 }
237 vf->addresses().pushBack(address);
238 }
239 if (!pci.attachIsolatedDma(vf) || !pci.hasDmaIsolation(vf)) {
240 WARNING("SR-IOV: cannot isolate VF " << Dec << vf->getPciBusPosition() << ":"
241 << vf->getPciDevicePosition() << "."
242 << vf->getPciFunctionNumber());
243 reclaim();
244 return false;
245 }
246 }
247 m_Count = requested;
248 return true;
249}
250
251Device* PciVirtualFunctions::function(size_t index) const {
252 LockGuard<Mutex> guard(g_Lock);
253 return index < m_Count ? m_Functions[index] : nullptr;
254}
255
256size_t PciVirtualFunctions::count() const {
257 LockGuard<Mutex> guard(g_Lock);
258 return m_Count;
259}
260
261bool PciVirtualFunctions::disable() {
262 LockGuard<Mutex> guard(g_Lock);
263 auto& pci = PciBus::instance();
264 for (size_t i = 0; i < m_Count; ++i) {
265 Device* vf = m_Functions[i];
266 uint16_t command = 0;
267 if (vf->getNumChildren() || !pci.readConfig16(vf, 4, command) || (command & 4U) ||
268 (pci.hasDmaIsolation(vf) && !pci.isolatedDmaIdle(vf)) ||
269 (pci.hasDmaRemapping(vf) && !pci.hasDmaIsolation(vf))) {
270 return false;
271 }
272 }
273 reclaim();
274 return true;
275}
276
277void PciVirtualFunctions::reclaim() {
278 auto& pci = PciBus::instance();
279 Config config{m_Pf};
280 if (m_Touched) {
281 bool quiescent = true;
282 for (size_t i = 0; i < m_PlannedCount; ++i) {
283 if (m_Present[i] && !pci.updateCommand(m_Functions[i], 4U, 0)) {
284 quiescent = false;
285 }
286 }
287 uint16_t control = 0;
288 if (!config.read16(m_Offset + 8, control) ||
289 !PciSriov::writeVerified16(config, m_Offset + 8, control & ~PciSriov::VfControl) ||
290 !PciSriov::writeVerified16(config, m_Offset + 0x10, 0)) {
291 panic("SR-IOV: cannot disable VF decoding");
292 }
293 if (m_EnableAttempted) {
294 settle();
295 }
296 if (!quiescent) {
297 panic("SR-IOV: cannot verify VF DMA quiescence");
298 }
299 }
300 for (size_t i = 0; i < m_PlannedCount; ++i) {
301 Device* vf = m_Functions[i];
302 if (vf->getNumChildren() || (pci.hasDmaIsolation(vf) && !pci.detachDisabledIsolatedDma(vf)) ||
303 pci.hasDmaRemapping(vf)) {
304 panic("SR-IOV: VF still owns DMA resources");
305 }
306 delete vf;
307 m_Functions[i] = nullptr;
308 m_Present[i] = false;
309 }
310 if (m_Touched) {
311 for (size_t i = 0; i < 6; ++i) {
312 if (!PciSriov::writeVerified32(config, m_Offset + 0x24 + 4 * i, m_Original.bars[i])) {
313 panic("SR-IOV: cannot restore VF BARs");
314 }
315 }
316 if (!PciSriov::writeVerified32(config, m_Offset + 0x20, m_Original.systemPageSize) ||
317 !PciSriov::writeVerified16(config, m_Offset + 0x10, m_Original.numVfs) ||
318 !PciSriov::writeVerified16(config, m_Offset + 8, m_Original.control)) {
319 panic("SR-IOV: cannot restore PF state");
320 }
321 }
322 for (size_t i = 0; i < 6; ++i) {
323 if (m_Reserved[i] &&
324 !PciResources::release(m_Pf, m_Bars.bars[i].base, m_Bars.bars[i].apertureBytes)) {
325 panic("SR-IOV: lost VF BAR reservation");
326 }
327 m_Reserved[i] = false;
328 }
329 m_Count = 0;
330 m_PlannedCount = 0;
331 m_Touched = false;
332 m_EnableAttempted = false;
333 m_Offset = 0;
334 m_NextCapability = 0;
335 m_Original = {};
336 m_Bars = {};
337}
338
339bool PciVirtualFunctions::bar(Device* vf, uint8_t index, uint64_t& pciBase, uint64_t& bytes) {
340 LockGuard<Mutex> guard(g_Lock);
341 auto& pci = PciBus::instance();
342 Device* pf = vf ? vf->getPhysicalFunction() : nullptr;
344 PciSriov::State state;
345 Config config{pf};
346 if (!pf || index >= 6 ||
347 pci.findExtendedCapability(pf, PciSriov::CapabilityId, capability) !=
348 PciExtendedCapabilities::FindResult::Found ||
349 !PciSriov::read(config, capability.offset, capability.next, state) ||
350 (state.control & PciSriov::VfControl) != PciSriov::VfControl ||
351 state.systemPageSize != PciSriov::PageSize4K ||
352 vf->getVirtualFunctionIndex() >= state.numVfs) {
353 return false;
354 }
355 uint8_t bus = 0, device = 0, function = 0;
356 if (!PciSriov::vfRoutingId(pf->getPciBusPosition(), pf->getPciDevicePosition(),
357 pf->getPciFunctionNumber(), state, vf->getVirtualFunctionIndex(), bus,
358 device, function) ||
359 bus != vf->getPciBusPosition() || device != vf->getPciDevicePosition() ||
360 function != vf->getPciFunctionNumber()) {
361 return false;
362 }
363 for (unsigned i = 0; i < index; ++i) {
364 if (!(state.bars[i] & 1U) && (state.bars[i] & 6U) == 4) {
365 if (++i == index) {
366 return false;
367 }
368 }
369 }
370 const uint32_t low = state.bars[index];
371 const unsigned type = (low >> 1) & 3U;
372 const bool wide = type == 2;
373 if ((low & 1U) || (type != 0 && !wide) || (wide && index == 5)) {
374 return false;
375 }
376 uint32_t raw = 0;
377 if (!pci.readConfig32(vf, 0x10 + 4 * index, raw) || raw ||
378 (wide && (!pci.readConfig32(vf, 0x14 + 4 * index, raw) || raw))) {
379 return false;
380 }
381 char name[] = {'b', 'a', 'r', static_cast<char>('0' + index), 0};
382 Device::Address* address = nullptr;
383 for (Device::Address* candidate : vf->addresses()) {
384 if (candidate && candidate->m_Name == name && !candidate->m_IsIoSpace) {
385 if (address) {
386 return false;
387 }
388 address = candidate;
389 }
390 }
391 const uint64_t size = address ? address->m_Size : 0;
392 const uint64_t base = (uint64_t(wide ? state.bars[index + 1] : 0) << 32) | (low & ~15U);
393 const uint64_t maximum = wide ? ~uint64_t{0} : 0xffffffffU;
394 if (!base || !size || (size & (size - 1)) || (base & (size - 1)) ||
395 size > ~uint64_t{0} / state.totalVfs || size * state.totalVfs - 1 > maximum - base) {
396 return false;
397 }
398 const uint64_t slice = base + vf->getVirtualFunctionIndex() * size;
399 uint64_t cpu = 0;
400 if (!pci.translateAddress(slice, size, false, cpu) || cpu != address->m_Address) {
401 return false;
402 }
403 pciBase = slice;
404 bytes = size;
405 return true;
406}
size_t getNumChildren()
Definition Device.cc:143
uint16_t getPciVendorId()
Definition Device.h:224
uint32_t getPciFunctionNumber()
Definition Device.h:244
uint32_t getPciDevicePosition()
Definition Device.h:240
void setPciConfigHeader(const PciBus::ConfigSpace &space)
Definition Device.h:208
virtual Vector< Address * > & addresses()
Definition Device.h:266
void setPciIdentifiers(uint8_t classCode, uint8_t subclassCode, uint16_t vendorId, uint16_t deviceId, uint8_t progIf)
Definition Device.h:199
virtual void setSpecificType(String str)
Definition Device.h:187
virtual const String & getSpecificType()
Definition Device.h:183
void setVirtualFunction(Device *physicalFunction, size_t index)
Definition Device.h:255
Device * getParent() const
Definition Device.h:166
uint32_t getPciBusPosition()
Definition Device.h:236
void setParent(Device *p)
Definition Device.h:170
void setPciPosition(uint32_t bus, uint32_t device, uint32_t func)
Definition Device.h:192
Definition Mutex.h:56
void EXPORTED_PUBLIC panic(const char *msg) NORETURN
Definition panic.cc:117
@ Dec
Definition Log.h:126