blob: 44aad87860424dd4bd3a93c1b7efb969a8944fae [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/devices/block/drivers/nvme/queue-pair.h"
#include <zircon/errors.h>
#include <zircon/syscalls.h>
#include <sdk/lib/driver/logging/cpp/logger.h>
#include "src/devices/block/drivers/nvme/commands.h"
#include "src/devices/block/drivers/nvme/commands/nvme-io.h"
#include "src/devices/block/drivers/nvme/registers.h"
namespace nvme {
zx::result<std::unique_ptr<QueuePair>> QueuePair::Create(zx::unowned_bti bti, uint16_t queue_id,
uint32_t max_entries, CapabilityReg& caps,
fdf::MmioBuffer& mmio, bool prealloc_prp) {
auto completion_queue = Queue::Create(bti->borrow(), queue_id, max_entries, sizeof(Completion));
if (completion_queue.is_error()) {
return completion_queue.take_error();
}
auto submission_queue = Queue::Create(bti->borrow(), queue_id, max_entries, sizeof(Submission));
if (submission_queue.is_error()) {
return submission_queue.take_error();
}
fbl::AllocChecker ac;
fbl::Vector<TransactionData> txns;
txns.resize(submission_queue->entry_count(), &ac);
if (!ac.check()) {
FDF_LOG(ERROR, "Failed to allocate memory for %u transactions for queue pair with id %u.",
submission_queue->entry_count(), queue_id);
return zx::error(ZX_ERR_NO_MEMORY);
}
auto completion_doorbell = DoorbellReg::CompletionQueue(queue_id, caps).FromValue(0);
auto submission_doorbell = DoorbellReg::SubmissionQueue(queue_id, caps).FromValue(0);
auto queue_pair = fbl::make_unique_checked<QueuePair>(
&ac, std::move(*completion_queue), std::move(*submission_queue), std::move(txns),
std::move(bti), mmio, completion_doorbell, submission_doorbell);
if (!ac.check()) {
FDF_LOG(ERROR, "Failed to allocate memory for queue pair with id %u.", queue_id);
return zx::error(ZX_ERR_NO_MEMORY);
}
if (prealloc_prp) {
zx_status_t status = queue_pair->PreallocatePrpBuffers();
if (status != ZX_OK) {
return zx::error(status);
}
}
return zx::ok(std::move(queue_pair));
}
zx_status_t QueuePair::PreallocatePrpBuffers() {
auto buffer_factory = dma_buffer::CreateBufferFactory();
for (auto& txn_data : txns_) {
zx_status_t status = buffer_factory->CreateContiguous(*bti_, zx_system_get_page_size(), 0, true,
&txn_data.prp_buffer);
if (status != ZX_OK) {
return status;
}
}
return ZX_OK;
}
zx_status_t QueuePair::CheckForNewCompletion(Completion** completion, IoCommand** io_cmd) {
if (static_cast<Completion*>(completion_.Peek())->phase() != completion_ready_phase_) {
return ZX_ERR_SHOULD_WAIT;
}
*completion = static_cast<Completion*>(completion_.Next());
if (completion_.NextIndex() == 0) {
// Toggle the ready phase when we're about to wrap around.
completion_ready_phase_ ^= 1;
}
sq_head_ = (*completion)->sq_head();
auto txn_id = (*completion)->command_id();
{
if (txn_id > txns_.size()) {
FDF_LOG(ERROR, "Completed transaction has invalid ID: %u", txn_id);
return ZX_ERR_BAD_STATE;
}
TransactionData& txn_data = txns_[txn_id];
if (!txn_data.active) {
FDF_LOG(ERROR, "Completed transaction #%u was not active.", txn_id);
return ZX_ERR_BAD_STATE;
}
if (io_cmd != nullptr) {
*io_cmd = txn_data.io_cmd;
}
if (txn_data.pmt.is_valid()) {
zx_status_t status = txn_data.pmt.unpin();
if (status != ZX_OK) {
FDF_LOG(ERROR, "Failed to unpin IO buffer: %s", zx_status_get_string(status));
return ZX_ERR_INTERNAL;
}
}
txn_data.ClearExceptPrp();
}
return ZX_OK;
}
void QueuePair::RingCompletionDb() {
// Ring the doorbell.
completion_doorbell_.set_value(static_cast<uint32_t>(completion_.NextIndex())).WriteTo(&mmio_);
}
zx_status_t QueuePair::Submit(cpp20::span<uint8_t> submission_data,
std::optional<zx::unowned_vmo> data_vmo, zx_off_t vmo_offset,
size_t bytes, IoCommand* io_cmd) {
if ((submission_.NextIndex() + 1) % submission_.entry_count() == sq_head_) {
// No room. Try again later.
return ZX_ERR_SHOULD_WAIT;
}
if (submission_data.size() != sizeof(Submission)) {
return ZX_ERR_INVALID_ARGS;
}
// Allocate a new submission
size_t index = submission_.NextIndex();
TransactionData& txn_data = txns_[index];
if (txn_data.active) {
// TODO(https://fxbug.dev/42053036): Consider decoupling the submission queue from the tracking
// of outstanding transactions. We tie a transaction's state to its submission queue slot, even
// if the submission entry has already been consumed by the controller.
return ZX_ERR_SHOULD_WAIT;
}
txn_data.ClearExceptPrp();
txn_data.io_cmd = io_cmd;
txn_data.data_vmo = data_vmo;
// We only peek here so that if the transaction setup fails somewhere we can easily roll-back
auto submission = static_cast<Submission*>(submission_.Peek());
// Copy provided data into place.
memcpy(submission, submission_data.data(), submission_data.size());
// We do not support metadata.
submission->metadata_pointer = 0;
submission->set_cid(static_cast<uint32_t>(index)).set_fused(0).set_data_transfer_mode(0);
if (data_vmo.has_value()) {
// Page offset of first page of transfer
const size_t page_offset = vmo_offset & (~kPageMask);
// Byte offset into first page of transfer
const size_t byte_offset = vmo_offset & kPageMask;
// Total pages mapped / touched
const size_t page_count = (byte_offset + bytes + kPageMask) >> kPageShift;
if (page_count > kMaxTransferPages) {
FDF_LOG(ERROR, "Did not expect a single transaction to transfer more than %u pages.",
kMaxTransferPages);
return ZX_ERR_BAD_STATE;
}
zx_paddr_t single_page;
zx_paddr_t* page_list;
if (io_cmd == nullptr) {
if (page_count != 1) {
FDF_LOG(ERROR, "Unexpected page count for admin command.");
return ZX_ERR_INVALID_ARGS;
}
page_list = &single_page;
} else {
if (!txn_data.prp_buffer) {
FDF_LOG(ERROR, "No PRP buffer was preallocated for this IO transaction.");
return ZX_ERR_BAD_STATE;
}
page_list = static_cast<zx_paddr_t*>(txn_data.prp_buffer->virt());
}
// Read disk and write memory (PERM_WRITE), or read memory (PERM_READ) and write disk.
const uint32_t options =
submission->opcode() == IoCommandOpcode::kWrite ? ZX_BTI_PERM_READ : ZX_BTI_PERM_WRITE;
// These get unpinned in CheckForNewCompletion().
zx_status_t status = bti_->pin(options, *data_vmo.value(), page_offset,
page_count << kPageShift, page_list, page_count, &txn_data.pmt);
if (status != ZX_OK) {
FDF_LOG(ERROR, "Failed to pin IO buffer: %s", zx_status_get_string(status));
return status;
}
FDF_LOG(TRACE, "Submitting transaction #%zu command %p: op=%s, pages=%zu", index, io_cmd,
options == ZX_BTI_PERM_WRITE ? "RD" : "WR", page_count);
submission->data_pointer[0] = page_list[0] | byte_offset;
if (page_count == 2) {
submission->data_pointer[1] = page_list[1];
} else if (page_count > 2) {
// See QueuePair::kMaxTransferPages.
submission->data_pointer[1] = txn_data.prp_buffer->phys() + sizeof(zx_paddr_t*);
}
}
// We used Peek() before, so advance the pointer, and mark the transaction as in-flight.
submission_.Next();
txn_data.active = true;
// Ring the doorbell.
submission_doorbell_.set_value(static_cast<uint32_t>(submission_.NextIndex())).WriteTo(&mmio_);
return ZX_OK;
}
zx_status_t QueuePair::PreparePrpList(std::unique_ptr<dma_buffer::PagedBuffer>& buf,
cpp20::span<const zx_paddr_t> pages) {
const size_t addresses_per_page = zx_system_get_page_size() / sizeof(zx_paddr_t);
size_t page_count = 0;
// TODO(https://fxbug.dev/42053036): improve this in cases where we would allocate a page with
// only one entry.
page_count = pages.size() / (addresses_per_page - 1);
page_count += 1;
auto buffer_factory = dma_buffer::CreateBufferFactory();
zx_status_t status = buffer_factory->CreatePaged(*bti_, page_count * zx_system_get_page_size(),
/*enable_cache=*/false, &buf);
if (status != ZX_OK) {
return status;
}
zx_paddr_t* addresses = static_cast<zx_paddr_t*>(buf->virt());
size_t prp_index = 0;
const zx_paddr_t* prp_list_pages = buf->phys();
size_t prp_list_page_count = page_count;
for (size_t i = 0; i < pages.size(); i++, prp_index++) {
// If we're about to cross a page boundary, put the address of the next page here.
if (prp_index % addresses_per_page == (addresses_per_page - 1)) {
if (prp_list_page_count == 0) {
FDF_LOG(ERROR, "Ran out of PRP pages?");
return ZX_ERR_INTERNAL;
}
addresses[prp_index] = *prp_list_pages;
prp_list_pages++;
prp_list_page_count--;
prp_index++;
}
addresses[prp_index] = pages[i];
}
return ZX_OK;
}
} // namespace nvme