The Pedigree Project 0.1
VirtioPci.cc
1/* Copyright (c) 2026, Pedigree Developers. SPDX-License-Identifier: ISC */
2#include "VirtioPci.h"
3#include "pedigree/kernel/Log.h"
4#include "pedigree/kernel/machine/Device.h"
5#include "pedigree/kernel/machine/IrqHandler.h"
6#include "pedigree/kernel/machine/IrqManager.h"
7#include "pedigree/kernel/machine/Machine.h"
8#include "pedigree/kernel/machine/Pci.h"
9#include "pedigree/kernel/panic.h"
10#include "pedigree/kernel/processor/IoBase.h"
11#include "pedigree/kernel/time/Time.h"
12
13#include "Virtqueue.h"
14
15using namespace Virtio;
16
17namespace {
18constexpr uint8_t VendorCapability = 9;
19constexpr uint8_t CommonConfig = 1;
20constexpr uint8_t NotifyConfig = 2;
21constexpr uint8_t IsrConfig = 3;
22constexpr uint8_t DeviceConfig = 4;
23constexpr uint64_t Version1 = 1ULL << 32;
24constexpr uint8_t Acknowledge = 1;
25constexpr uint8_t Driver = 2;
26constexpr uint8_t DriverOk = 4;
27constexpr uint8_t FeaturesOk = 8;
28constexpr uint8_t Failed = 128;
29} // namespace
30
31PciTransport::PciTransport(Device* device)
32 : m_Device(device),
33 m_Common(),
34 m_Notify(),
35 m_Isr(),
36 m_DeviceConfig(),
37 m_Original(),
38 m_Queues{},
39 m_NotifyOffsets{},
40 m_NotifyMultiplier(0),
41 m_Features(0),
42 m_NumQueues(0),
43 m_PciChanged(false),
44 m_Initialised(false),
45 m_Negotiated(false),
46 m_DmaActive(false),
47 m_UsingMsix(false) {}
48
49PciTransport::~PciTransport() {
50 if (m_Common.io && !reset()) {
51 panic("virtio: cannot stop DMA during transport teardown");
52 }
53 if (m_PciChanged &&
54 !PciBus::instance().updateCommand(m_Device, 0x407U, (m_Original.command & 3U) | 0x400U)) {
55 panic("virtio: cannot leave PCI function quiesced");
56 }
57}
58
59bool PciTransport::locateCapabilities() {
60 auto& pci = PciBus::instance();
61 uint16_t pciStatus = 0;
62 uint8_t cap = 0;
63 if (!pci.readConfig16(m_Device, 6, pciStatus) || !(pciStatus & 0x10U) ||
64 !pci.readConfig8(m_Device, 0x34, cap)) {
65 return false;
66 }
67 uint64_t visited = 0;
68 for (unsigned count = 0; cap && count < 48; ++count) {
69 if (cap < 0x40 || (cap & 3U) || (visited & (1ULL << (cap / 4)))) {
70 return false;
71 }
72 visited |= 1ULL << (cap / 4);
73 uint8_t id = 0, next = 0;
74 if (!pci.readConfig8(m_Device, cap, id) || !pci.readConfig8(m_Device, cap + 1, next)) {
75 return false;
76 }
77 if (id == VendorCapability) {
78 uint8_t length = 0, type = 0;
79 if (!pci.readConfig8(m_Device, cap + 2, length) || length < 16 || cap + length > 256 ||
80 !pci.readConfig8(m_Device, cap + 3, type)) {
81 return false;
82 }
83 if (type >= CommonConfig && type <= DeviceConfig) {
84 uint8_t bar = 0;
85 uint32_t offset = 0, bytes = 0;
86 if (!pci.readConfig8(m_Device, cap + 4, bar) || bar >= 6 ||
87 !pci.readConfig32(m_Device, cap + 8, offset) ||
88 !pci.readConfig32(m_Device, cap + 12, bytes) || !bytes) {
89 return false;
90 }
91 char name[] = "bar0";
92 name[3] = '0' + bar;
93 Device::Address* address = nullptr;
94 for (auto* candidate : m_Device->addresses()) {
95 if (candidate->m_Name == name && !candidate->m_IsIoSpace &&
96 uint64_t(offset) + bytes <= candidate->m_Size) {
97 address = candidate;
98 break;
99 }
100 }
101 if (!address) {
102 return false;
103 }
104 address->map();
105 if (!address->m_Io || address->m_Io->size() < uint64_t(offset) + bytes) {
106 return false;
107 }
108 Capability* target = nullptr;
109 switch (type) {
110 case CommonConfig:
111 target = &m_Common;
112 if (bytes < 56 || (offset & 3U)) {
113 return false;
114 }
115 break;
116 case NotifyConfig:
117 target = &m_Notify;
118 if (length < 20 || bytes < 2 || (offset & 1U) ||
119 !pci.readConfig32(m_Device, cap + 16, m_NotifyMultiplier)) {
120 return false;
121 }
122 break;
123 case IsrConfig:
124 target = &m_Isr;
125 break;
126 case DeviceConfig:
127 target = &m_DeviceConfig;
128 break;
129 }
130 if (target->io) {
131 return false;
132 }
133 *target = {address->m_Io, offset, bytes};
134 }
135 }
136 cap = next;
137 }
138 return !cap && m_Common.io && m_Notify.io && m_Isr.io;
139}
140
141bool PciTransport::status(uint8_t bits) {
142 if (!m_Common.io) {
143 return false;
144 }
145 const uint8_t before = m_Common.io->read8(m_Common.offset + 20);
146 if (before == 0xff || (before & Failed)) {
147 return false;
148 }
149 const uint8_t desired = before | bits;
150 m_Common.io->write8(desired, m_Common.offset + 20);
151 return (m_Common.io->read8(m_Common.offset + 20) & bits) == bits;
152}
153
154bool PciTransport::initialise() {
155 if (m_Initialised || !m_Device || m_Device->getPciVendorId() != 0x1af4 ||
156 m_Device->getPciDeviceId() < 0x1000 || m_Device->getPciDeviceId() > 0x107f) {
157 return false;
158 }
159 auto& pci = PciBus::instance();
160 if (!pci.inspectFunction(m_Device, m_Original, false)) {
161 ERROR("virtio-pci: invalid PCI function state");
162 return false;
163 }
164 m_PciChanged = true;
165 if (!pci.updateCommand(m_Device, 4U, 2U | 0x400U) ||
166 !pci.disableMessageInterrupts(m_Device, m_Original) ||
167 !pci.resourcesUnchanged(m_Device, m_Original)) {
168 ERROR("virtio-pci: could not prepare PCI function");
169 return false;
170 }
171 if (!locateCapabilities()) {
172 ERROR("virtio-pci: invalid or missing modern PCI capabilities");
173 return false;
174 }
175 if (!reset() || !status(Acknowledge) || !status(Driver)) {
176 ERROR("virtio-pci: device did not acknowledge reset or driver status");
177 return false;
178 }
179 m_NumQueues = m_Common.io->read16(m_Common.offset + 18);
180 if (!m_NumQueues) {
181 ERROR("virtio-pci: device exposes no queues");
182 return false;
183 }
184 m_Initialised = true;
185 return true;
186}
187
188bool PciTransport::negotiate(uint64_t supportedFeatures, uint64_t requiredFeatures) {
189 if (!m_Initialised || m_Negotiated || (requiredFeatures & ~supportedFeatures)) {
190 return false;
191 }
192 auto* io = m_Common.io;
193 const uint32_t base = m_Common.offset;
194 io->write32(0, base);
195 const uint64_t offeredLow = io->read32(base + 4);
196 io->write32(1, base);
197 const uint64_t offered = offeredLow | (uint64_t(io->read32(base + 4)) << 32);
198 if (!(offered & Version1) || (requiredFeatures & ~offered)) {
199 ERROR("virtio-pci: required modern features unavailable");
200 return false;
201 }
202 m_Features = offered & (supportedFeatures | Version1);
203 io->write32(0, base + 8);
204 io->write32(m_Features, base + 12);
205 io->write32(1, base + 8);
206 io->write32(m_Features >> 32, base + 12);
207 io->write16(0xffff, base + 16);
208 if (!status(FeaturesOk) || !(io->read8(base + 20) & FeaturesOk)) {
209 ERROR("virtio-pci: feature negotiation rejected");
210 return false;
211 }
212 m_Negotiated = true;
213 return true;
214}
215
216bool PciTransport::setupQueue(uint16_t index, Queue& queue) {
217 if (!m_Negotiated || m_DmaActive || index >= m_NumQueues || index >= MaxQueues ||
218 m_Queues[index]) {
219 return false;
220 }
221 auto* io = m_Common.io;
222 const uint32_t base = m_Common.offset;
223 io->write16(index, base + 22);
224 uint16_t maximum = io->read16(base + 24);
225 if (maximum < 2 || io->read16(base + 28)) {
226 ERROR("virtio-pci: queue " << Dec << index << " unavailable or already enabled" << Hex);
227 return false;
228 }
229 if (maximum > Queue::MaxDepth) {
230 maximum = Queue::MaxDepth;
231 }
232 uint16_t depth = 1;
233 while (depth <= maximum / 2)
234 depth *= 2;
235 const uint16_t notify = io->read16(base + 30);
236 const uint64_t notifyOffset = uint64_t(notify) * m_NotifyMultiplier;
237 if (notifyOffset + 2 > m_Notify.length || (notifyOffset & 1U) || !queue.initialise(depth, this)) {
238 ERROR("virtio-pci: queue " << Dec << index << " notification or DMA setup failed" << Hex);
239 return false;
240 }
241 io->write16(depth, base + 24);
242 io->write16(0xffff, base + 26);
243 io->write32(queue.descriptorAddress(), base + 32);
244 io->write32(queue.descriptorAddress() >> 32, base + 36);
245 io->write32(queue.availableAddress(), base + 40);
246 io->write32(queue.availableAddress() >> 32, base + 44);
247 io->write32(queue.usedAddress(), base + 48);
248 io->write32(queue.usedAddress() >> 32, base + 52);
249 FENCE();
250 io->write16(1, base + 28);
251 if (io->read16(base + 28) != 1) {
252 ERROR("virtio-pci: queue " << Dec << index << " enable rejected" << Hex);
253 return false;
254 }
255 m_NotifyOffsets[index] = notifyOffset;
256 m_Queues[index] = &queue;
257 queue.m_Attached = true;
258 return true;
259}
260
261bool PciTransport::configureMsix() {
262 m_UsingMsix = false;
263 if (!m_Original.msix) {
264 return true;
265 }
266 uint16_t control = 0;
267 if (!PciBus::instance().readConfig16(m_Device, m_Original.msix + 2, control)) {
268 return false;
269 }
270 if (!(control & 0x8000U)) {
271 return true;
272 }
273
274 auto* io = m_Common.io;
275 const uint32_t base = m_Common.offset;
276 io->write16(0, base + 16);
277 if (io->read16(base + 16) != 0) {
278 return false;
279 }
280 for (size_t index = 0; index < MaxQueues; ++index) {
281 if (!m_Queues[index]) {
282 continue;
283 }
284 io->write16(index, base + 22);
285 io->write16(0, base + 26);
286 if (io->read16(base + 26) != 0) {
287 return false;
288 }
289 }
290 m_UsingMsix = true;
291 return true;
292}
293
294irq_id_t PciTransport::registerInterrupt(IrqHandler* handler) {
295 if (!m_Negotiated || m_DmaActive || !handler) {
296 return 0;
297 }
298 IrqManager* manager = Machine::instance().getIrqManager();
299 const IrqPolicy policy = IrqPolicy::pciIntxThreaded();
300 irq_id_t id = manager->registerPciMessageIrqHandler(handler, m_Device, policy);
301 if (!id) {
302 return 0;
303 }
304 if (configureMsix()) {
305 return id;
306 }
307 if (!manager->unregisterHandler(id, handler)) {
308 panic("virtio-pci: could not retire rejected MSI-X route");
309 }
310 id = manager->registerPciIrqHandler(handler, m_Device, policy);
311 if (id && !configureMsix()) {
312 if (!manager->unregisterHandler(id, handler)) {
313 panic("virtio-pci: could not retire invalid INTx route");
314 }
315 return 0;
316 }
317 return id;
318}
319
320bool PciTransport::ready() {
321 if (!m_Negotiated || m_DmaActive) {
322 return false;
323 }
324 bool haveQueue = false;
325 for (auto* queue : m_Queues)
326 haveQueue |= queue != nullptr;
327 if (!haveQueue) {
328 return false;
329 }
330 auto& pci = PciBus::instance();
331 if (!pci.updateCommand(m_Device, 0, 6U | 0x400U)) {
332 return false;
333 }
334 for (auto* queue : m_Queues)
335 if (queue) {
336 queue->m_DmaArmed = true;
337 }
338 m_DmaActive = true;
339 if (!status(DriverOk) || !pci.updateCommand(m_Device, 0x400U, 6U)) {
340 if (!reset()) {
341 panic("virtio: device could not be reset after failed activation");
342 }
343 return false;
344 }
345 return true;
346}
347
348bool PciTransport::reset() {
349 if (!m_PciChanged) {
350 return true;
351 }
352 auto& pci = PciBus::instance();
353 const bool busStopped = pci.updateCommand(m_Device, 4U, 0x400U);
354 if (!m_Common.io) {
355 return busStopped;
356 }
357 m_Common.io->write8(0, m_Common.offset + 20);
358 const auto deadline = Time::getTicks() + Time::Multiplier::Second;
359 while (m_Common.io->read8(m_Common.offset + 20) != 0 && Time::getTicks() < deadline)
360 Time::delay(Time::Multiplier::Millisecond);
361 if (m_Common.io->read8(m_Common.offset + 20) != 0 || !busStopped) {
362 return false;
363 }
364 m_DmaActive = false;
365 m_Negotiated = false;
366 m_Initialised = false;
367 m_Features = 0;
368 m_UsingMsix = false;
369 for (auto*& queue : m_Queues) {
370 if (queue) {
371 queue->stop();
372 queue->m_DmaArmed = false;
373 queue->m_Attached = false;
374 queue = nullptr;
375 }
376 }
377 return true;
378}
379
380uint8_t PciTransport::readIsr() {
381 if (!m_Initialised) {
382 return 0;
383 }
384 return m_UsingMsix ? 1 : (m_Isr.io ? m_Isr.io->read8(m_Isr.offset) : 0);
385}
386
387void PciTransport::notify(uint16_t index) {
388 if (!m_Negotiated || index >= MaxQueues || !m_Queues[index]) {
389 return;
390 }
391 FENCE();
392 m_Notify.io->write16(index, m_Notify.offset + m_NotifyOffsets[index]);
393}
394
395bool PciTransport::writeDeviceConfig8(uint16_t offset, uint8_t value) {
396 if (!m_Initialised || !m_DeviceConfig.io || offset >= m_DeviceConfig.length) {
397 return false;
398 }
399 m_DeviceConfig.io->write8(value, m_DeviceConfig.offset + offset);
400 return true;
401}
402
403bool PciTransport::readConfig(uint16_t offset, unsigned width, uint64_t& value) {
404 if (!m_Initialised || !m_DeviceConfig.io || uint32_t(offset) + width > m_DeviceConfig.length) {
405 return false;
406 }
407 const uint32_t common = m_Common.offset;
408 const uint32_t device = m_DeviceConfig.offset + offset;
409 for (unsigned attempt = 0; attempt < 8; ++attempt) {
410 const uint8_t generation = m_Common.io->read8(common + 21);
411 switch (width) {
412 case 1:
413 value = m_DeviceConfig.io->read8(device);
414 break;
415 case 2:
416 value = m_DeviceConfig.io->read16(device);
417 break;
418 case 4:
419 value = m_DeviceConfig.io->read32(device);
420 break;
421 case 8:
422 value = m_DeviceConfig.io->read32(device) |
423 (uint64_t(m_DeviceConfig.io->read32(device + 4)) << 32);
424 break;
425 default:
426 return false;
427 }
428 if (generation == m_Common.io->read8(common + 21)) {
429 return true;
430 }
431 }
432 return false;
433}
434
435bool PciTransport::readDeviceConfig8(uint16_t offset, uint8_t& value) {
436 uint64_t result = 0;
437 if (!readConfig(offset, 1, result)) {
438 return false;
439 }
440 value = result;
441 return true;
442}
443bool PciTransport::readDeviceConfig16(uint16_t offset, uint16_t& value) {
444 uint64_t result = 0;
445 if (!readConfig(offset, 2, result)) {
446 return false;
447 }
448 value = result;
449 return true;
450}
451bool PciTransport::readDeviceConfig32(uint16_t offset, uint32_t& value) {
452 uint64_t result = 0;
453 if (!readConfig(offset, 4, result)) {
454 return false;
455 }
456 value = result;
457 return true;
458}
459bool PciTransport::readDeviceConfig64(uint16_t offset, uint64_t& value) {
460 return readConfig(offset, 8, value);
461}
uint16_t getPciDeviceId()
Definition Device.h:228
uint16_t getPciVendorId()
Definition Device.h:224
virtual uint8_t read8(size_t offset=0)=0
virtual uint32_t read32(size_t offset=0)=0
virtual void write8(uint8_t value, size_t offset=0)=0
virtual void write16(uint16_t value, size_t offset=0)=0
virtual uint16_t read16(size_t offset=0)=0
virtual bool unregisterHandler(irq_id_t Id, IrqHandlerBase *handler)=0
virtual irq_id_t registerPciIrqHandler(IrqHandler *handler, Device *pDevice, const IrqPolicy &policy)=0
virtual irq_id_t registerPciMessageIrqHandler(IrqHandler *handler, Device *pDevice, const IrqPolicy &intxFallbackPolicy)
Definition IrqManager.cc:25
void EXPORTED_PUBLIC panic(const char *msg) NORETURN
Definition panic.cc:118
@ Dec
Definition Log.h:126
@ Hex
Definition Log.h:124