blob: 497dd238f510e6d6fdc9c4094e6226f76e908812 [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 <lib/driver/mmio/testing/cpp/test-helper.h>
#include <lib/driver/testing/cpp/scoped_global_logger.h>
#include <lib/fake-bti/bti.h>
#include <lib/mmio-ptr/fake.h>
#include <lib/sync/completion.h>
#include <lib/zx/bti.h>
#include <gtest/gtest.h>
#include "src/devices/block/drivers/nvme/commands.h"
#include "src/devices/block/drivers/nvme/registers.h"
#include "src/lib/testing/predicates/status.h"
namespace nvme {
class QueuePairTest : public ::testing::Test {
public:
void SetUp() override { ASSERT_OK(fake_bti_create(fake_bti_.reset_and_get_address())); }
protected:
TransactionData& txn(QueuePair* pair, size_t id) __TA_NO_THREAD_SAFETY_ANALYSIS {
return pair->txns_[id];
}
zx::bti fake_bti_;
// We only use the capability register for the doorbell stride, so 0 is fine.
CapabilityReg caps_ = CapabilityReg::Get().FromValue(0);
fdf::MmioBuffer mmio_ = fdf_testing::CreateMmioBuffer(
NVME_REG_DOORBELL_BASE * 0x100, ZX_CACHE_POLICY_UNCACHED_DEVICE, &kMmioOps, this);
// void doorbell_ring(bool is_completion, size_t queue_id, uint16_t value)
std::function<void(bool, size_t, uint16_t)> doorbell_ring_;
private:
static void Write32(const void* ctx, const mmio_buffer_t& mmio, uint32_t val, zx_off_t offs) {
offs -= NVME_REG_DOORBELL_BASE;
offs /= 4;
bool completion_doorbell = (offs & 1);
size_t queue_id = offs / 2;
static_cast<const QueuePairTest*>(ctx)->doorbell_ring_(completion_doorbell, queue_id,
val & 0xffff);
}
static uint32_t Read32(const void* ctx, const mmio_buffer_t& mmio, zx_off_t offs) {
ZX_ASSERT(false);
}
// Define read/write for |bits| that just crashes.
#define STUB_IO_OP(bits) \
static void Write##bits(const void* ctx, const mmio_buffer_t& mmio, uint##bits##_t val, \
zx_off_t offs) { \
ZX_ASSERT(false); \
} \
static uint##bits##_t Read##bits(const void* ctx, const mmio_buffer_t& mmio, zx_off_t offs) { \
ZX_ASSERT(false); \
}
STUB_IO_OP(64)
STUB_IO_OP(16)
STUB_IO_OP(8)
#undef STUB_IO_OP
static constexpr fdf::MmioBufferOps kMmioOps = {
.Read8 = Read8,
.Read16 = Read16,
.Read32 = Read32,
.Read64 = Read64,
.Write8 = Write8,
.Write16 = Write16,
.Write32 = Write32,
.Write64 = Write64,
};
fdf_testing::ScopedGlobalLogger logger_;
};
TEST_F(QueuePairTest, TestSubmit) {
auto pair = QueuePair::Create(fake_bti_.borrow(), 0, 100, caps_, mmio_, /*prealloc_prp=*/false);
ASSERT_OK(pair.status_value());
sync_completion_t rang;
size_t doorbell_ring_count = 0;
doorbell_ring_ = [&rang, &pair, &doorbell_ring_count](bool is_completion, size_t queue_id,
uint16_t value) {
ASSERT_FALSE(is_completion);
ASSERT_EQ(0u, queue_id);
ASSERT_EQ(doorbell_ring_count + 1, value);
doorbell_ring_count++;
Submission* submissions = static_cast<Submission*>(pair->submission().head());
ASSERT_EQ(0x9fu, submissions[value - 1].opcode());
sync_completion_signal(&rang);
};
Submission s(0x9f);
ASSERT_OK(pair->Submit(s, std::nullopt, 0, 0));
sync_completion_wait(&rang, ZX_TIME_INFINITE);
rang = {};
s.set_opcode(0x9f);
ASSERT_OK(pair->Submit(s, std::nullopt, 0, 0));
sync_completion_wait(&rang, ZX_TIME_INFINITE);
}
TEST_F(QueuePairTest, TestCheckCompletionsNothingReady) {
auto pair = QueuePair::Create(fake_bti_.borrow(), 0, 100, caps_, mmio_, /*prealloc_prp=*/false);
ASSERT_OK(pair.status_value());
Completion* completions = static_cast<Completion*>(pair->completion().head());
memset(completions, 0, sizeof(*completions));
doorbell_ring_ = [](bool, size_t, uint16_t) {
ZX_ASSERT_MSG(false, "Doorbell should not have been rung");
};
Completion* completion = nullptr;
ASSERT_EQ(pair->CheckForNewCompletion(&completion), ZX_ERR_SHOULD_WAIT);
}
TEST_F(QueuePairTest, TestCheckCompletionsOneReady) {
auto pair = QueuePair::Create(fake_bti_.borrow(), 0, 100, caps_, mmio_, /*prealloc_prp=*/false);
ASSERT_OK(pair.status_value());
doorbell_ring_ = [](bool is_completion, size_t queue_id, uint16_t new_value) {
ASSERT_FALSE(is_completion);
ASSERT_EQ(0u, queue_id);
ASSERT_EQ(1u, new_value);
};
Submission s(0);
ASSERT_OK(pair->Submit(s, std::nullopt, 0, 0));
Completion* completions = static_cast<Completion*>(pair->completion().head());
memset(completions, 0, sizeof(*completions) * pair->completion().entry_count());
completions[0].set_command_id(0);
completions[0].set_phase(1);
completions[0].set_sq_head(0);
doorbell_ring_ = [](bool is_completion, size_t queue_id, uint16_t new_value) {
ASSERT_TRUE(is_completion);
ASSERT_EQ(0u, queue_id);
ASSERT_EQ(1u, new_value);
};
Completion* completion = nullptr;
ASSERT_EQ(pair->CheckForNewCompletion(&completion), ZX_OK);
ASSERT_TRUE(completion->status_code_type() == StatusCodeType::kGeneric &&
completion->status_code() == 0);
pair->RingCompletionDb();
}
TEST_F(QueuePairTest, TestCheckCompletionsMultipleReady) {
auto pair = QueuePair::Create(fake_bti_.borrow(), 0, 100, caps_, mmio_, /*prealloc_prp=*/false);
ASSERT_OK(pair.status_value());
uint16_t expected_doorbell = 1;
doorbell_ring_ = [&expected_doorbell](bool is_completion, size_t queue_id, uint16_t new_value) {
ASSERT_FALSE(is_completion);
ASSERT_EQ(0u, queue_id);
ASSERT_EQ(expected_doorbell, new_value);
expected_doorbell++;
};
Submission s(0);
{
ASSERT_OK(pair->Submit(s, std::nullopt, 0, 0));
}
{
ASSERT_OK(pair->Submit(s, std::nullopt, 0, 0));
}
Completion* completions = static_cast<Completion*>(pair->completion().head());
memset(completions, 0, sizeof(*completions) * pair->completion().entry_count());
completions[0].set_command_id(0);
completions[0].set_phase(1);
completions[0].set_sq_head(0);
completions[1].set_command_id(1);
completions[1].set_phase(1);
completions[1].set_sq_head(1);
// Expect only a single ring of the doorbell.
doorbell_ring_ = [](bool is_completion, size_t queue_id, uint16_t new_value) {
ASSERT_TRUE(is_completion);
ASSERT_EQ(0u, queue_id);
ASSERT_EQ(2, new_value);
};
Completion* completion = nullptr;
ASSERT_EQ(pair->CheckForNewCompletion(&completion), ZX_OK);
ASSERT_TRUE(completion->status_code_type() == StatusCodeType::kGeneric &&
completion->status_code() == 0);
ASSERT_EQ(pair->CheckForNewCompletion(&completion), ZX_OK);
ASSERT_TRUE(completion->status_code_type() == StatusCodeType::kGeneric &&
completion->status_code() == 0);
pair->RingCompletionDb();
}
TEST_F(QueuePairTest, TestSubmitWithDataOnePage) {
auto pair = QueuePair::Create(fake_bti_.borrow(), 0, 100, caps_, mmio_, /*prealloc_prp=*/false);
ASSERT_OK(pair.status_value());
doorbell_ring_ = [](bool is_completion, size_t queue_id, uint16_t new_value) {
ASSERT_FALSE(is_completion);
ASSERT_EQ(0u, queue_id);
ASSERT_EQ(1u, new_value);
};
zx::vmo data_vmo;
ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &data_vmo));
Submission s(0xa9);
{
ASSERT_OK(pair->Submit(s, data_vmo.borrow(), 0, zx_system_get_page_size()));
}
Submission* submitted = static_cast<Submission*>(pair->submission().head());
ASSERT_EQ(0u, submitted->data_transfer_mode());
ASSERT_EQ(0u, submitted->fused());
ASSERT_EQ(0xa9u, submitted->opcode());
ASSERT_EQ(uint64_t{FAKE_BTI_PHYS_ADDR}, submitted->data_pointer[0]);
ASSERT_EQ(0u, submitted->data_pointer[1]);
TransactionData& txn_data = txn(pair.value().get(), 0);
ASSERT_FALSE(txn_data.prp_buffer);
ASSERT_TRUE(txn_data.active);
}
TEST_F(QueuePairTest, TestSubmitWithDataTwoPages) {
auto pair = QueuePair::Create(fake_bti_.borrow(), 0, 100, caps_, mmio_, /*prealloc_prp=*/false);
ASSERT_OK(pair.status_value());
doorbell_ring_ = [](bool is_completion, size_t queue_id, uint16_t new_value) {
ASSERT_FALSE(is_completion);
ASSERT_EQ(0u, queue_id);
ASSERT_EQ(1u, new_value);
};
zx::vmo data_vmo;
ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &data_vmo));
Submission s(0xa9);
{
ASSERT_OK(pair->Submit(s, data_vmo.borrow(), 0, zx_system_get_page_size()));
}
Submission* submitted = static_cast<Submission*>(pair->submission().head());
ASSERT_EQ(0u, submitted->data_transfer_mode());
ASSERT_EQ(0u, submitted->fused());
ASSERT_EQ(0xa9u, submitted->opcode());
ASSERT_EQ(uint64_t{FAKE_BTI_PHYS_ADDR}, submitted->data_pointer[0]);
ASSERT_EQ(0u, submitted->data_pointer[1]);
TransactionData& txn_data = txn(pair.value().get(), 0);
ASSERT_FALSE(txn_data.prp_buffer);
ASSERT_TRUE(txn_data.active);
}
// Not using QueuePair::PreparePrpList() for now. See QueuePair::kMaxTransferPages.
TEST_F(QueuePairTest, DISABLED_TestSubmitWithDataManyPages) {
auto pair = QueuePair::Create(fake_bti_.borrow(), 0, 100, caps_, mmio_, /*prealloc_prp=*/false);
ASSERT_OK(pair.status_value());
doorbell_ring_ = [](bool is_completion, size_t queue_id, uint16_t new_value) {
ASSERT_FALSE(is_completion);
ASSERT_EQ(0u, queue_id);
ASSERT_EQ(1u, new_value);
};
zx::vmo data_vmo;
constexpr size_t kNumPages = 4;
ASSERT_OK(zx::vmo::create(kNumPages * zx_system_get_page_size(), 0, &data_vmo));
Submission s(0xa9);
{
ASSERT_OK(pair->Submit(s, data_vmo.borrow(), 0, kNumPages * zx_system_get_page_size()));
}
Submission* submitted = static_cast<Submission*>(pair->submission().head());
ASSERT_EQ(0u, submitted->data_transfer_mode());
ASSERT_EQ(0u, submitted->fused());
ASSERT_EQ(0xa9u, submitted->opcode());
ASSERT_EQ(uint64_t{FAKE_BTI_PHYS_ADDR}, submitted->data_pointer[0]);
ASSERT_EQ(uint64_t{FAKE_BTI_PHYS_ADDR}, submitted->data_pointer[1]);
TransactionData& txn_data = txn(pair.value().get(), 0);
ASSERT_TRUE(txn_data.prp_buffer);
ASSERT_TRUE(txn_data.active);
uint64_t* prps = static_cast<uint64_t*>(txn_data.prp_buffer->virt());
for (size_t i = 0; i < kNumPages - 1; i++) {
ASSERT_EQ(uint64_t{FAKE_BTI_PHYS_ADDR}, prps[i]);
}
ASSERT_EQ(0u, prps[kNumPages - 1]);
}
// Not using QueuePair::PreparePrpList() for now. See QueuePair::kMaxTransferPages.
TEST_F(QueuePairTest, DISABLED_TestSubmitWithMultiPagePrp) {
auto pair = QueuePair::Create(fake_bti_.borrow(), 0, 100, caps_, mmio_, /*prealloc_prp=*/false);
ASSERT_OK(pair.status_value());
doorbell_ring_ = [](bool is_completion, size_t queue_id, uint16_t new_value) {
ASSERT_FALSE(is_completion);
ASSERT_EQ(0u, queue_id);
ASSERT_EQ(1u, new_value);
};
zx::vmo data_vmo;
const size_t addr_per_page = zx_system_get_page_size() / sizeof(uint64_t);
const size_t kNumAddresses = addr_per_page + 10;
ASSERT_OK(zx::vmo::create(zx_system_get_page_size() * kNumAddresses, 0, &data_vmo));
Submission s(0xa9);
{
ASSERT_OK(pair->Submit(s, data_vmo.borrow(), 0, zx_system_get_page_size() * kNumAddresses));
}
Submission* submitted = static_cast<Submission*>(pair->submission().head());
ASSERT_EQ(0u, submitted->data_transfer_mode());
ASSERT_EQ(0u, submitted->fused());
ASSERT_EQ(0xa9u, submitted->opcode());
ASSERT_EQ(uint64_t{FAKE_BTI_PHYS_ADDR}, submitted->data_pointer[0]);
ASSERT_EQ(uint64_t{FAKE_BTI_PHYS_ADDR}, submitted->data_pointer[1]);
TransactionData& txn_data = txn(pair.value().get(), 0);
ASSERT_TRUE(txn_data.prp_buffer);
ASSERT_TRUE(txn_data.active);
uint64_t* prps = static_cast<uint64_t*>(txn_data.prp_buffer->virt());
for (size_t i = 0; i < kNumAddresses; i++) {
ZX_ASSERT_MSG(FAKE_BTI_PHYS_ADDR == prps[i], "PRP %zu had wrong value", i);
}
ASSERT_EQ(0u, prps[kNumAddresses]);
}
} // namespace nvme