blob: 05e7a7834b728710b6085274817e2cf402a797ab [file] [log] [blame]
// Copyright 2019 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 "sdmmc-block-device.h"
#include <hw/sdmmc.h>
#include <lib/fake_ddk/fake_ddk.h>
#include <lib/mock-function/mock-function.h>
#include <zircon/thread_annotations.h>
#include <zxtest/zxtest.h>
#include "mock-sdmmc-device.h"
namespace sdmmc {
class SdmmcBlockDeviceTest : public SdmmcBlockDevice {
public:
SdmmcBlockDeviceTest(MockSdmmcDevice* mock_sdmmc, const block_info_t& block_info)
: SdmmcBlockDevice(fake_ddk::kFakeParent, SdmmcDevice({}, {})), mock_sdmmc_(mock_sdmmc) {
block_info_ = block_info;
}
auto& mock_DoTxn() { return mock_do_txn_; }
auto& mock_BlockComplete() { return mock_block_complete_; }
void VerifyAll() {
mock_do_txn_.VerifyAndClear();
mock_block_complete_.VerifyAndClear();
}
void WaitForBlockOps(uint32_t count) {
fbl::AutoLock lock(&lock_);
for (;;) {
if (block_ops_done_ >= count) {
block_ops_done_ = 0;
return;
}
block_ops_event_.Wait(&lock_);
}
}
void DoTxn(uint32_t command, uint32_t length, uint64_t offset) {
std::optional<block::Operation<>> op = MakeBlockOp(command, length, offset);
ASSERT_TRUE(op.has_value());
BlockOperation unowned_op(op->operation(), nullptr, nullptr, sizeof(block_op_t), false);
DoTxn(&unowned_op);
}
void DoTxn(BlockOperation* txn) override {
if (mock_do_txn_.HasExpectations()) {
fbl::AutoLock lock(&lock_);
const block_read_write_t& brw = txn->operation()->rw;
mock_do_txn_.Call(brw.command, brw.length, brw.offset_dev);
block_ops_done_++;
block_ops_event_.Broadcast();
} else {
SdmmcBlockDevice::DoTxn(txn);
}
}
zx_status_t WaitForTran() override { return ZX_OK; }
std::optional<block::Operation<>> MakeBlockOp(uint32_t command, uint32_t length,
uint64_t offset) {
block_info_t block_info;
size_t op_size;
BlockImplQuery(&block_info, &op_size);
std::optional<block::Operation<>> op = block::Operation<>::Alloc(op_size);
if (op) {
*op->operation() = block_op_t{
.rw = {
.command = command,
.extra = 0,
.vmo = ZX_HANDLE_INVALID,
.length = length,
.offset_dev = offset,
.offset_vmo = 0
}
};
}
return op;
}
private:
SdmmcDevice& sdmmc() override { return *mock_sdmmc_; }
void BlockComplete(BlockOperation* txn, zx_status_t status,
trace_async_id_t async_id) override {
const block_read_write_t& brw = txn->operation()->rw;
mock_block_complete_.Call(brw.command, brw.length, brw.offset_dev, status);
}
MockSdmmcDevice* mock_sdmmc_;
fbl::Mutex lock_;
fbl::ConditionVariable block_ops_event_ TA_GUARDED(lock_);
uint32_t block_ops_done_ TA_GUARDED(lock_) = 0;
mock_function::MockFunction<void, uint32_t, uint32_t, uint64_t> mock_do_txn_;
mock_function::MockFunction<void, uint32_t, uint32_t, uint64_t, zx_status_t>
mock_block_complete_;
};
TEST(SdmmcBlockDeviceTest, BlockImplQueue) {
MockSdmmcDevice mock_sdmmc({});
SdmmcBlockDeviceTest dut(&mock_sdmmc, {
.block_count = 0x10000,
.block_size = 512,
.max_transfer_size = BLOCK_MAX_TRANSFER_UNBOUNDED,
.flags = 0,
.reserved = 0
});
std::optional<block::Operation<>> op1 = dut.MakeBlockOp(BLOCK_OP_WRITE, 1, 0);
ASSERT_TRUE(op1.has_value());
std::optional<block::Operation<>> op2 = dut.MakeBlockOp(BLOCK_OP_WRITE, 5, 0x8000);
ASSERT_TRUE(op2.has_value());
std::optional<block::Operation<>> op3 = dut.MakeBlockOp(BLOCK_OP_FLUSH, 0, 0);
ASSERT_TRUE(op3.has_value());
std::optional<block::Operation<>> op4 = dut.MakeBlockOp(BLOCK_OP_READ, 1, 0x400);
ASSERT_TRUE(op4.has_value());
std::optional<block::Operation<>> op5 = dut.MakeBlockOp(BLOCK_OP_READ, 10, 0x2000);
ASSERT_TRUE(op5.has_value());
ASSERT_OK(dut.StartWorkerThread());
dut.mock_DoTxn()
.ExpectCall(BLOCK_OP_WRITE, 1, 0)
.ExpectCall(BLOCK_OP_WRITE, 5, 0x8000)
.ExpectCall(BLOCK_OP_FLUSH, 0, 0)
.ExpectCall(BLOCK_OP_READ, 1, 0x400)
.ExpectCall(BLOCK_OP_READ, 10, 0x2000);
// BlockComplete is always mocked, so the BlockOperation that gets created for this call
// will get automatically completed with ZX_ERR_INTERNAL upon destruction. Give it a no-op
// callback to keep this from causing test errors.
auto noop_callback = [](void* ctx, zx_status_t status, block_op_t* op) {};
dut.BlockImplQueue(op1->operation(), noop_callback, nullptr);
dut.BlockImplQueue(op2->operation(), noop_callback, nullptr);
dut.BlockImplQueue(op3->operation(), noop_callback, nullptr);
dut.BlockImplQueue(op4->operation(), noop_callback, nullptr);
dut.BlockImplQueue(op5->operation(), noop_callback, nullptr);
dut.WaitForBlockOps(5);
dut.StopWorkerThread();
dut.VerifyAll();
mock_sdmmc.VerifyAll();
}
TEST(SdmmcBlockDeviceTest, BlockImplQueueOutOfRange) {
MockSdmmcDevice mock_sdmmc({});
SdmmcBlockDeviceTest dut(&mock_sdmmc, {
.block_count = 0x1000,
.block_size = 512,
.max_transfer_size = BLOCK_MAX_TRANSFER_UNBOUNDED,
.flags = 0,
.reserved = 0
});
std::optional<block::Operation<>> op1 = dut.MakeBlockOp(BLOCK_OP_WRITE, 1, 0x1000);
ASSERT_TRUE(op1.has_value());
std::optional<block::Operation<>> op2 = dut.MakeBlockOp(BLOCK_OP_READ, 10, 0x2000);
ASSERT_TRUE(op2.has_value());
std::optional<block::Operation<>> op3 = dut.MakeBlockOp(BLOCK_OP_WRITE, 8, 0xff8);
ASSERT_TRUE(op3.has_value());
std::optional<block::Operation<>> op4 = dut.MakeBlockOp(BLOCK_OP_READ, 9, 0xff8);
ASSERT_TRUE(op4.has_value());
std::optional<block::Operation<>> op5 = dut.MakeBlockOp(BLOCK_OP_WRITE, 16, 0xff8);
ASSERT_TRUE(op5.has_value());
std::optional<block::Operation<>> op6 = dut.MakeBlockOp(BLOCK_OP_READ, 0, 0x800);
ASSERT_TRUE(op6.has_value());
std::optional<block::Operation<>> op7 = dut.MakeBlockOp(BLOCK_OP_WRITE, 1, 0xfff);
ASSERT_TRUE(op7.has_value());
ASSERT_OK(dut.StartWorkerThread());
dut.mock_DoTxn()
.ExpectCall(BLOCK_OP_WRITE, 8, 0xff8)
.ExpectCall(BLOCK_OP_WRITE, 1, 0xfff);
dut.mock_BlockComplete()
.ExpectCall(BLOCK_OP_WRITE, 1, 0x1000, ZX_ERR_OUT_OF_RANGE)
.ExpectCall(BLOCK_OP_READ, 10, 0x2000, ZX_ERR_OUT_OF_RANGE)
.ExpectCall(BLOCK_OP_READ, 9, 0xff8, ZX_ERR_OUT_OF_RANGE)
.ExpectCall(BLOCK_OP_WRITE, 16, 0xff8, ZX_ERR_OUT_OF_RANGE)
.ExpectCall(BLOCK_OP_READ, 0, 0x800, ZX_OK);
auto noop_callback = [](void* ctx, zx_status_t status, block_op_t* op) {};
dut.BlockImplQueue(op1->operation(), noop_callback, nullptr);
dut.BlockImplQueue(op2->operation(), noop_callback, nullptr);
dut.BlockImplQueue(op3->operation(), noop_callback, nullptr);
dut.BlockImplQueue(op4->operation(), noop_callback, nullptr);
dut.BlockImplQueue(op5->operation(), noop_callback, nullptr);
dut.BlockImplQueue(op6->operation(), noop_callback, nullptr);
dut.BlockImplQueue(op7->operation(), noop_callback, nullptr);
dut.WaitForBlockOps(2);
dut.StopWorkerThread();
dut.VerifyAll();
mock_sdmmc.VerifyAll();
}
TEST(SdmmcBlockDeviceTest, DoTxn) {
MockSdmmcDevice mock_sdmmc({
.caps = SDMMC_HOST_CAP_ADMA2 | SDMMC_HOST_CAP_SIXTY_FOUR_BIT | SDMMC_HOST_CAP_AUTO_CMD12,
.max_transfer_size = BLOCK_MAX_TRANSFER_UNBOUNDED,
.max_transfer_size_non_dma = 0,
.prefs = 0
});
SdmmcBlockDeviceTest dut(&mock_sdmmc, {
.block_count = 0x10000,
.block_size = 512,
.max_transfer_size = BLOCK_MAX_TRANSFER_UNBOUNDED,
.flags = 0,
.reserved = 0
});
mock_sdmmc.mock_SdmmcRequest().ExpectCall(ZX_OK, SDMMC_WRITE_BLOCK, 0, 1, 512);
dut.mock_BlockComplete().ExpectCall(BLOCK_OP_WRITE, 1, 0, ZX_OK);
mock_sdmmc.mock_SdmmcRequest().ExpectCall(ZX_OK, SDMMC_WRITE_MULTIPLE_BLOCK, 0x8000, 5, 512);
dut.mock_BlockComplete().ExpectCall(BLOCK_OP_WRITE, 5, 0x8000, ZX_OK);
dut.mock_BlockComplete().ExpectCall(BLOCK_OP_FLUSH, 0, 0, ZX_OK);
mock_sdmmc.mock_SdmmcRequest().ExpectCall(ZX_OK, SDMMC_READ_BLOCK, 0x400, 1, 512);
dut.mock_BlockComplete().ExpectCall(BLOCK_OP_READ, 1, 0x400, ZX_OK);
mock_sdmmc.mock_SdmmcRequest().ExpectCall(ZX_OK, SDMMC_READ_MULTIPLE_BLOCK, 0x2000, 10, 512);
dut.mock_BlockComplete().ExpectCall(BLOCK_OP_READ, 10, 0x2000, ZX_OK);
dut.DoTxn(BLOCK_OP_WRITE, 1, 0);
dut.DoTxn(BLOCK_OP_WRITE, 5, 0x8000);
dut.DoTxn(BLOCK_OP_FLUSH, 0, 0);
dut.DoTxn(BLOCK_OP_READ, 1, 0x400);
dut.DoTxn(BLOCK_OP_READ, 10, 0x2000);
dut.VerifyAll();
mock_sdmmc.VerifyAll();
}
TEST(SdmmcBlockDeviceTest, DoTxnNoACmd12) {
MockSdmmcDevice mock_sdmmc({
.caps = SDMMC_HOST_CAP_ADMA2 | SDMMC_HOST_CAP_SIXTY_FOUR_BIT,
.max_transfer_size = BLOCK_MAX_TRANSFER_UNBOUNDED,
.max_transfer_size_non_dma = 0,
.prefs = 0
});
SdmmcBlockDeviceTest dut(&mock_sdmmc, {
.block_count = 0x10000,
.block_size = 512,
.max_transfer_size = BLOCK_MAX_TRANSFER_UNBOUNDED,
.flags = 0,
.reserved = 0
});
mock_sdmmc.mock_SdmmcRequest().ExpectCall(ZX_OK, SDMMC_WRITE_BLOCK, 0, 1, 512);
dut.mock_BlockComplete().ExpectCall(BLOCK_OP_WRITE, 1, 0, ZX_OK);
mock_sdmmc.mock_SdmmcRequest().ExpectCall(ZX_OK, SDMMC_WRITE_MULTIPLE_BLOCK, 0x8000, 5, 512);
mock_sdmmc.mock_SdmmcStopTransmission().ExpectCall(ZX_OK);
dut.mock_BlockComplete().ExpectCall(BLOCK_OP_WRITE, 5, 0x8000, ZX_OK);
dut.mock_BlockComplete().ExpectCall(BLOCK_OP_FLUSH, 0, 0, ZX_OK);
mock_sdmmc.mock_SdmmcRequest().ExpectCall(ZX_OK, SDMMC_READ_BLOCK, 0x400, 1, 512);
dut.mock_BlockComplete().ExpectCall(BLOCK_OP_READ, 1, 0x400, ZX_OK);
mock_sdmmc.mock_SdmmcRequest().ExpectCall(ZX_OK, SDMMC_READ_MULTIPLE_BLOCK, 0x2000, 10, 512);
mock_sdmmc.mock_SdmmcStopTransmission().ExpectCall(ZX_OK);
dut.mock_BlockComplete().ExpectCall(BLOCK_OP_READ, 10, 0x2000, ZX_OK);
dut.DoTxn(BLOCK_OP_WRITE, 1, 0);
dut.DoTxn(BLOCK_OP_WRITE, 5, 0x8000);
dut.DoTxn(BLOCK_OP_FLUSH, 0, 0);
dut.DoTxn(BLOCK_OP_READ, 1, 0x400);
dut.DoTxn(BLOCK_OP_READ, 10, 0x2000);
dut.VerifyAll();
mock_sdmmc.VerifyAll();
}
TEST(SdmmcBlockDeviceTest, DoTxnErrorsPropagate) {
MockSdmmcDevice mock_sdmmc({
.caps = SDMMC_HOST_CAP_ADMA2 | SDMMC_HOST_CAP_SIXTY_FOUR_BIT,
.max_transfer_size = BLOCK_MAX_TRANSFER_UNBOUNDED,
.max_transfer_size_non_dma = 0,
.prefs = 0
});
SdmmcBlockDeviceTest dut(&mock_sdmmc, {
.block_count = 0x10000,
.block_size = 512,
.max_transfer_size = BLOCK_MAX_TRANSFER_UNBOUNDED,
.flags = 0,
.reserved = 0
});
mock_sdmmc.mock_SdmmcRequest().ExpectCall(ZX_ERR_IO, SDMMC_WRITE_BLOCK, 0, 1, 512);
dut.mock_BlockComplete().ExpectCall(BLOCK_OP_WRITE, 1, 0, ZX_ERR_IO);
mock_sdmmc.mock_SdmmcRequest().ExpectCall(ZX_ERR_BAD_STATE, SDMMC_WRITE_MULTIPLE_BLOCK, 0x8000,
5, 512);
dut.mock_BlockComplete().ExpectCall(BLOCK_OP_WRITE, 5, 0x8000, ZX_ERR_BAD_STATE);
dut.mock_BlockComplete().ExpectCall(BLOCK_OP_FLUSH, 0, 0, ZX_OK);
mock_sdmmc.mock_SdmmcRequest().ExpectCall(ZX_ERR_TIMED_OUT, SDMMC_READ_BLOCK, 0x400, 1, 512);
dut.mock_BlockComplete().ExpectCall(BLOCK_OP_READ, 1, 0x400, ZX_ERR_TIMED_OUT);
mock_sdmmc.mock_SdmmcRequest().ExpectCall(ZX_OK, SDMMC_READ_MULTIPLE_BLOCK, 0x2000, 10, 512);
mock_sdmmc.mock_SdmmcStopTransmission().ExpectCall(ZX_ERR_IO_DATA_INTEGRITY);
dut.mock_BlockComplete().ExpectCall(BLOCK_OP_READ, 10, 0x2000, ZX_ERR_IO_DATA_INTEGRITY);
dut.DoTxn(BLOCK_OP_WRITE, 1, 0);
dut.DoTxn(BLOCK_OP_WRITE, 5, 0x8000);
dut.DoTxn(BLOCK_OP_FLUSH, 0, 0);
dut.DoTxn(BLOCK_OP_READ, 1, 0x400);
dut.DoTxn(BLOCK_OP_READ, 10, 0x2000);
dut.VerifyAll();
mock_sdmmc.VerifyAll();
}
TEST(SdmmcBlockDeviceTest, DdkLifecycle) {
MockSdmmcDevice mock_sdmmc({});
SdmmcBlockDeviceTest dut(&mock_sdmmc, {});
fake_ddk::Bind ddk;
EXPECT_OK(dut.AddDevice());
dut.DdkUnbind();
EXPECT_TRUE(ddk.Ok());
dut.StopWorkerThread();
}
} // namespace sdmmc