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/Processor.h"
10#include "pedigree/kernel/processor/VirtualAddressSpace.h"
11#include "pedigree/kernel/time/Time.h"
12#include "pedigree/kernel/utilities/utility.h"
14NvmeQueue::Slot::Slot()
15 : data(
"NVMe transfer"),
16 prps(
"NVMe PRP list"),
23 : m_Registers(nullptr),
25 m_Submission(
"NVMe SQ"),
26 m_Completion(
"NVMe CQ"),
27 m_Available(0, false),
37 m_PolledInterrupt(false),
38 m_InterruptCompletions(0),
40 m_MaximumOutstanding(0) {}
42bool NvmeQueue::initialise(
IoBase* registers, uint16_t
id, uint16_t depth,
size_t stride,
43 size_t transferBytes,
Device* dmaDevice) {
45 !transferBytes || transferBytes > Nvme::MaxTransfer || transferBytes % Nvme::PageSize)
47 m_Registers = registers;
48 m_DmaDevice = dmaDevice;
52 m_TransferBytes = transferBytes;
56 if (!memory.allocateRegion(m_Submission, 1, constraints, flags) ||
57 !memory.allocateRegion(m_Completion, 1, constraints, flags))
59 auto& pci = PciBus::instance();
60 if (!pci.mapDmaPage(m_DmaDevice, m_Submission.
physicalAddress(), Nvme::PageSize,
62 !pci.mapDmaPage(m_DmaDevice, m_Completion.
physicalAddress(), Nvme::PageSize,
68 for (
size_t i = 0; i < depth - 1U; ++i) {
69 auto& slot = m_Slots[i];
70 if (!memory.allocateRegion(slot.data, transferBytes / Nvme::PageSize,
72 !memory.allocateRegion(slot.prps, 1, constraints, flags))
74 if (!pci.mapDmaPage(m_DmaDevice, slot.prps.physicalAddress(), Nvme::PageSize, slot.prpsDma)) {
77 ByteSet(slot.data.virtualAddress(), 0, transferBytes);
78 ByteSet(slot.prps.virtualAddress(), 0, Nvme::PageSize);
79 auto* prps =
static_cast<uint64_t*
>(slot.prps.virtualAddress());
82 for (
size_t page = 0; page < transferBytes / Nvme::PageSize; ++page) {
83 physical_uintptr_t address = 0;
84 size_t mappingFlags = 0;
86 static_cast<uint8_t*
>(slot.data.virtualAddress()) + page * Nvme::PageSize, address,
88 if (!pci.mapDmaPage(m_DmaDevice, address, Nvme::PageSize, slot.dataDma[page])) {
92 slot.firstPage = slot.dataDma[page].address();
94 prps[page - 1] = slot.dataDma[page].address();
103void NvmeQueue::stopLocked() {
107 for (
size_t i = 0; i < m_Depth - 1U; ++i) {
108 if (m_Slots[i].active)
109 m_Slots[i].completion.
release();
113void NvmeQueue::stop() {
119 m_SubmissionDma.release();
120 m_CompletionDma.release();
121 for (
auto& slot : m_Slots) {
122 slot.prpsDma.release();
123 for (
auto& mapping : slot.dataDma) {
129bool NvmeQueue::observe(
bool fromInterrupt,
bool interruptsEnabled) {
130 const bool credited = fromInterrupt && m_PolledInterrupt;
132 m_PolledInterrupt =
false;
136 bool consumed =
false;
137 for (
size_t count = 0; count < m_Depth; ++count) {
138 volatile auto& entry = entries[m_Head];
139 const uint16_t status = entry.status;
140 if ((status & 1U) != m_Phase)
144 const uint16_t cid = entry.cid;
145 if (entry.sqId != m_Id || entry.sqHead >= m_Depth || cid >= m_Depth - 1U ||
146 !m_Slots[cid].active || m_Slots[cid].done) {
147 ERROR(
"NVMe: invalid completion on queue " << m_Id);
151 auto& slot = m_Slots[cid];
152 m_SubmissionHead = entry.sqHead;
153 slot.status = (status >> 1) & 0x7ffU;
154 slot.result = entry.result;
158 ++m_InterruptCompletions;
159 slot.completion.release();
160 if (++m_Head == m_Depth) {
168 if (!fromInterrupt && interruptsEnabled)
169 m_PolledInterrupt =
true;
171 m_Registers->
write32(m_Head, Nvme::Doorbells + (2U * m_Id + 1U) * m_Stride);
172 (void)m_Registers->
read32(Nvme::Status);
174 return consumed || credited;
176bool NvmeQueue::complete(
bool fromInterrupt) {
178 return observe(fromInterrupt);
181NvmeQueue::Result NvmeQueue::execute(
Nvme::Command command,
void* buffer,
size_t bytes,
182 bool writing,
bool interrupts,
size_t timeoutSeconds,
183 uint32_t* result,
bool interruptProbe,
bool cacheFill,
184 physical_uintptr_t directWritePhysical,
187 if (bytes > m_TransferBytes || (bytes && !buffer))
188 return Result::CommandError;
189 const bool directWrite = directWritePhysical != 0;
190 if (directWrite && (!writing || cacheFill || !bytes || bytes > Nvme::PageSize ||
191 (
reinterpret_cast<uintptr_t
>(buffer) & (Nvme::PageSize - 1)) ||
192 (directWritePhysical & (Nvme::PageSize - 1)) ||
193 uint64_t{directWritePhysical} >= (uint64_t{1} << 32))) {
194 return Result::CommandError;
196 physical_uintptr_t directPage = 0;
198 const size_t pageOffset =
reinterpret_cast<uintptr_t
>(buffer) & (Nvme::PageSize - 1);
200 if (cacheFill && !writing && bytes && !(pageOffset & 3U) &&
201 bytes <= Nvme::PageSize - pageOffset) {
204 physical_uintptr_t physical = 0;
205 void* page =
reinterpret_cast<void*
>(
reinterpret_cast<uintptr_t
>(buffer) - pageOffset);
206 if (space.getMapping(page, physical, flags) && physical && !(physical & (Nvme::PageSize - 1)) &&
211 if (directReadMapping && PciBus::instance().mapDmaPage(m_DmaDevice, physical, Nvme::PageSize,
212 *directReadMapping)) {
213 directPage = directReadMapping->address() + pageOffset;
220 return Result::TransportError;
227 return Result::TransportError;
229 for (; cid < m_Depth - 1U && m_Slots[cid].active; ++cid) {
231 const uint16_t nextTail = (m_Tail + 1U) % m_Depth;
232 if (cid == m_Depth - 1U || nextTail == m_SubmissionHead) {
234 return Result::TransportError;
236 auto& slot = m_Slots[cid];
237 [[maybe_unused]]
const size_t drained = slot.completion.
drainAvailable();
239 if (++m_Outstanding > m_MaximumOutstanding)
240 m_MaximumOutstanding = m_Outstanding;
244 if (writing && !directWrite) {
245 MemoryCopy(slot.data.virtualAddress(), buffer, bytes);
247 command.prp1 = directWrite ? directWritePhysical : direct ? directPage : slot.firstPage;
248 command.prp2 = directWrite || bytes <= Nvme::PageSize ? 0
249 : bytes <= 2 * Nvme::PageSize
250 ?
static_cast<uint64_t*
>(slot.prps.virtualAddress())[0]
251 : slot.prpsDma.address();
253 command.opcode = (command.opcode & 0xffffU) | (cid << 16);
255 submission[m_Tail] = command;
258 m_Registers->
write32(m_Tail, Nvme::Doorbells + 2U * m_Id * m_Stride);
259 (void)m_Registers->
read32(Nvme::Status);
261 const auto deadline = Time::getTicks() + timeoutSeconds * Time::Multiplier::Second;
262 bool interruptGrace = interruptProbe;
264 bool poll = !interrupts;
269 interruptGrace ? 0 : 10000);
270 interruptGrace =
false;
274 observe(
false, interrupts);
275 auto& slot = m_Slots[cid];
276 if (!m_Online || (m_Registers->
read32(Nvme::Status) & 2U)) {
279 return Result::TransportError;
282 const bool success = !slot.status;
283 if (success && bytes && !writing) {
286 MemoryCopy(buffer, slot.data.virtualAddress(), bytes);
290 *result = slot.result;
292 WARNING(
"NVMe: command " <<
Hex << (command.opcode & 255U) <<
" namespace "
293 << command.nsid <<
" status " << slot.status);
296 return success ? Result::Success : Result::CommandError;
298 if (Time::getTicks() >= deadline) {
299 ERROR(
"NVMe: command timeout on queue " << m_Id <<
", CID " << cid);
302 return Result::TransportError;
311 Time::delay(Time::Multiplier::Millisecond);
316size_t NvmeQueue::interruptCompletions()
const {
318 return m_InterruptCompletions;
320size_t NvmeQueue::maximumOutstanding()
const {
322 return m_MaximumOutstanding;
Abstrace base class for hardware I/O capabilities.
virtual void write32(uint32_t value, size_t offset=0)=0
virtual uint32_t read32(size_t offset=0)=0
void * virtualAddress() const
physical_uintptr_t physicalAddress() const
static const size_t continuous
static PhysicalMemoryManager & instance()
static const size_t below4GB
MUST_USE_RESULT size_t drainAvailable()
MUST_USE_RESULT bool acquireForCompletion(size_t n=1, size_t timeoutSecs=0, size_t timeoutUsecs=0)
static constexpr size_t getPageSize() noexcept
static const size_t CopyOnWrite
static const size_t Borrowed
static const size_t KernelMode
virtual bool getMapping(void *virtualAddress, physical_uintptr_t &physicalAddress, size_t &flags)=0
static const size_t NoAccess
static const size_t Write
static EXPORTED_PUBLIC VirtualAddressSpace & getKernelAddressSpace()
static const size_t Swapped