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/panic.h"
9#include "pedigree/kernel/processor/IoBase.h"
10#include "pedigree/kernel/time/Time.h"
11#include "pedigree/kernel/utilities/new"
12#include "pedigree/kernel/utilities/utility.h"
13
14#include "NvmeDisk.h"
15#include "modules/drivers/common/InterruptProbe.h"
16using namespace Nvme;
17
18NvmeController::NvmeController(Device* pci)
19 : m_Pci(pci),
20 m_Registers(nullptr),
21 m_ReadyMilliseconds(500),
22 m_MaxTransfer(MaxTransfer),
23 m_Irq(0),
24 m_OriginalCommand(0),
25 m_PciChanged(false),
26 m_HardwareOwned(false),
27 m_DmaInstalled(false),
28 m_Interrupts(false),
29 m_Failed(false),
30 m_Stopping(false),
31 m_Shutdown(false),
32 m_Model{} {
33 setSpecificType(String("nvme-controller"));
34}
35NvmeController::~NvmeController() {
36 shutdown();
37}
38
39bool NvmeController::waitReady(bool ready) {
40 const auto deadline = Time::getTicks() + m_ReadyMilliseconds * Time::Multiplier::Millisecond;
41 do {
42 const uint32_t status = m_Registers->read32(Status);
43 if (status == 0xffffffffU || (ready && (status & 2U)))
44 return false;
45 if (bool(status & 1U) == ready)
46 return true;
47 Time::delay(Time::Multiplier::Millisecond);
48 } while (Time::getTicks() < deadline);
49 return false;
50}
51bool NvmeController::disable() {
52 m_Registers->write32(0xffffffffU, InterruptMaskSet);
53 m_Registers->write32(m_Registers->read32(Configuration) & ~(1U | (3U << 14)), Configuration);
54 (void)m_Registers->read32(Configuration);
55 return waitReady(false);
56}
57void NvmeController::failController() {
58 LockGuard<Mutex> reset(m_ResetLock);
59 if (m_Failed)
60 return;
61 {
62 LockGuard<Mutex> irqLock(m_IrqLock);
63 m_Registers->write32(0xffffffffU, InterruptMaskSet);
64 (void)m_Registers->read32(InterruptMaskSet);
65 m_Stopping = true;
66 m_Interrupts = false;
67 m_Admin.stop();
68 m_Io.stop();
69 }
70 // Callers only lend CPU buffers. Queue and bounce pages remain ours until
71 // the controller acknowledges reset, including commands with lost completions.
72 if (!disable())
73 panic("NVMe: controller cannot stop DMA; refusing to release memory");
74 m_DmaInstalled = false;
75 m_Failed = true;
76}
77bool NvmeController::command(NvmeQueue& queue, Command request, void* buffer, size_t bytes,
78 bool writing, uint32_t* result, bool interruptProbe) {
79 bool interrupts;
80 {
81 LockGuard<Mutex> irqLock(m_IrqLock);
82 interrupts = m_Interrupts;
83 }
84 const size_t timeout = (&queue == &m_Io && (request.opcode & 255U) == 0) ? 120 : 30;
85 const auto status =
86 queue.execute(request, buffer, bytes, writing, interrupts, timeout, result, interruptProbe);
87 if (status == NvmeQueue::Result::TransportError)
88 failController();
89 return status == NvmeQueue::Result::Success;
90}
91bool NvmeController::identify(uint32_t nsid, uint8_t kind, void* buffer, bool interruptProbe) {
93 if (!m_Commands.tryAcquire(lease))
94 return false;
95 Command request{};
96 request.opcode = 6;
97 request.nsid = nsid;
98 request.cdw10 = kind;
99 return command(m_Admin, request, buffer, PageSize, false, nullptr, interruptProbe);
100}
101
102bool NvmeController::initialiseController() {
103 if (!m_Pci || m_Pci->getPciClassCode() != 1 || m_Pci->getPciSubclassCode() != 8 ||
104 m_Pci->getPciProgInterface() != 2)
105 return false;
106 auto& pci = PciBus::instance();
107 PciFunctionState::State inherited;
108 if (!pci.inspectFunction(m_Pci, inherited)) {
109 ERROR("NVMe: unsupported PCI state (D0, valid capabilities and PIC INTx required)");
110 return false;
111 }
112 m_OriginalCommand = inherited.command;
113 const uint32_t bar = pci.readConfigSpace(m_Pci, 4);
114 if ((bar & 1U) || ((bar & 6U) != 0 && (bar & 6U) != 4))
115 return false;
116 uint64_t base = bar & ~15U;
117 if ((bar & 6U) == 4)
118 base |= static_cast<uint64_t>(pci.readConfigSpace(m_Pci, 5)) << 32;
119 Device::Address* mapping = nullptr;
120 for (auto* address : m_Pci->addresses()) {
121 if (address->m_Name == "bar0" && base && !address->m_IsIoSpace && address->m_Address == base)
122 mapping = address;
123 }
124 if (!mapping || mapping->m_Size < Doorbells + 16)
125 return false;
126 m_PciChanged = true;
127 if (!pci.updateCommand(m_Pci, 0, 2U | 0x400U))
128 return false;
129 mapping->map();
130 m_Registers = mapping->m_Io;
131 if (!m_Registers || m_Registers->size() < mapping->m_Size)
132 return false;
133 const uint32_t version = m_Registers->read32(Version);
134 const uint64_t cap =
135 m_Registers->read32(Cap) | (static_cast<uint64_t>(m_Registers->read32(Cap + 4)) << 32);
136 // CNS=2 active namespace discovery starts with NVMe 1.1. Use the NVM
137 // command set and 4 KiB host pages; other profiles need separate handling.
138 if (version < 0x00010100 || version == 0xffffffffU || !(cap & (1ULL << 37)) ||
139 ((cap >> 48) & 15U))
140 return false;
141 const size_t stride = size_t{4} << ((cap >> 32) & 15U);
142 if (mapping->m_Size < Doorbells + 3 * stride + 4)
143 return false;
144 m_ReadyMilliseconds = (((cap >> 24) & 255U) + 1) * 500;
145 m_HardwareOwned = true;
146 // Firmware may have left an enabled controller. Stop it before replacing
147 // queue addresses, and never restore firmware's old DMA enable on unload.
148 if (!disable())
149 return false;
150 if (!pci.updateCommand(m_Pci, 4U, 2U | 0x400U) ||
151 !pci.disableMessageInterrupts(m_Pci, inherited) || !pci.resourcesUnchanged(m_Pci, inherited))
152 return false;
153 const uint16_t depth = (cap & 0xffffU) >= QueueDepth - 1 ? QueueDepth : (cap & 0xffffU) + 1;
154 if (!m_Admin.initialise(m_Registers, 0, depth, stride, PageSize) ||
155 !m_Io.initialise(m_Registers, 1, depth, stride, MaxTransfer))
156 return false;
157 m_Registers->write32((depth - 1U) | ((depth - 1U) << 16), AdminAttributes);
158 m_Registers->write32(m_Admin.submissionAddress(), AdminSubmission);
159 m_Registers->write32(m_Admin.submissionAddress() >> 32, AdminSubmission + 4);
160 m_Registers->write32(m_Admin.completionAddress(), AdminCompletion);
161 m_Registers->write32(m_Admin.completionAddress() >> 32, AdminCompletion + 4);
162 FENCE();
163 m_DmaInstalled = true;
164 if (!pci.updateCommand(m_Pci, 0, 6U | 0x400U))
165 return false;
166 m_Registers->write32(1U | (6U << 16) | (4U << 20), Configuration);
167 if (!waitReady(true))
168 return false;
169 uint8_t controller[PageSize]{};
170 if (!identify(0, 1, controller))
171 return false;
172 if ((controller[512] & 15U) > 6 || (controller[512] >> 4) < 6 || (controller[513] & 15U) > 4 ||
173 (controller[513] >> 4) < 4)
174 return false;
175 const uint8_t mdts = controller[77];
176 if (mdts && mdts < 4)
177 m_MaxTransfer = PageSize << mdts;
178 for (size_t i = 0; i < 40; ++i) {
179 const uint8_t ch = controller[24 + i];
180 m_Model[i] = ch >= 32 && ch <= 126 ? ch : ' ';
181 }
182 size_t length = 40;
183 while (length && m_Model[length - 1] == ' ')
184 --length;
185 m_Model[length] = 0;
186 if (!createIoQueue())
187 return false;
188 const uint32_t maximumId = controller[516] | (uint32_t{controller[517]} << 8) |
189 (uint32_t{controller[518]} << 16) | (uint32_t{controller[519]} << 24);
190 if (!maximumId || !discoverNamespaces(maximumId) || !getNumChildren())
191 return false;
192 m_Irq = Machine::instance().getIrqManager()->registerPciIrqHandler(this, m_Pci,
193 IrqPolicy::pciIntxThreaded());
194 if (!m_Irq) {
195 ERROR("NVMe: could not register PCI INTx");
196 return false;
197 }
198 {
199 LockGuard<Mutex> irqLock(m_IrqLock);
200 m_Interrupts = true;
201 if (!pci.updateCommand(m_Pci, 0x400U, 6U))
202 return false;
203 m_Registers->write32(1, InterruptMaskClear);
204 (void)m_Registers->read32(Status);
205 }
206 if (!InterruptProbe::run([&] { return identify(0, 1, controller, true); },
207 [&] { return m_Admin.interruptCompletions(); })) {
208 ERROR("NVMe: interrupt delivery probe failed");
209 return false;
210 }
211 for (size_t i = 0; i < getNumChildren(); ++i)
212 static_cast<NvmeDisk*>(getChild(i))->publishEndpoint();
213 NOTICE("NVMe: '" << m_Model << "' ready, " << Dec << getNumChildren() << " namespaces, "
214 << depth - 1U << " command slots, shared INTx" << Hex);
215 return true;
216}
217bool NvmeController::createIoQueue() {
218 Command request{};
219 request.opcode = 9;
220 request.cdw10 = 7;
221 if (!command(m_Admin, request))
222 return false;
223 request = {};
224 request.opcode = 5;
225 request.prp1 = m_Io.completionAddress();
226 request.cdw10 = 1U | ((m_Io.depth() - 1U) << 16);
227 request.cdw11 = 3;
228 if (!command(m_Admin, request))
229 return false;
230 request = {};
231 request.opcode = 1;
232 request.prp1 = m_Io.submissionAddress();
233 request.cdw10 = 1U | ((m_Io.depth() - 1U) << 16);
234 request.cdw11 = 1U | (1U << 16);
235 return command(m_Admin, request);
236}
237bool NvmeController::discoverNamespaces(uint32_t maximumId) {
238 uint32_t previous = 0;
239 // Bound hostile or nonsensical firmware enumeration, while supporting
240 // sparse namespace IDs and continuation pages rather than assuming NSID 1.
241 for (size_t page = 0; page < 1024; ++page) {
242 uint32_t ids[1024]{};
243 if (!identify(previous, 2, ids))
244 return false;
245 for (uint32_t nsid : ids) {
246 if (!nsid)
247 return true;
248 if (nsid <= previous || nsid > maximumId || nsid == 0xffffffffU)
249 return false;
250 previous = nsid;
251 auto* disk = new NvmeDisk(this, nsid);
252 if (disk->initialise())
253 addChild(disk);
254 else
255 delete disk;
256 }
257 }
258 ERROR("NVMe: active namespace list exceeds enumeration limit");
259 return false;
260}
261NvmeDisk* NvmeController::findNamespace(uint32_t nsid) {
262 for (size_t i = 0; i < getNumChildren(); ++i) {
263 auto* disk = static_cast<NvmeDisk*>(getChild(i));
264 if (disk->namespaceId() == nsid)
265 return disk;
266 }
267 return nullptr;
268}
269bool NvmeController::readWrite(uint32_t nsid, uint64_t lba, uint32_t blocks, void* buffer,
270 size_t bytes, bool writing) {
272 if (!m_Commands.tryAcquire(lease))
273 return false;
274 const NvmeDisk* disk = findNamespace(nsid);
275 if (!disk || !blocks || blocks > 65536 || bytes > m_MaxTransfer ||
276 blocks > (~size_t{0} / disk->getNativeBlockSize()) ||
277 bytes != blocks * disk->getNativeBlockSize() || lba >= disk->getBlockCount() ||
278 blocks > disk->getBlockCount() - lba)
279 return false;
280 LockGuard<Mutex> writeLock(m_WriteLock, writing);
281 Command request{};
282 request.opcode = writing ? 1 : 2;
283 request.nsid = nsid;
284 request.cdw10 = lba;
285 request.cdw11 = lba >> 32;
286 request.cdw12 = blocks - 1;
287 return command(m_Io, request, buffer, bytes, writing);
288}
289bool NvmeController::flush(uint32_t nsid) {
291 if (!m_Commands.tryAcquire(lease) || !findNamespace(nsid))
292 return false;
293 // NVMe Flush guarantees persistence for writes completed before submission.
294 LockGuard<Mutex> writeLock(m_WriteLock);
295 Command request{};
296 request.nsid = nsid;
297 return command(m_Io, request);
298}
300 LockGuard<Mutex> irqLock(m_IrqLock);
301 if (m_Stopping)
303 if (!m_Interrupts)
304 return IrqDisposition::NotHandled;
305 const bool admin = m_Admin.complete(true);
306 const bool io = m_Io.complete(true);
307 return admin || io ? IrqDisposition::Handled : IrqDisposition::NotHandled;
308}
309size_t NvmeController::interruptCompletions() const {
310 return m_Admin.interruptCompletions() + m_Io.interruptCompletions();
311}
312void NvmeController::shutdown() {
313 if (m_Shutdown)
314 return;
317 m_Commands.closeAndWait();
318 {
319 LockGuard<Mutex> irqLock(m_IrqLock);
320 m_Stopping = true;
321 m_Interrupts = false;
322 if (m_HardwareOwned)
323 m_Registers->write32(0xffffffffU, InterruptMaskSet);
324 }
325 if (m_Irq && !Machine::instance().getIrqManager()->unregisterHandler(m_Irq, this))
326 panic("NVMe: synchronous interrupt retirement failed");
327 m_Irq = 0;
328 m_Admin.stop();
329 m_Io.stop();
330 if (m_DmaInstalled && !m_Failed && (m_Registers->read32(Status) & 1U)) {
331 m_Registers->write32((m_Registers->read32(Configuration) & ~(3U << 14)) | (1U << 14),
332 Configuration);
333 const auto deadline = Time::getTicks() + 30 * Time::Multiplier::Second;
334 while ((m_Registers->read32(Status) & 12U) != 8U && Time::getTicks() < deadline)
335 Time::delay(Time::Multiplier::Millisecond);
336 if ((m_Registers->read32(Status) & 12U) != 8U)
337 panic("NVMe: orderly shutdown notification timed out");
338 }
339 if (m_DmaInstalled && !disable())
340 panic("NVMe: cannot stop DMA during shutdown");
341 m_DmaInstalled = false;
342 if (m_PciChanged) {
343 const uint16_t command =
344 m_HardwareOwned ? (m_OriginalCommand & ~4U) | 0x400U : m_OriginalCommand;
345 if (!PciBus::instance().updateCommand(m_Pci, 0x407U, command & 0x407U))
346 panic("NVMe: failed to verify PCI state during shutdown");
347 }
348 m_Shutdown = true;
349}
350
351size_t NvmeController::maximumOutstanding() const {
352 return m_Io.maximumOutstanding();
353}
IoBase * m_Io
Definition Device.h:110
size_t m_Size
Definition Device.h:104
void map(size_t forcedSize=0, bool bUser=false, bool bWriteCombine=false, bool bWriteThrough=false)
Definition Device.cc:334
size_t getNumChildren()
Definition Device.cc:131
Device * getChild(size_t n)
Definition Device.cc:127
virtual Vector< Address * > & addresses()
Definition Device.h:249
uint8_t getPciClassCode()
Definition Device.h:211
void addChild(Device *pDevice)
Definition Device.cc:123
uint8_t getPciSubclassCode()
Definition Device.h:215
uint8_t getPciProgInterface()
Definition Device.h:227
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 registerPciIrqHandler(IrqHandler *handler, Device *pDevice, const IrqPolicy &policy)=0
IrqDisposition irq(irq_id_t number) override
MUST_USE_RESULT bool tryAcquire(Lease &lease)
virtual void destroy()
void shutdownDiskCaches()
void EXPORTED_PUBLIC panic(const char *msg) NORETURN
Definition panic.cc:117
@ Dec
Definition Log.h:144
@ Hex
Definition Log.h:142
IrqDisposition
Definition IrqHandler.h:31