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"
13NvmeQueue::Slot::Slot()
14 : data(
"NVMe transfer"),
15 prps(
"NVMe PRP list"),
22 : m_Registers(nullptr),
23 m_Submission(
"NVMe SQ"),
24 m_Completion(
"NVMe CQ"),
25 m_Available(0, false),
35 m_PolledInterrupt(false),
36 m_InterruptCompletions(0),
38 m_MaximumOutstanding(0) {}
40bool NvmeQueue::initialise(
IoBase* registers, uint16_t
id, uint16_t depth,
size_t stride,
41 size_t transferBytes) {
43 !transferBytes || transferBytes > Nvme::MaxTransfer || transferBytes % Nvme::PageSize)
45 m_Registers = registers;
49 m_TransferBytes = transferBytes;
53 if (!memory.allocateRegion(m_Submission, 1, constraints, flags) ||
54 !memory.allocateRegion(m_Completion, 1, constraints, flags))
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))
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());
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,
75 if (address >= (uint64_t{1} << 32))
78 slot.firstPage = address;
80 prps[page - 1] = address;
89void NvmeQueue::stopLocked() {
93 for (
size_t i = 0; i < m_Depth - 1U; ++i) {
94 if (m_Slots[i].active)
95 m_Slots[i].completion.
release();
99void NvmeQueue::stop() {
104bool NvmeQueue::observe(
bool fromInterrupt,
bool interruptsEnabled) {
105 const bool credited = fromInterrupt && m_PolledInterrupt;
107 m_PolledInterrupt =
false;
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)
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);
126 auto& slot = m_Slots[cid];
127 m_SubmissionHead = entry.sqHead;
128 slot.status = (status >> 1) & 0x7ffU;
129 slot.result = entry.result;
133 ++m_InterruptCompletions;
134 slot.completion.release();
135 if (++m_Head == m_Depth) {
143 if (!fromInterrupt && interruptsEnabled)
144 m_PolledInterrupt =
true;
146 m_Registers->
write32(m_Head, Nvme::Doorbells + (2U * m_Id + 1U) * m_Stride);
147 (void)m_Registers->
read32(Nvme::Status);
149 return consumed || credited;
151bool NvmeQueue::complete(
bool fromInterrupt) {
153 return observe(fromInterrupt);
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) {
160 if (bytes > m_TransferBytes || (bytes && !buffer))
161 return Result::CommandError;
164 return Result::TransportError;
171 return Result::TransportError;
173 for (; cid < m_Depth - 1U && m_Slots[cid].active; ++cid) {
175 const uint16_t nextTail = (m_Tail + 1U) % m_Depth;
176 if (cid == m_Depth - 1U || nextTail == m_SubmissionHead) {
178 return Result::TransportError;
180 auto& slot = m_Slots[cid];
181 [[maybe_unused]]
const size_t drained = slot.completion.
drainAvailable();
183 if (++m_Outstanding > m_MaximumOutstanding)
184 m_MaximumOutstanding = m_Outstanding;
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]
196 command.opcode = (command.opcode & 0xffffU) | (cid << 16);
198 submission[m_Tail] = command;
201 m_Registers->
write32(m_Tail, Nvme::Doorbells + 2U * m_Id * m_Stride);
202 (void)m_Registers->
read32(Nvme::Status);
204 const auto deadline = Time::getTicks() + timeoutSeconds * Time::Multiplier::Second;
205 bool interruptGrace = interruptProbe;
207 bool poll = !interrupts;
212 interruptGrace ? 0 : 10000);
213 interruptGrace =
false;
217 observe(
false, interrupts);
218 auto& slot = m_Slots[cid];
219 if (!m_Online || (m_Registers->
read32(Nvme::Status) & 2U)) {
222 return Result::TransportError;
225 const bool success = !slot.status;
226 if (success && bytes && !writing) {
228 MemoryCopy(buffer, slot.data.virtualAddress(), bytes);
231 *result = slot.result;
233 WARNING(
"NVMe: command " <<
Hex << (command.opcode & 255U) <<
" namespace "
234 << command.nsid <<
" status " << slot.status);
237 return success ? Result::Success : Result::CommandError;
239 if (Time::getTicks() >= deadline) {
240 ERROR(
"NVMe: command timeout on queue " << m_Id <<
", CID " << cid);
243 return Result::TransportError;
247 Time::delay(Time::Multiplier::Millisecond);
250size_t NvmeQueue::interruptCompletions()
const {
252 return m_InterruptCompletions;
254size_t NvmeQueue::maximumOutstanding()
const {
256 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
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 KernelMode
virtual bool getMapping(void *virtualAddress, physical_uintptr_t &physicalAddress, size_t &flags)=0
static const size_t Write
static EXPORTED_PUBLIC VirtualAddressSpace & getKernelAddressSpace()
uintptr_t physicalAddress(physical_uintptr_t address) PURE