The Pedigree Project 0.1
NvmeController.cc
1/* Copyright (c) 2026, Pedigree Developers. SPDX-License-Identifier: ISC */
2#include "NvmeController.h"
3#include "pedigree/kernel/LockGuard.h"
4#include "pedigree/kernel/Log.h"
5#include "pedigree/kernel/machine/IrqManager.h"
6#include "pedigree/kernel/machine/Machine.h"
7#include "pedigree/kernel/machine/Pci.h"
8#include "pedigree/kernel/machine/PciVirtualFunctions.h"
9#include "pedigree/kernel/panic.h"
10#include "pedigree/kernel/process/Scheduler.h"
11#include "pedigree/kernel/process/TerminationDeferral.h"
12#include "pedigree/kernel/processor/IoBase.h"
13#include "pedigree/kernel/processor/Processor.h"
14#include "pedigree/kernel/time/Time.h"
15#include "pedigree/kernel/utilities/new"
16#include "pedigree/kernel/utilities/utility.h"
17
18#include "NvmeDisk.h"
19#include "modules/drivers/common/InterruptProbe.h"
20using namespace Nvme;
21
22namespace {
23uint16_t readLe16(const uint8_t* data) {
24 return data[0] | (uint16_t{data[1]} << 8);
25}
26uint32_t readLe32(const uint8_t* data) {
27 return readLe16(data) | (uint32_t{readLe16(data + 2)} << 16);
28}
29} // namespace
30
31NvmeController::NvmeController(Device* pci)
32 : m_Pci(pci),
33 m_Registers(nullptr),
34 m_Io{},
35 m_IoCount(0),
36 m_IoProcessor{},
37 m_ReadyMilliseconds(500),
38 m_MaxTransfer(MaxTransfer),
39 m_Irq(0),
40 m_IoIrq{},
41 m_OriginalCommand(0),
42 m_ControllerId(0),
43 m_VirtualizationSupported(false),
44 m_NamespaceManagementSupported(false),
45 m_PciChanged(false),
46 m_HardwareOwned(false),
47 m_DmaInstalled(false),
48 m_DmaIsolated(false),
49 m_Interrupts(false),
50 m_BatchInitialising(false),
51 m_Failed(false),
52 m_Stopping(false),
53 m_Shutdown(false),
54 m_Model{} {
55 setSpecificType(String("nvme-controller"));
56}
57NvmeController::~NvmeController() {
58 shutdown();
59}
60
61bool NvmeController::waitReady(bool ready) {
62 const auto deadline = Time::getTicks() + m_ReadyMilliseconds * Time::Multiplier::Millisecond;
63 do {
64 const uint32_t status = m_Registers->read32(Status);
65 if (status == 0xffffffffU || (ready && (status & 2U)))
66 return false;
67 if (bool(status & 1U) == ready)
68 return true;
69 Time::delay(Time::Multiplier::Millisecond);
70 } while (Time::getTicks() < deadline);
71 return false;
72}
73bool NvmeController::disable() {
74 m_Registers->write32(0xffffffffU, InterruptMaskSet);
75 m_Registers->write32(m_Registers->read32(Configuration) & ~(1U | (3U << 14)), Configuration);
76 (void)m_Registers->read32(Configuration);
77 return waitReady(false);
78}
79void NvmeController::failController() {
80 LockGuard<Mutex> reset(m_ResetLock);
81 if (m_Failed)
82 return;
83 {
84 LockGuard<Mutex> irqLock(m_IrqLock);
85 m_Registers->write32(0xffffffffU, InterruptMaskSet);
86 (void)m_Registers->read32(InterruptMaskSet);
87 m_Stopping = true;
88 m_Interrupts = false;
89 }
90 m_Admin.stop();
91 for (auto* queue : m_Io) {
92 if (queue) {
93 queue->stop();
94 }
95 }
96 // A cache fill may use a page as a DMA target. Stop the controller before
97 // the failed fill can discard that page, even if its completion was lost.
98 if (!disable())
99 panic("NVMe: controller cannot stop DMA; refusing to release memory");
100 m_DmaInstalled = false;
101 m_Failed = true;
102}
103bool NvmeController::command(NvmeQueue& queue, Command request, void* buffer, size_t bytes,
104 bool writing, uint32_t* result, bool interruptProbe, bool cacheFill,
105 physical_uintptr_t directWritePhysical) {
106 TerminationDeferral lifetime;
107 PciBus::DmaMapping directReadMapping;
108 bool interrupts;
109 {
110 LockGuard<Mutex> irqLock(m_IrqLock);
111 interrupts = m_Interrupts;
112 }
113 const size_t timeout = (&queue != &m_Admin && (request.opcode & 255U) == 0) ? 120 : 30;
114 const auto status =
115 queue.execute(request, buffer, bytes, writing, interrupts, timeout, result, interruptProbe,
116 cacheFill, directWritePhysical, &directReadMapping);
117 if (status == NvmeQueue::Result::TransportError)
118 failController();
119 return status == NvmeQueue::Result::Success;
120}
121bool NvmeController::identify(uint32_t nsid, uint8_t kind, void* buffer, bool interruptProbe) {
123 if (!m_Commands.tryAcquire(lease))
124 return false;
125 Command request{};
126 request.opcode = 6;
127 request.nsid = nsid;
128 request.cdw10 = kind;
129 return command(m_Admin, request, buffer, PageSize, false, nullptr, interruptProbe);
130}
131
132bool NvmeController::primaryControllerCapabilities(PrimaryControllerCapabilities& capabilities) {
133 capabilities = {};
135 if (!m_Commands.tryAcquire(lease) || !m_VirtualizationSupported) {
136 return false;
137 }
138 LockGuard<Mutex> management(m_VirtualizationLock);
139 return primaryControllerCapabilitiesLocked(capabilities);
140}
141
142bool NvmeController::primaryControllerCapabilitiesLocked(
143 PrimaryControllerCapabilities& capabilities) {
144 uint8_t data[PageSize]{};
145 Command request{};
146 request.opcode = 6;
147 request.cdw10 = 0x14U | (uint32_t{m_ControllerId} << 16);
148 if (!command(m_Admin, request, data, sizeof(data)) || readLe16(data) != m_ControllerId) {
149 return false;
150 }
151 capabilities.controllerId = readLe16(data);
152 capabilities.portId = readLe16(data + 2);
153 capabilities.resourceTypes = data[4] & 3U;
154 VirtualResourceCapabilities* resources[2] = {&capabilities.queues, &capabilities.interrupts};
155 for (size_t i = 0; i < 2; ++i) {
156 const uint8_t* resource = data + 32 + (32 * i);
157 *resources[i] = {readLe32(resource), readLe32(resource + 4), readLe16(resource + 8),
158 readLe16(resource + 10), readLe16(resource + 12), readLe16(resource + 14)};
159 }
160 return true;
161}
162
163bool NvmeController::secondaryControllers(uint16_t firstControllerId,
164 SecondaryControllerList& controllers) {
165 controllers = {};
167 if (!m_Commands.tryAcquire(lease) || !m_VirtualizationSupported) {
168 return false;
169 }
170 LockGuard<Mutex> management(m_VirtualizationLock);
171 return secondaryControllersLocked(firstControllerId, controllers);
172}
173
174bool NvmeController::secondaryControllersLocked(uint16_t firstControllerId,
175 SecondaryControllerList& controllers) {
176 uint8_t data[PageSize]{};
177 Command request{};
178 request.opcode = 6;
179 request.cdw10 = 0x15U | (uint32_t{firstControllerId} << 16);
180 if (!command(m_Admin, request, data, sizeof(data)) || data[0] > 127) {
181 return false;
182 }
183 for (size_t i = 0; i < data[0]; ++i) {
184 const uint8_t* entry = data + 32 + (32 * i);
185 const uint16_t id = readLe16(entry);
186 if (id < firstControllerId || id == m_ControllerId || readLe16(entry + 2) != m_ControllerId) {
187 controllers = {};
188 return false;
189 }
190 controllers.entries[i] = {id,
191 readLe16(entry + 2),
192 bool(entry[4] & 1U),
193 readLe16(entry + 8),
194 readLe16(entry + 10),
195 readLe16(entry + 12)};
196 }
197 controllers.count = data[0];
198 return true;
199}
200
201bool NvmeController::virtualizationManagement(uint16_t controllerId, VirtualResource resource,
202 VirtualizationAction action, uint16_t count,
203 uint16_t* modified) {
204 if (modified) {
205 *modified = 0;
206 }
207 if ((resource != VirtualResource::Queue && resource != VirtualResource::Interrupt) ||
208 (action != VirtualizationAction::AllocatePrimary && action != VirtualizationAction::Offline &&
209 action != VirtualizationAction::Assign && action != VirtualizationAction::Online) ||
210 ((action == VirtualizationAction::Offline || action == VirtualizationAction::Online) &&
211 count)) {
212 return false;
213 }
215 if (!m_Commands.tryAcquire(lease) || !m_VirtualizationSupported) {
216 return false;
217 }
218 LockGuard<Mutex> management(m_VirtualizationLock);
219 if (action == VirtualizationAction::AllocatePrimary) {
220 if (controllerId != m_ControllerId) {
221 return false;
222 }
223 } else {
224 SecondaryControllerList controllers{};
225 if (controllerId == m_ControllerId || !secondaryControllersLocked(controllerId, controllers)) {
226 return false;
227 }
228 const SecondaryController* target = nullptr;
229 for (size_t i = 0; i < controllers.count; ++i) {
230 if (controllers.entries[i].controllerId == controllerId) {
231 target = &controllers.entries[i];
232 break;
233 }
234 }
235 if (!target || (action == VirtualizationAction::Assign && target->online)) {
236 return false;
237 }
238 }
239 if (action == VirtualizationAction::AllocatePrimary || action == VirtualizationAction::Assign) {
240 PrimaryControllerCapabilities capabilities{};
241 if (!primaryControllerCapabilitiesLocked(capabilities) ||
242 !(capabilities.resourceTypes & (1U << static_cast<unsigned>(resource)))) {
243 return false;
244 }
245 const VirtualResourceCapabilities& limits =
246 resource == VirtualResource::Queue ? capabilities.queues : capabilities.interrupts;
247 if (count > limits.flexibleTotal ||
248 (action == VirtualizationAction::Assign && count > limits.secondaryMaximum)) {
249 return false;
250 }
251 }
252 Command request{};
253 request.opcode = 0x1c;
254 request.cdw10 = (uint32_t{controllerId} << 16) | (static_cast<uint32_t>(resource) << 8) |
255 static_cast<uint32_t>(action);
256 request.cdw11 = count;
257 uint32_t result = 0;
258 if (!command(m_Admin, request, nullptr, 0, false, &result)) {
259 return false;
260 }
261 if (modified &&
262 (action == VirtualizationAction::AllocatePrimary || action == VirtualizationAction::Assign)) {
263 *modified = result & 0xffffU;
264 }
265 return true;
266}
267
268bool NvmeController::setNamespaceAttachment(uint32_t nsid, uint16_t controllerId, bool attached) {
269 if (!nsid || nsid == 0xffffffffU) {
270 return false;
271 }
273 if (!m_Commands.tryAcquire(lease) || !m_NamespaceManagementSupported) {
274 return false;
275 }
276 LockGuard<Mutex> management(m_VirtualizationLock);
277 if (controllerId == m_ControllerId && findNamespace(nsid)) {
278 return false;
279 }
280 uint8_t controllers[PageSize]{};
281 controllers[0] = 1;
282 controllers[2] = controllerId;
283 controllers[3] = controllerId >> 8;
284 Command request{};
285 request.opcode = 0x15;
286 request.nsid = nsid;
287 request.cdw10 = attached ? 0 : 1;
288 return command(m_Admin, request, controllers, sizeof(controllers), true);
289}
290
291bool NvmeController::initialiseController() {
292 if (!m_Pci || m_Pci->getPciClassCode() != 1 || m_Pci->getPciSubclassCode() != 8 ||
293 m_Pci->getPciProgInterface() != 2)
294 return false;
295 auto& pci = PciBus::instance();
296 PciFunctionState::State inherited;
297 if (!pci.inspectFunction(m_Pci, inherited, false)) {
298 ERROR("NVMe: unsupported PCI state (D0 and valid capabilities required)");
299 return false;
300 }
301 m_OriginalCommand = inherited.command;
302 const bool virtualFunction = m_Pci->getPhysicalFunction() != nullptr;
303 uint64_t base = 0, assignedBytes = 0;
304 if (virtualFunction) {
305 if (!PciVirtualFunctions::bar(m_Pci, 0, base, assignedBytes)) {
306 return false;
307 }
308 } else {
309 const uint32_t bar = pci.readConfigSpace(m_Pci, 4);
310 if ((bar & 1U) || ((bar & 6U) != 0 && (bar & 6U) != 4)) {
311 return false;
312 }
313 base = bar & ~15U;
314 if ((bar & 6U) == 4) {
315 base |= static_cast<uint64_t>(pci.readConfigSpace(m_Pci, 5)) << 32;
316 }
317 }
318 Device::Address* mapping = nullptr;
319 for (auto* address : m_Pci->addresses()) {
320 if (address->m_Name != "bar0" || !base || !address->m_Address || address->m_IsIoSpace ||
321 (virtualFunction && address->m_Size != assignedBytes)) {
322 continue;
323 }
324 uint64_t cpuPhysical = 0;
325 if (!pci.translateAddress(base, address->m_Size, false, cpuPhysical) ||
326 address->m_Address != cpuPhysical) {
327 continue;
328 }
329 mapping = address;
330 }
331 if (!mapping || mapping->m_Size < Doorbells + 16)
332 return false;
333 m_PciChanged = true;
334 if (!pci.updateCommand(m_Pci, 0, 2U | 0x400U))
335 return false;
336 mapping->map();
337 m_Registers = mapping->m_Io;
338 if (!m_Registers || m_Registers->size() < mapping->m_Size)
339 return false;
340 const uint32_t version = m_Registers->read32(Version);
341 const uint64_t cap =
342 m_Registers->read32(Cap) | (static_cast<uint64_t>(m_Registers->read32(Cap + 4)) << 32);
343 // CNS=2 active namespace discovery starts with NVMe 1.1. Use the NVM
344 // command set and 4 KiB host pages; other profiles need separate handling.
345 if (version < 0x00010100 || version == 0xffffffffU || !(cap & (1ULL << 37)) ||
346 ((cap >> 48) & 15U))
347 return false;
348 const size_t stride = size_t{4} << ((cap >> 32) & 15U);
349 if (mapping->m_Size < Doorbells + 3 * stride + 4)
350 return false;
351 m_ReadyMilliseconds = (((cap >> 24) & 255U) + 1) * 500;
352 m_HardwareOwned = true;
353 // Firmware may have left an enabled controller. Stop it before replacing
354 // queue addresses, and never restore firmware's old DMA enable on unload.
355 if (!disable())
356 return false;
357 if (!pci.updateCommand(m_Pci, 4U, 2U | 0x400U) ||
358 !pci.disableMessageInterrupts(m_Pci, inherited) ||
359 !pci.resourcesUnchanged(m_Pci, inherited)) {
360 return false;
361 }
362 if (pci.attachIsolatedDma(m_Pci)) {
363 // The VF group retains its isolated domains until all VF drivers are gone.
364 m_DmaIsolated = !virtualFunction;
365 NOTICE("NVMe: using isolated DMA domain");
366 } else if (virtualFunction) {
367 ERROR("NVMe: virtual function requires isolated DMA");
368 return false;
369 } else {
370 (void)pci.attachDmaRemapping(m_Pci);
371 }
372 const uint16_t depth = (cap & 0xffffU) >= QueueDepth - 1 ? QueueDepth : (cap & 0xffffU) + 1;
373 if (!m_Admin.initialise(m_Registers, 0, depth, stride, PageSize, m_Pci))
374 return false;
375 m_Registers->write32((depth - 1U) | ((depth - 1U) << 16), AdminAttributes);
376 m_Registers->write32(m_Admin.submissionAddress(), AdminSubmission);
377 m_Registers->write32(m_Admin.submissionAddress() >> 32, AdminSubmission + 4);
378 m_Registers->write32(m_Admin.completionAddress(), AdminCompletion);
379 m_Registers->write32(m_Admin.completionAddress() >> 32, AdminCompletion + 4);
380 FENCE();
381 m_DmaInstalled = true;
382 if (!pci.updateCommand(m_Pci, 0, 6U | 0x400U))
383 return false;
384 m_Registers->write32(1U | (6U << 16) | (4U << 20), Configuration);
385 if (!waitReady(true))
386 return false;
387 uint8_t controller[PageSize]{};
388 if (!identify(0, 1, controller))
389 return false;
390 m_ControllerId = readLe16(controller + 78);
391 m_VirtualizationSupported = (readLe16(controller + 256) & (1U << 7)) != 0;
392 m_NamespaceManagementSupported = (readLe16(controller + 256) & (1U << 3)) != 0;
393 if ((controller[512] & 15U) > 6 || (controller[512] >> 4) < 6 || (controller[513] & 15U) > 4 ||
394 (controller[513] >> 4) < 4)
395 return false;
396 const uint8_t mdts = controller[77];
397 if (mdts && mdts < 4)
398 m_MaxTransfer = PageSize << mdts;
399 for (size_t i = 0; i < 40; ++i) {
400 const uint8_t ch = controller[24 + i];
401 m_Model[i] = ch >= 32 && ch <= 126 ? ch : ' ';
402 }
403 size_t length = 40;
404 while (length && m_Model[length - 1] == ' ')
405 --length;
406 m_Model[length] = 0;
407 const uint32_t maximumId = controller[516] | (uint32_t{controller[517]} << 8) |
408 (uint32_t{controller[518]} << 16) | (uint32_t{controller[519]} << 24);
409 bool activeNamespaces = false;
410 if (maximumId && !discoverNamespaces(maximumId, activeNamespaces)) {
411 return false;
412 }
413 if (!getNumChildren() && (activeNamespaces || virtualFunction || !m_VirtualizationSupported)) {
414 return false;
415 }
416 size_t requestedQueues = 0;
417 const CpuAffinityMask online = Scheduler::onlineAffinity();
418 for (size_t cpu = 0; cpu < Processor::getCount() && requestedQueues < MaxIoQueues; ++cpu) {
419 if (online.contains(cpu)) {
420 m_IoProcessor[requestedQueues++] = cpu;
421 }
422 }
423 if (!requestedQueues) {
424 requestedQueues = 1;
425 m_IoProcessor[0] = 0;
426 }
427 uint16_t msixControl = 0;
428 const size_t messageCount =
429 inherited.msix && pci.readConfig16(m_Pci, inherited.msix + 2, msixControl)
430 ? (msixControl & 0x7ffU) + 1U
431 : 0;
432 if (requestedQueues > (messageCount > 1 ? messageCount - 1 : 1)) {
433 requestedQueues = messageCount > 1 ? messageCount - 1 : 1;
434 }
435 // The last queue's CQ doorbell must also fit the assigned BAR.
436 const size_t doorbellQueues = ((mapping->m_Size - Doorbells - 4) / stride - 1) / 2;
437 if (requestedQueues > doorbellQueues) {
438 requestedQueues = doorbellQueues;
439 }
440 if (getNumChildren()) {
441 Command request{};
442 request.opcode = 9;
443 request.cdw10 = 7;
444 request.cdw11 = (requestedQueues - 1) | ((requestedQueues - 1) << 16);
445 uint32_t granted = 0;
446 if (!command(m_Admin, request, nullptr, 0, false, &granted)) {
447 return false;
448 }
449 m_IoCount = requestedQueues;
450 const size_t submissionQueues = (granted & 0xffffU) + 1U;
451 const size_t completionQueues = (granted >> 16) + 1U;
452 if (m_IoCount > submissionQueues) {
453 m_IoCount = submissionQueues;
454 }
455 if (m_IoCount > completionQueues) {
456 m_IoCount = completionQueues;
457 }
458 }
459 IrqManager* irqManager = Machine::instance().getIrqManager();
460 IrqHandler* handlers[MaxIoQueues + 1];
461 size_t processors[MaxIoQueues + 1] = {};
462 for (size_t i = 0; i < MaxIoQueues + 1; ++i) {
463 handlers[i] = this;
464 if (i) {
465 processors[i] = m_IoProcessor[i - 1];
466 }
467 }
468 size_t vectorCount = m_IoCount && messageCount > 1 ? m_IoCount + 1 : 1;
469 {
470 LockGuard<Mutex> irqLock(m_IrqLock);
471 m_BatchInitialising = true;
472 }
473 for (;;) {
474 irq_id_t vectors[MaxIoQueues + 1] = {};
475 bool fallbackSafe = true;
476 if (irqManager->registerPciMsixIrqHandlers(m_Pci, handlers, vectorCount, vectors, fallbackSafe,
477 processors)) {
478 LockGuard<Mutex> irqLock(m_IrqLock);
479 m_Irq = vectors[0];
480 for (size_t i = 1; i < vectorCount; ++i) {
481 m_IoIrq[i - 1] = vectors[i];
482 }
483 NOTICE("NVMe: MSI-X vectors=" << Dec << vectorCount << ", I/O queues=" << m_IoCount << Hex);
484 break;
485 }
486 if (!fallbackSafe) {
487 ERROR("NVMe: MSI-X setup failed without a safe fallback");
488 return false;
489 }
490 if (vectorCount == 1) {
491 LockGuard<Mutex> irqLock(m_IrqLock);
492 m_BatchInitialising = false;
493 break;
494 }
495 if (m_IoCount > 1) {
496 --m_IoCount;
497 vectorCount = m_IoCount + 1;
498 } else {
499 vectorCount = 1;
500 }
501 }
502 uint16_t ioDepth = depth;
503 if (m_IoCount && ioDepth > MaxIoSlots / m_IoCount + 1) {
504 ioDepth = MaxIoSlots / m_IoCount + 1;
505 }
506 for (size_t i = 0; i < m_IoCount; ++i) {
507 auto* queue = new NvmeQueue;
508 if (!queue->initialise(m_Registers, i + 1, ioDepth, stride, m_MaxTransfer, m_Pci)) {
509 queue->releaseDma();
510 delete queue;
511 if (!i) {
512 return false;
513 }
514 LockGuard<Mutex> irqLock(m_IrqLock);
515 m_IoCount = i;
516 break;
517 }
518 {
519 LockGuard<Mutex> irqLock(m_IrqLock);
520 m_Io[i] = queue;
521 }
522 if (!createIoQueue(i, m_IoIrq[i] ? i + 1 : 0)) {
523 return false;
524 }
525 }
526 if (!m_Irq) {
527 m_Irq = irqManager->registerPciMessageIrqHandler(this, m_Pci, IrqPolicy::pciIntxThreaded());
528 }
529 if (!m_Irq) {
530 ERROR("NVMe: could not register PCI interrupt");
531 return false;
532 }
533 {
534 LockGuard<Mutex> irqLock(m_IrqLock);
535 m_Interrupts = true;
536 m_BatchInitialising = false;
537 if (!pci.updateCommand(m_Pci, 0x400U, 6U))
538 return false;
539 m_Registers->write32(1, InterruptMaskClear);
540 (void)m_Registers->read32(Status);
541 }
542 if (!InterruptProbe::run([&] { return identify(0, 1, controller, true); },
543 [&] { return m_Admin.interruptCompletions(); })) {
544 ERROR("NVMe: interrupt delivery probe failed");
545 return false;
546 }
547 for (size_t i = 0; i < m_IoCount; ++i) {
548 Command flush{};
549 flush.nsid = static_cast<NvmeDisk*>(getChild(0))->namespaceId();
550 if (!InterruptProbe::run(
551 [&] { return command(*m_Io[i], flush, nullptr, 0, false, nullptr, true); },
552 [&] { return m_Io[i]->interruptCompletions(); })) {
553 ERROR("NVMe: I/O vector delivery probe failed for queue " << Dec << i + 1 << Hex);
554 return false;
555 }
556 }
557 for (size_t i = 0; i < getNumChildren(); ++i)
558 static_cast<NvmeDisk*>(getChild(i))->publishEndpoint();
559 NOTICE("NVMe: '" << m_Model << "' ready, " << Dec << getNumChildren() << " namespaces, "
560 << m_IoCount << " I/O queues, " << ioDepth - 1U << " slots per queue" << Hex);
561 return true;
562}
563bool NvmeController::createIoQueue(size_t index, uint16_t interruptVector) {
564 NvmeQueue& queue = *m_Io[index];
565 const uint16_t id = index + 1;
566 Command request{};
567 request.opcode = 5;
568 request.prp1 = queue.completionAddress();
569 request.cdw10 = id | ((queue.depth() - 1U) << 16);
570 request.cdw11 = 3U | (uint32_t(interruptVector) << 16);
571 if (!command(m_Admin, request)) {
572 return false;
573 }
574 request = {};
575 request.opcode = 1;
576 request.prp1 = queue.submissionAddress();
577 request.cdw10 = id | ((queue.depth() - 1U) << 16);
578 request.cdw11 = 1U | (uint32_t(id) << 16);
579 return command(m_Admin, request);
580}
581NvmeQueue& NvmeController::ioQueue() {
582 const size_t cpu = Processor::index();
583 for (size_t i = 0; i < m_IoCount; ++i) {
584 if (m_IoProcessor[i] == cpu) {
585 return *m_Io[i];
586 }
587 }
588 return *m_Io[cpu % m_IoCount];
589}
590bool NvmeController::discoverNamespaces(uint32_t maximumId, bool& activeNamespaces) {
591 activeNamespaces = false;
592 uint32_t previous = 0;
593 // Bound hostile or nonsensical firmware enumeration, while supporting
594 // sparse namespace IDs and continuation pages rather than assuming NSID 1.
595 for (size_t page = 0; page < 1024; ++page) {
596 uint32_t ids[1024]{};
597 if (!identify(previous, 2, ids))
598 return false;
599 for (uint32_t nsid : ids) {
600 if (!nsid)
601 return true;
602 if (nsid <= previous || nsid > maximumId || nsid == 0xffffffffU)
603 return false;
604 activeNamespaces = true;
605 previous = nsid;
606 auto* disk = new NvmeDisk(this, nsid);
607 if (disk->initialise())
608 addChild(disk);
609 else
610 delete disk;
611 }
612 }
613 ERROR("NVMe: active namespace list exceeds enumeration limit");
614 return false;
615}
616NvmeDisk* NvmeController::findNamespace(uint32_t nsid) {
617 for (size_t i = 0; i < getNumChildren(); ++i) {
618 auto* disk = static_cast<NvmeDisk*>(getChild(i));
619 if (disk->namespaceId() == nsid)
620 return disk;
621 }
622 return nullptr;
623}
624bool NvmeController::readWrite(uint32_t nsid, uint64_t lba, uint32_t blocks, void* buffer,
625 size_t bytes, bool writing) {
626 return readWrite(nsid, lba, blocks, buffer, bytes, writing, false);
627}
628bool NvmeController::readWrite(uint32_t nsid, uint64_t lba, uint32_t blocks, void* buffer,
629 size_t bytes, bool writing, bool cacheFill,
630 physical_uintptr_t directWritePhysical) {
632 if (!m_Commands.tryAcquire(lease))
633 return false;
634 const NvmeDisk* disk = findNamespace(nsid);
635 if (!disk || !blocks || blocks > 65536 || bytes > m_MaxTransfer ||
636 blocks > (~size_t{0} / disk->getNativeBlockSize()) ||
637 bytes != blocks * disk->getNativeBlockSize() || lba >= disk->getBlockCount() ||
638 blocks > disk->getBlockCount() - lba)
639 return false;
640 LockGuard<Mutex> writeLock(m_WriteLock, writing);
641 Command request{};
642 request.opcode = writing ? 1 : 2;
643 request.nsid = nsid;
644 request.cdw10 = lba;
645 request.cdw11 = lba >> 32;
646 request.cdw12 = blocks - 1;
647 return command(ioQueue(), request, buffer, bytes, writing, nullptr, false, cacheFill,
648 directWritePhysical);
649}
650bool NvmeController::flush(uint32_t nsid) {
652 if (!m_Commands.tryAcquire(lease) || !findNamespace(nsid))
653 return false;
654 // NVMe Flush guarantees persistence for writes completed before submission.
655 LockGuard<Mutex> writeLock(m_WriteLock);
656 Command request{};
657 request.nsid = nsid;
658 return command(ioQueue(), request);
659}
661 NvmeQueue* queues[MaxIoQueues + 1] = {};
662 size_t count = 0;
663 {
664 LockGuard<Mutex> irqLock(m_IrqLock);
665 if (m_Stopping) {
667 }
668 if (!m_Interrupts && !m_BatchInitialising) {
669 return IrqDisposition::NotHandled;
670 }
671 if (m_BatchInitialising || !m_IoIrq[0]) {
672 queues[count++] = &m_Admin;
673 for (auto* queue : m_Io) {
674 if (queue) {
675 queues[count++] = queue;
676 }
677 }
678 } else if (id == m_Irq) {
679 queues[count++] = &m_Admin;
680 } else {
681 for (size_t i = 0; i < MaxIoQueues; ++i) {
682 if (id == m_IoIrq[i] && m_Io[i]) {
683 queues[count++] = m_Io[i];
684 break;
685 }
686 }
687 }
688 }
689 // Synchronous IRQ retirement keeps the queues alive across this snapshot.
690 // Independent vectors must not serialize their completion work together.
691 bool handled = false;
692 for (size_t i = 0; i < count; ++i) {
693 handled |= queues[i]->complete(true);
694 }
695 return handled ? IrqDisposition::Handled : IrqDisposition::NotHandled;
696}
697size_t NvmeController::interruptCompletions() const {
698 size_t completions = m_Admin.interruptCompletions();
699 for (size_t i = 0; i < m_IoCount; ++i) {
700 completions += m_Io[i]->interruptCompletions();
701 }
702 return completions;
703}
704size_t NvmeController::ioQueueInterruptCompletions(size_t queue) const {
705 return queue < m_IoCount ? m_Io[queue]->interruptCompletions() : 0;
706}
707void NvmeController::shutdown() {
708 if (m_Shutdown)
709 return;
712 m_Commands.closeAndWait();
713 {
714 LockGuard<Mutex> irqLock(m_IrqLock);
715 m_Stopping = true;
716 m_Interrupts = false;
717 if (m_HardwareOwned)
718 m_Registers->write32(0xffffffffU, InterruptMaskSet);
719 }
720 if (m_Irq && !Machine::instance().getIrqManager()->unregisterHandler(m_Irq, this))
721 panic("NVMe: synchronous interrupt retirement failed");
722 m_Irq = 0;
723 for (auto& irq : m_IoIrq) {
724 if (irq && !Machine::instance().getIrqManager()->unregisterHandler(irq, this)) {
725 panic("NVMe: synchronous I/O vector retirement failed");
726 }
727 irq = 0;
728 }
729 m_Admin.stop();
730 for (auto* queue : m_Io) {
731 if (queue) {
732 queue->stop();
733 }
734 }
735 if (m_DmaInstalled && !m_Failed && (m_Registers->read32(Status) & 1U)) {
736 m_Registers->write32((m_Registers->read32(Configuration) & ~(3U << 14)) | (1U << 14),
737 Configuration);
738 const auto deadline = Time::getTicks() + 30 * Time::Multiplier::Second;
739 while ((m_Registers->read32(Status) & 12U) != 8U && Time::getTicks() < deadline)
740 Time::delay(Time::Multiplier::Millisecond);
741 if ((m_Registers->read32(Status) & 12U) != 8U)
742 panic("NVMe: orderly shutdown notification timed out");
743 }
744 if (m_DmaInstalled && !disable())
745 panic("NVMe: cannot stop DMA during shutdown");
746 m_DmaInstalled = false;
747 if (m_PciChanged) {
748 const uint16_t command =
749 m_HardwareOwned ? (m_OriginalCommand & ~4U) | 0x400U : m_OriginalCommand;
750 if (!PciBus::instance().updateCommand(m_Pci, 0x407U, command & 0x407U))
751 panic("NVMe: failed to verify PCI state during shutdown");
752 }
753 m_Admin.releaseDma();
754 for (auto*& queue : m_Io) {
755 if (queue) {
756 queue->releaseDma();
757 delete queue;
758 queue = nullptr;
759 }
760 }
761 m_IoCount = 0;
762 if (m_DmaIsolated) {
763 if (!PciBus::instance().detachIsolatedDma(m_Pci)) {
764 panic("NVMe: failed to retire isolated DMA domain");
765 }
766 m_DmaIsolated = false;
767 }
768 m_Shutdown = true;
769}
770
771size_t NvmeController::maximumOutstanding() const {
772 size_t maximum = 0;
773 for (size_t i = 0; i < m_IoCount; ++i) {
774 const size_t outstanding = m_Io[i]->maximumOutstanding();
775 if (outstanding > maximum) {
776 maximum = outstanding;
777 }
778 }
779 return maximum;
780}
IoBase * m_Io
Definition Device.h:115
size_t m_Size
Definition Device.h:109
void map(size_t forcedSize=0, bool bUser=false, bool bWriteCombine=false, bool bWriteThrough=false)
Definition Device.cc:346
size_t getNumChildren()
Definition Device.cc:143
Device * getChild(size_t n)
Definition Device.cc:139
virtual Vector< Address * > & addresses()
Definition Device.h:266
uint8_t getPciClassCode()
Definition Device.h:216
void addChild(Device *pDevice)
Definition Device.cc:135
uint8_t getPciSubclassCode()
Definition Device.h:220
uint8_t getPciProgInterface()
Definition Device.h:232
virtual void write32(uint32_t value, size_t offset=0)=0
virtual uint32_t read32(size_t offset=0)=0
virtual size_t size() const =0
virtual irq_id_t registerPciMessageIrqHandler(IrqHandler *handler, Device *pDevice, const IrqPolicy &intxFallbackPolicy)
Definition IrqManager.cc:25
virtual bool registerPciMsixIrqHandlers(Device *device, IrqHandler *const *handlers, size_t count, irq_id_t *ids, bool &fallbackSafe, const size_t *processors=nullptr)
Definition IrqManager.cc:30
IrqDisposition irq(irq_id_t number) override
size_t getNativeBlockSize() const override
Definition NvmeDisk.h:19
void releaseDma()
Definition NvmeQueue.cc:118
MUST_USE_RESULT bool tryAcquire(Lease &lease)
static size_t getCount()
static size_t index()
virtual void destroy()
void shutdownDiskCaches(bool deviceAvailable=true)
void EXPORTED_PUBLIC panic(const char *msg) NORETURN
Definition panic.cc:118
@ Dec
Definition Log.h:126
@ Hex
Definition Log.h:124
IrqDisposition
Definition IrqHandler.h:31