The Pedigree Project 0.1
VirtioBlkController.cc
1/* Copyright (c) 2026, Pedigree Developers. SPDX-License-Identifier: ISC */
2#include "VirtioBlkController.h"
3#include "pedigree/kernel/LockGuard.h"
4#include "pedigree/kernel/Log.h"
5#include "pedigree/kernel/TargetInfo.h"
6#include "pedigree/kernel/machine/IrqManager.h"
7#include "pedigree/kernel/machine/Machine.h"
8#include "pedigree/kernel/panic.h"
9#include "pedigree/kernel/process/TerminationDeferral.h"
10#include "pedigree/kernel/processor/PhysicalMemoryManager.h"
11#include "pedigree/kernel/processor/VirtualAddressSpace.h"
12#include "pedigree/kernel/time/Time.h"
13#include "pedigree/kernel/utilities/new"
14#include "pedigree/kernel/utilities/utility.h"
15
16#include <stddef.h>
17
18#include "VirtioBlkDisk.h"
19#include "modules/drivers/common/InterruptProbe.h"
20
21namespace {
22constexpr uint64_t FeatureReadOnly = uint64_t{1} << 5;
23constexpr uint64_t FeatureBlockSize = uint64_t{1} << 6;
24constexpr uint64_t FeatureFlush = uint64_t{1} << 9;
25constexpr uint32_t RequestRead = 0;
26constexpr uint32_t RequestWrite = 1;
27constexpr uint32_t RequestFlush = 4;
28
29struct VirtioBlkRequest {
30 uint32_t type;
31 uint32_t reserved;
32 uint64_t sector;
33 uint8_t status;
34} PACKED;
35static_assert(offsetof(VirtioBlkRequest, status) == 16);
36} // namespace
37
38VirtioBlkController::VirtioBlkController(Device* pci)
39 : m_Pci(pci),
40 m_Transport(pci),
41 m_Control("Virtio block request"),
42 m_Data("Virtio block data"),
43 m_Completion(0, false),
44 m_Irq(0),
45 m_Bytes(0),
46 m_SectorBytes(512),
47 m_ExpectedUsed(0),
48 m_InterruptCompletions(0),
49 m_ReadOnly(false),
50 m_Flush(false),
51 m_TransportInitialised(false),
52 m_Ready(false),
53 m_CommandSeen(false),
54 m_CommandValid(false),
55 m_Stopping(false),
56 m_Failed(false),
57 m_Shutdown(false) {
58 setSpecificType(String("virtio-blk-controller"));
59}
60
61VirtioBlkController::~VirtioBlkController() {
62 shutdown();
63}
64
65bool VirtioBlkController::initialiseController() {
66 if (!m_Pci || m_Pci->getPciVendorId() != 0x1af4 ||
67 (m_Pci->getPciDeviceId() != 0x1042 && m_Pci->getPciDeviceId() != 0x1001) ||
68 !m_Transport.initialise()) {
69 return false;
70 }
71 m_TransportInitialised = true;
72 if (!m_Transport.negotiate(FeatureReadOnly | FeatureBlockSize | FeatureFlush) ||
73 !m_Transport.setupQueue(0, m_Queue) || m_Queue.depth() < 3) {
74 return false;
75 }
76
77 const uint64_t features = m_Transport.features();
78 m_ReadOnly = features & FeatureReadOnly;
79 m_Flush = features & FeatureFlush;
80 uint64_t sectors = 0;
81 if (!m_Transport.readDeviceConfig64(0, sectors) || !sectors ||
82 sectors > static_cast<uint64_t>(~size_t{0} / 512)) {
83 return false;
84 }
85 m_Bytes = static_cast<size_t>(sectors * 512);
86 if (features & FeatureBlockSize) {
87 uint32_t blockSize = 0;
88 if (!m_Transport.readDeviceConfig32(20, blockSize)) {
89 return false;
90 }
91 m_SectorBytes = blockSize;
92 }
93 const size_t page = TargetInfo::getPageSize();
94 if (m_SectorBytes < 512 || (m_SectorBytes & (m_SectorBytes - 1)) || m_SectorBytes > page ||
95 page % m_SectorBytes || m_Bytes % m_SectorBytes) {
96 return false;
97 }
98
99 auto& memory = PhysicalMemoryManager::instance();
101 if (!memory.allocateRegion(m_Control, 1, PhysicalMemoryManager::continuous, flags) ||
102 !memory.allocateRegion(m_Data, 1, PhysicalMemoryManager::continuous, flags)) {
103 return false;
104 }
105 ByteSet(m_Control.virtualAddress(), 0, page);
106 ByteSet(m_Data.virtualAddress(), 0, page);
107
108 m_Irq = m_Transport.registerInterrupt(this);
109 if (!m_Irq) {
110 ERROR("Virtio block: could not register PCI interrupt");
111 return false;
112 }
113 if (!m_Transport.ready()) {
114 return false;
115 }
116 m_Ready = true;
117
118 if (!InterruptProbe::run([&] { return command(RequestRead, 0, nullptr, m_SectorBytes, 2, true); },
119 [&] {
120 LockGuard<Mutex> guard(m_IrqLock);
121 return m_InterruptCompletions;
122 })) {
123 ERROR("Virtio block: interrupt delivery probe failed");
124 return false;
125 }
126
127 auto* disk = new VirtioBlkDisk(this);
128 addChild(disk);
129 disk->publishEndpoint();
130 NOTICE("Virtio block: " << Dec << sectors << " sectors, " << m_Bytes << " bytes, "
131 << m_SectorBytes << "-byte blocks, "
132 << (m_ReadOnly ? "read-only" : "read-write") << Hex);
133 return true;
134}
135
136void VirtioBlkController::drainCompletions(bool fromInterrupt) {
137 LockGuard<Mutex> guard(m_IrqLock);
138 if (m_Stopping) {
139 return;
140 }
141 Virtio::Completion completion{};
142 while (m_Queue.pop(completion)) {
143 m_CommandValid = completion.cookie == this && completion.length >= m_ExpectedUsed;
144 m_CommandSeen = true;
145 if (fromInterrupt) {
146 ++m_InterruptCompletions;
147 }
148 m_Completion.release();
149 }
150}
151
153 LockGuard<Mutex> guard(m_IrqLock);
154 if (m_Stopping) {
156 }
157 const uint8_t isr = m_Transport.readIsr();
158 if (!isr) {
159 return IrqDisposition::NotHandled;
160 }
161 if (isr & 1U) {
162 Virtio::Completion completion{};
163 while (m_Queue.pop(completion)) {
164 m_CommandValid = completion.cookie == this && completion.length >= m_ExpectedUsed;
165 m_CommandSeen = true;
166 ++m_InterruptCompletions;
167 m_Completion.release();
168 }
169 }
170 return IrqDisposition::Handled;
171}
172
173void VirtioBlkController::failController() {
174 {
175 LockGuard<Mutex> guard(m_IrqLock);
176 if (m_Failed) {
177 return;
178 }
179 m_Stopping = true;
180 m_Failed = true;
181 }
182 if (!m_Transport.reset()) {
183 panic("Virtio block: device did not stop DMA after command failure");
184 }
185 m_Ready = false;
186 m_Queue.stop();
187}
188
189bool VirtioBlkController::command(uint32_t type, uint64_t sector, void* buffer, size_t bytes,
190 size_t timeoutSeconds, bool interruptProbe) {
191 TerminationDeferral lifetime;
192 LockGuard<Mutex> serial(m_CommandLock);
193 if (!m_Ready || m_Failed || (bytes && bytes > TargetInfo::getPageSize()) ||
194 (type == RequestWrite && (!buffer || m_ReadOnly)) ||
195 (type == RequestFlush && (bytes || buffer)) || (type != RequestFlush && !bytes)) {
196 return false;
197 }
198
199 auto* request = static_cast<VirtioBlkRequest*>(m_Control.virtualAddress());
200 request->type = HOST_TO_LITTLE32(type);
201 request->reserved = 0;
202 request->sector = HOST_TO_LITTLE64(sector);
203 request->status = 0xff;
204 if (type == RequestWrite) {
205 MemoryCopy(m_Data.virtualAddress(), buffer, bytes);
206 }
207
208 Virtio::Buffer descriptors[3] = {
209 {m_Control.physicalAddress(), 16, false},
210 {m_Data.physicalAddress(), static_cast<uint32_t>(bytes), type == RequestRead},
211 {m_Control.physicalAddress() + 16, 1, true},
212 };
213 const size_t descriptorCount = type == RequestFlush ? 2 : 3;
214 if (type == RequestFlush) {
215 descriptors[1] = descriptors[2];
216 }
217 {
218 LockGuard<Mutex> guard(m_IrqLock);
219 m_CommandSeen = false;
220 m_CommandValid = false;
221 m_ExpectedUsed = type == RequestRead ? bytes + 1 : 1;
222 [[maybe_unused]] const size_t stale = m_Completion.drainAvailable();
223 }
224 FENCE();
225 if (!m_Queue.submit(descriptors, descriptorCount, this)) {
226 failController();
227 return false;
228 }
229 m_Transport.notify(0);
230
231 const auto deadline = Time::getTicks() + timeoutSeconds * Time::Multiplier::Second;
232 bool first = true;
233 for (;;) {
234 const bool grace = first && interruptProbe;
235 const bool signalled = m_Completion.acquireForCompletion(1, grace ? 1 : 0, grace ? 0 : 10000);
236 first = false;
237 if (!signalled) {
238 drainCompletions(false);
239 }
240 bool seen = false;
241 bool valid = false;
242 {
243 LockGuard<Mutex> guard(m_IrqLock);
244 seen = m_CommandSeen;
245 valid = m_CommandValid;
246 }
247 if (seen) {
248 FENCE();
249 if (!valid) {
250 ERROR("Virtio block: malformed completion");
251 failController();
252 return false;
253 }
254 if (request->status != 0) {
255 WARNING("Virtio block: request " << type << " failed with status " << request->status);
256 return false;
257 }
258 if (type == RequestRead && buffer) {
259 MemoryCopy(buffer, m_Data.virtualAddress(), bytes);
260 }
261 return true;
262 }
263 if (Time::getTicks() >= deadline) {
264 ERROR("Virtio block: command timeout");
265 failController();
266 return false;
267 }
268 }
269}
270
271bool VirtioBlkController::readWrite(uint64_t location, void* buffer, size_t bytes, bool writing) {
272 if (!buffer || !bytes || bytes > TargetInfo::getPageSize() || location >= m_Bytes ||
273 bytes > m_Bytes - location || location % m_SectorBytes || bytes % m_SectorBytes ||
274 (writing && m_ReadOnly)) {
275 return false;
276 }
277 return command(writing ? RequestWrite : RequestRead, location / 512, buffer, bytes, 30, false);
278}
279
280bool VirtioBlkController::flush() {
281 // With neither FLUSH nor CONFIG_WCE negotiated, virtio specifies a
282 // writethrough cache; read-only media have no outstanding writes.
283 return (m_ReadOnly || !m_Flush) ? m_Ready : command(RequestFlush, 0, nullptr, 0, 30, false);
284}
285
286void VirtioBlkController::shutdown() {
287 if (m_Shutdown) {
288 return;
289 }
292 {
293 LockGuard<Mutex> serial(m_CommandLock);
294 {
295 LockGuard<Mutex> guard(m_IrqLock);
296 m_Stopping = true;
297 }
298 if (m_TransportInitialised && !m_Transport.reset()) {
299 panic("Virtio block: device did not stop DMA during shutdown");
300 }
301 m_Ready = false;
302 m_Queue.stop();
303 }
304 if (m_Irq && !Machine::instance().getIrqManager()->unregisterHandler(m_Irq, this)) {
305 panic("Virtio block: synchronous interrupt retirement failed");
306 }
307 m_Irq = 0;
308 m_Shutdown = true;
309}
uint16_t getPciDeviceId()
Definition Device.h:228
uint16_t getPciVendorId()
Definition Device.h:224
void addChild(Device *pDevice)
Definition Device.cc:135
void * virtualAddress() const
physical_uintptr_t physicalAddress() const
static PhysicalMemoryManager & instance()
virtual void destroy()
void shutdownDiskCaches()
void release(size_t n=1)
Definition Semaphore.cc:549
MUST_USE_RESULT size_t drainAvailable()
Definition Semaphore.cc:531
MUST_USE_RESULT bool acquireForCompletion(size_t n=1, size_t timeoutSecs=0, size_t timeoutUsecs=0)
Definition Semaphore.cc:372
static constexpr size_t getPageSize() noexcept
Definition TargetInfo.h:40
IrqDisposition irq(irq_id_t number) override
void EXPORTED_PUBLIC panic(const char *msg) NORETURN
Definition panic.cc:117
@ Dec
Definition Log.h:126
@ Hex
Definition Log.h:124
IrqDisposition
Definition IrqHandler.h:31