The Pedigree Project 0.1
NvmeQueue.cc
1/* Copyright (c) 2026, Pedigree Developers. SPDX-License-Identifier: ISC */
2#include "NvmeQueue.h"
3#include "pedigree/kernel/LockGuard.h"
4#include "pedigree/kernel/Log.h"
5#include "pedigree/kernel/TargetInfo.h"
6#include "pedigree/kernel/process/TerminationDeferral.h"
7#include "pedigree/kernel/processor/IoBase.h"
8#include "pedigree/kernel/processor/PhysicalMemoryManager.h"
9#include "pedigree/kernel/processor/VirtualAddressSpace.h"
10#include "pedigree/kernel/time/Time.h"
11#include "pedigree/kernel/utilities/utility.h"
12
13NvmeQueue::Slot::Slot()
14 : data("NVMe transfer"),
15 prps("NVMe PRP list"),
16 completion(0, false),
17 active(false),
18 done(false),
19 status(0),
20 result(0) {}
21NvmeQueue::NvmeQueue()
22 : m_Registers(nullptr),
23 m_Submission("NVMe SQ"),
24 m_Completion("NVMe CQ"),
25 m_Available(0, false),
26 m_Stride(0),
27 m_TransferBytes(0),
28 m_Id(0),
29 m_Depth(0),
30 m_Tail(0),
31 m_Head(0),
32 m_SubmissionHead(0),
33 m_Phase(true),
34 m_Online(false),
35 m_PolledInterrupt(false),
36 m_InterruptCompletions(0),
37 m_Outstanding(0),
38 m_MaximumOutstanding(0) {}
39
40bool NvmeQueue::initialise(IoBase* registers, uint16_t id, uint16_t depth, size_t stride,
41 size_t transferBytes) {
42 if (TargetInfo::getPageSize() != Nvme::PageSize || depth < 2 || depth > Nvme::QueueDepth ||
43 !transferBytes || transferBytes > Nvme::MaxTransfer || transferBytes % Nvme::PageSize)
44 return false;
45 m_Registers = registers;
46 m_Id = id;
47 m_Depth = depth;
48 m_Stride = stride;
49 m_TransferBytes = transferBytes;
50 auto& memory = PhysicalMemoryManager::instance();
53 if (!memory.allocateRegion(m_Submission, 1, constraints, flags) ||
54 !memory.allocateRegion(m_Completion, 1, constraints, flags))
55 return false;
56 ByteSet(m_Submission.virtualAddress(), 0, Nvme::PageSize);
57 ByteSet(m_Completion.virtualAddress(), 0, Nvme::PageSize);
58 for (size_t i = 0; i < depth - 1U; ++i) {
59 auto& slot = m_Slots[i];
60 if (!memory.allocateRegion(slot.data, transferBytes / Nvme::PageSize,
62 !memory.allocateRegion(slot.prps, 1, constraints, flags))
63 return false;
64 ByteSet(slot.data.virtualAddress(), 0, transferBytes);
65 ByteSet(slot.prps.virtualAddress(), 0, Nvme::PageSize);
66 auto* prps = static_cast<uint64_t*>(slot.prps.virtualAddress());
67 // PRPs do not require adjacent physical pages. Preserve the low DMA pool
68 // for devices whose descriptors really need contiguous allocations.
69 for (size_t page = 0; page < transferBytes / Nvme::PageSize; ++page) {
70 physical_uintptr_t address = 0;
71 size_t mappingFlags = 0;
73 static_cast<uint8_t*>(slot.data.virtualAddress()) + page * Nvme::PageSize, address,
74 mappingFlags);
75 if (address >= (uint64_t{1} << 32))
76 return false;
77 if (!page)
78 slot.firstPage = address;
79 else
80 prps[page - 1] = address;
81 }
82 }
83 FENCE();
84 m_Online = true;
85 m_Available.release(depth - 1);
86 return true;
87}
88
89void NvmeQueue::stopLocked() {
90 if (!m_Online)
91 return;
92 m_Online = false;
93 for (size_t i = 0; i < m_Depth - 1U; ++i) {
94 if (m_Slots[i].active)
95 m_Slots[i].completion.release();
96 }
97 m_Available.release(m_Depth);
98}
99void NvmeQueue::stop() {
100 LockGuard<Mutex> guard(m_Lock);
101 stopLocked();
102}
103
104bool NvmeQueue::observe(bool fromInterrupt, bool interruptsEnabled) {
105 const bool credited = fromInterrupt && m_PolledInterrupt;
106 if (fromInterrupt)
107 m_PolledInterrupt = false;
108 if (!m_Online)
109 return credited;
110 auto* entries = static_cast<volatile Nvme::Completion*>(m_Completion.virtualAddress());
111 bool consumed = false;
112 for (size_t count = 0; count < m_Depth; ++count) {
113 volatile auto& entry = entries[m_Head];
114 const uint16_t status = entry.status;
115 if ((status & 1U) != m_Phase)
116 break;
117 // The phase bit publishes the remainder of the coherent DMA completion.
118 FENCE();
119 const uint16_t cid = entry.cid;
120 if (entry.sqId != m_Id || entry.sqHead >= m_Depth || cid >= m_Depth - 1U ||
121 !m_Slots[cid].active || m_Slots[cid].done) {
122 ERROR("NVMe: invalid completion on queue " << m_Id);
123 stopLocked();
124 return true;
125 }
126 auto& slot = m_Slots[cid];
127 m_SubmissionHead = entry.sqHead;
128 slot.status = (status >> 1) & 0x7ffU;
129 slot.result = entry.result;
130 slot.done = true;
131 --m_Outstanding;
132 if (fromInterrupt)
133 ++m_InterruptCompletions;
134 slot.completion.release();
135 if (++m_Head == m_Depth) {
136 m_Head = 0;
137 m_Phase = !m_Phase;
138 }
139 consumed = true;
140 }
141 if (consumed) {
142 // Advancing CQ head can withdraw INTx before its IRQ worker observes it.
143 if (!fromInterrupt && interruptsEnabled)
144 m_PolledInterrupt = true;
145 FENCE();
146 m_Registers->write32(m_Head, Nvme::Doorbells + (2U * m_Id + 1U) * m_Stride);
147 (void)m_Registers->read32(Nvme::Status);
148 }
149 return consumed || credited;
150}
151bool NvmeQueue::complete(bool fromInterrupt) {
152 LockGuard<Mutex> guard(m_Lock);
153 return observe(fromInterrupt);
154}
155
156NvmeQueue::Result NvmeQueue::execute(Nvme::Command command, void* buffer, size_t bytes,
157 bool writing, bool interrupts, size_t timeoutSeconds,
158 uint32_t* result, bool interruptProbe) {
159 TerminationDeferral lifetime;
160 if (bytes > m_TransferBytes || (bytes && !buffer))
161 return Result::CommandError;
162 if (!m_Available.acquireForCompletion(1, timeoutSeconds)) {
163 stop();
164 return Result::TransportError;
165 }
166 size_t cid = 0;
167 {
168 LockGuard<Mutex> guard(m_Lock);
169 if (!m_Online) {
170 m_Available.release();
171 return Result::TransportError;
172 }
173 for (; cid < m_Depth - 1U && m_Slots[cid].active; ++cid) {
174 }
175 const uint16_t nextTail = (m_Tail + 1U) % m_Depth;
176 if (cid == m_Depth - 1U || nextTail == m_SubmissionHead) {
177 stopLocked();
178 return Result::TransportError;
179 }
180 auto& slot = m_Slots[cid];
181 [[maybe_unused]] const size_t drained = slot.completion.drainAvailable();
182 slot.active = true;
183 if (++m_Outstanding > m_MaximumOutstanding)
184 m_MaximumOutstanding = m_Outstanding;
185 slot.done = false;
186 slot.status = 0;
187 if (bytes) {
188 if (writing)
189 MemoryCopy(slot.data.virtualAddress(), buffer, bytes);
190 command.prp1 = slot.firstPage;
191 command.prp2 = bytes <= Nvme::PageSize ? 0
192 : bytes <= 2 * Nvme::PageSize
193 ? static_cast<uint64_t*>(slot.prps.virtualAddress())[0]
194 : slot.prps.physicalAddress();
195 }
196 command.opcode = (command.opcode & 0xffffU) | (cid << 16);
197 auto* submission = static_cast<Nvme::Command*>(m_Submission.virtualAddress());
198 submission[m_Tail] = command;
199 m_Tail = nextTail;
200 FENCE();
201 m_Registers->write32(m_Tail, Nvme::Doorbells + 2U * m_Id * m_Stride);
202 (void)m_Registers->read32(Nvme::Status);
203 }
204 const auto deadline = Time::getTicks() + timeoutSeconds * Time::Multiplier::Second;
205 bool interruptGrace = interruptProbe;
206 for (;;) {
207 bool poll = !interrupts;
208 // Ordinary commands retain their existing poll-recovery latency. The
209 // readiness probe first leaves the CQ entry available to the IRQ worker.
210 if (interrupts)
211 poll = !m_Slots[cid].completion.acquireForCompletion(1, interruptGrace ? 1 : 0,
212 interruptGrace ? 0 : 10000);
213 interruptGrace = false;
214 {
215 LockGuard<Mutex> guard(m_Lock);
216 if (poll)
217 observe(false, interrupts);
218 auto& slot = m_Slots[cid];
219 if (!m_Online || (m_Registers->read32(Nvme::Status) & 2U)) {
220 stopLocked();
221 slot.active = false;
222 return Result::TransportError;
223 }
224 if (slot.done) {
225 const bool success = !slot.status;
226 if (success && bytes && !writing) {
227 FENCE();
228 MemoryCopy(buffer, slot.data.virtualAddress(), bytes);
229 }
230 if (result)
231 *result = slot.result;
232 if (!success)
233 WARNING("NVMe: command " << Hex << (command.opcode & 255U) << " namespace "
234 << command.nsid << " status " << slot.status);
235 slot.active = false;
236 m_Available.release();
237 return success ? Result::Success : Result::CommandError;
238 }
239 if (Time::getTicks() >= deadline) {
240 ERROR("NVMe: command timeout on queue " << m_Id << ", CID " << cid);
241 stopLocked();
242 slot.active = false;
243 return Result::TransportError;
244 }
245 }
246 if (!interrupts)
247 Time::delay(Time::Multiplier::Millisecond);
248 }
249}
250size_t NvmeQueue::interruptCompletions() const {
251 LockGuard<Mutex> guard(m_Lock);
252 return m_InterruptCompletions;
253}
254size_t NvmeQueue::maximumOutstanding() const {
255 LockGuard<Mutex> guard(m_Lock);
256 return m_MaximumOutstanding;
257}
Abstrace base class for hardware I/O capabilities.
Definition IoBase.h:32
virtual void write32(uint32_t value, size_t offset=0)=0
virtual uint32_t read32(size_t offset=0)=0
void * virtualAddress() const
static PhysicalMemoryManager & instance()
void release(size_t n=1)
Definition Semaphore.cc:546
MUST_USE_RESULT size_t drainAvailable()
Definition Semaphore.cc:528
MUST_USE_RESULT bool acquireForCompletion(size_t n=1, size_t timeoutSecs=0, size_t timeoutUsecs=0)
Definition Semaphore.cc:369
static constexpr size_t getPageSize() noexcept
Definition TargetInfo.h:40
virtual bool getMapping(void *virtualAddress, physical_uintptr_t &physicalAddress, size_t &flags)=0
static EXPORTED_PUBLIC VirtualAddressSpace & getKernelAddressSpace()
@ Hex
Definition Log.h:142
uintptr_t physicalAddress(physical_uintptr_t address) PURE
Definition utils.h:39