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