blob: eb953261fab7886fa744cf934e12ad65e5934c65 [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 <endian.h>
#include <fbl/algorithm.h>
#include <fbl/auto_call.h>
#include <hw/sdmmc.h>
#include <lib/fake_ddk/fake_ddk.h>
#include <lib/fzl/vmo-mapper.h>
#include <zxtest/zxtest.h>
#include "fake-sdmmc-device.h"
namespace sdmmc {
class SdmmcBlockDeviceTest : public zxtest::Test {
public:
SdmmcBlockDeviceTest() : dut_(fake_ddk::kFakeParent, SdmmcDevice(sdmmc_.GetClient())) {
for (size_t i = 0; i < (FakeSdmmcDevice::kBlockSize / sizeof(kTestData)); i++) {
test_block_.insert(test_block_.end(), kTestData, kTestData + sizeof(kTestData));
}
}
void SetUp() override {
sdmmc_.Reset();
sdmmc_.set_command_callback(SDMMC_SEND_CSD, [](sdmmc_req_t* req) -> void {
uint8_t* response = reinterpret_cast<uint8_t*>(req->response);
response[MMC_CSD_SPEC_VERSION] = MMC_CID_SPEC_VRSN_40 << 2;
response[MMC_CSD_SIZE_START] = 0x03 << 6;
response[MMC_CSD_SIZE_START + 1] = 0xff;
response[MMC_CSD_SIZE_START + 2] = 0x03;
});
sdmmc_.set_command_callback(MMC_SEND_EXT_CSD, [](sdmmc_req_t* req) -> void {
uint8_t* const ext_csd = reinterpret_cast<uint8_t*>(req->virt_buffer) + req->buf_offset;
*reinterpret_cast<uint32_t*>(&ext_csd[212]) = htole32(kBlockCount);
ext_csd[MMC_EXT_CSD_PARTITION_CONFIG] = 0xa8;
ext_csd[MMC_EXT_CSD_PARTITION_SWITCH_TIME] = 0;
ext_csd[MMC_EXT_CSD_BOOT_SIZE_MULT] = 0x10;
ext_csd[MMC_EXT_CSD_GENERIC_CMD6_TIME] = 0;
});
}
void TearDown() override { dut_.StopWorkerThread(); }
protected:
static constexpr uint32_t kBlockCount = 0x100000;
static constexpr size_t kBlockOpSize = BlockOperation::OperationSize(sizeof(block_op_t));
struct OperationContext {
zx::vmo vmo;
fzl::VmoMapper mapper;
zx_status_t status;
bool completed;
};
struct CallbackContext {
CallbackContext(uint32_t exp_op) : expected_operations(exp_op) {}
uint32_t expected_operations;
sync_completion_t completion;
};
static void OperationCallback(void* ctx, zx_status_t status, block_op_t* op) {
auto* const cb_ctx = reinterpret_cast<CallbackContext*>(ctx);
block::Operation<OperationContext> block_op(op, kBlockOpSize, false);
block_op.private_storage()->completed = true;
block_op.private_storage()->status = status;
if (--(cb_ctx->expected_operations) == 0) {
sync_completion_signal(&cb_ctx->completion);
}
}
void AddDevice() {
EXPECT_OK(dut_.ProbeMmc());
EXPECT_OK(dut_.AddDevice());
user_ = GetBlockClient(USER_DATA_PARTITION);
boot1_ = GetBlockClient(BOOT_PARTITION_1);
boot2_ = GetBlockClient(BOOT_PARTITION_2);
ASSERT_TRUE(user_.is_valid());
}
void MakeBlockOp(uint32_t command, uint32_t length, uint64_t offset,
std::optional<block::Operation<OperationContext>>* out_op) {
*out_op = block::Operation<OperationContext>::Alloc(kBlockOpSize);
ASSERT_TRUE(*out_op);
*(*out_op)->operation() = block_op_t{
.rw =
{
.command = command,
.extra = 0,
.vmo = ZX_HANDLE_INVALID,
.length = length,
.offset_dev = offset,
.offset_vmo = 0,
},
};
if ((command == BLOCK_OP_READ || command == BLOCK_OP_WRITE) && length > 0) {
OperationContext* const ctx = (*out_op)->private_storage();
const size_t vmo_size =
fbl::round_up<size_t, size_t>(length * FakeSdmmcDevice::kBlockSize, PAGE_SIZE);
ASSERT_OK(ctx->mapper.CreateAndMap(vmo_size, ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, nullptr,
&ctx->vmo));
ctx->completed = false;
ctx->status = ZX_OK;
(*out_op)->operation()->rw.vmo = ctx->vmo.get();
}
}
void FillSdmmc(uint32_t length, uint64_t offset) {
for (uint32_t i = 0; i < length; i++) {
sdmmc_.Write((offset + i) * test_block_.size(), test_block_);
}
}
void FillVmo(const fzl::VmoMapper& mapper, uint32_t length) {
auto* ptr = reinterpret_cast<uint8_t*>(mapper.start());
for (uint32_t i = 0; i < length; i++, ptr += test_block_.size()) {
memcpy(ptr, test_block_.data(), test_block_.size());
}
}
void CheckSdmmc(uint32_t length, uint64_t offset) {
const std::vector<uint8_t> data =
sdmmc_.Read(offset * test_block_.size(), length * test_block_.size());
const uint8_t* ptr = data.data();
for (uint32_t i = 0; i < length; i++, ptr += test_block_.size()) {
EXPECT_BYTES_EQ(ptr, test_block_.data(), test_block_.size());
}
}
void CheckVmo(const fzl::VmoMapper& mapper, uint32_t length) {
const uint8_t* ptr = reinterpret_cast<uint8_t*>(mapper.start());
for (uint32_t i = 0; i < length; i++, ptr += test_block_.size()) {
EXPECT_BYTES_EQ(ptr, test_block_.data(), test_block_.size());
}
}
ddk::BlockImplProtocolClient GetBlockClient(size_t index) {
block_impl_protocol_t proto;
if (ddk_.GetChildProtocol(index, ZX_PROTOCOL_BLOCK_IMPL, &proto) != ZX_OK) {
return ddk::BlockImplProtocolClient();
}
return ddk::BlockImplProtocolClient(&proto);
}
FakeSdmmcDevice sdmmc_;
SdmmcBlockDevice dut_;
ddk::BlockImplProtocolClient user_;
ddk::BlockImplProtocolClient boot1_;
ddk::BlockImplProtocolClient boot2_;
Bind ddk_;
private:
static constexpr uint8_t kTestData[] = {
// clang-format off
0xd0, 0x0d, 0x7a, 0xf2, 0xbc, 0x13, 0x81, 0x07,
0x72, 0xbe, 0x33, 0x5f, 0x21, 0x4e, 0xd7, 0xba,
0x1b, 0x0c, 0x25, 0xcf, 0x2c, 0x6f, 0x46, 0x3a,
0x78, 0x22, 0xea, 0x9e, 0xa0, 0x41, 0x65, 0xf8,
// clang-format on
};
static_assert(FakeSdmmcDevice::kBlockSize % sizeof(kTestData) == 0);
std::vector<uint8_t> test_block_;
};
TEST_F(SdmmcBlockDeviceTest, BlockImplQuery) {
AddDevice();
size_t block_op_size;
block_info_t info;
user_.Query(&info, &block_op_size);
EXPECT_EQ(info.block_count, kBlockCount);
EXPECT_EQ(info.block_size, FakeSdmmcDevice::kBlockSize);
EXPECT_EQ(block_op_size, kBlockOpSize);
}
TEST_F(SdmmcBlockDeviceTest, BlockImplQueue) {
AddDevice();
std::optional<block::Operation<OperationContext>> op1;
ASSERT_NO_FATAL_FAILURES(MakeBlockOp(BLOCK_OP_WRITE, 1, 0, &op1));
std::optional<block::Operation<OperationContext>> op2;
ASSERT_NO_FATAL_FAILURES(MakeBlockOp(BLOCK_OP_WRITE, 5, 0x8000, &op2));
std::optional<block::Operation<OperationContext>> op3;
ASSERT_NO_FATAL_FAILURES(MakeBlockOp(BLOCK_OP_FLUSH, 0, 0, &op3));
std::optional<block::Operation<OperationContext>> op4;
ASSERT_NO_FATAL_FAILURES(MakeBlockOp(BLOCK_OP_READ, 1, 0x400, &op4));
std::optional<block::Operation<OperationContext>> op5;
ASSERT_NO_FATAL_FAILURES(MakeBlockOp(BLOCK_OP_READ, 10, 0x2000, &op5));
CallbackContext ctx(5);
FillVmo(op1->private_storage()->mapper, 1);
FillVmo(op2->private_storage()->mapper, 5);
FillSdmmc(1, 0x400);
FillSdmmc(10, 0x2000);
user_.Queue(op1->operation(), OperationCallback, &ctx);
user_.Queue(op2->operation(), OperationCallback, &ctx);
user_.Queue(op3->operation(), OperationCallback, &ctx);
user_.Queue(op4->operation(), OperationCallback, &ctx);
user_.Queue(op5->operation(), OperationCallback, &ctx);
EXPECT_OK(sync_completion_wait(&ctx.completion, zx::duration::infinite().get()));
EXPECT_TRUE(op1->private_storage()->completed);
EXPECT_TRUE(op2->private_storage()->completed);
EXPECT_TRUE(op3->private_storage()->completed);
EXPECT_TRUE(op4->private_storage()->completed);
EXPECT_TRUE(op5->private_storage()->completed);
EXPECT_OK(op1->private_storage()->status);
EXPECT_OK(op2->private_storage()->status);
EXPECT_OK(op3->private_storage()->status);
EXPECT_OK(op4->private_storage()->status);
EXPECT_OK(op5->private_storage()->status);
ASSERT_NO_FATAL_FAILURES(CheckSdmmc(1, 0));
ASSERT_NO_FATAL_FAILURES(CheckSdmmc(5, 0x8000));
ASSERT_NO_FATAL_FAILURES(CheckVmo(op4->private_storage()->mapper, 1));
ASSERT_NO_FATAL_FAILURES(CheckVmo(op5->private_storage()->mapper, 10));
}
TEST_F(SdmmcBlockDeviceTest, BlockImplQueueOutOfRange) {
AddDevice();
std::optional<block::Operation<OperationContext>> op1;
ASSERT_NO_FATAL_FAILURES(MakeBlockOp(BLOCK_OP_WRITE, 1, 0x100000, &op1));
std::optional<block::Operation<OperationContext>> op2;
ASSERT_NO_FATAL_FAILURES(MakeBlockOp(BLOCK_OP_READ, 10, 0x200000, &op2));
std::optional<block::Operation<OperationContext>> op3;
ASSERT_NO_FATAL_FAILURES(MakeBlockOp(BLOCK_OP_WRITE, 8, 0xffff8, &op3));
std::optional<block::Operation<OperationContext>> op4;
ASSERT_NO_FATAL_FAILURES(MakeBlockOp(BLOCK_OP_READ, 9, 0xffff8, &op4));
std::optional<block::Operation<OperationContext>> op5;
ASSERT_NO_FATAL_FAILURES(MakeBlockOp(BLOCK_OP_WRITE, 16, 0xffff8, &op5));
std::optional<block::Operation<OperationContext>> op6;
ASSERT_NO_FATAL_FAILURES(MakeBlockOp(BLOCK_OP_READ, 0, 0x80000, &op6));
std::optional<block::Operation<OperationContext>> op7;
ASSERT_NO_FATAL_FAILURES(MakeBlockOp(BLOCK_OP_WRITE, 1, 0xfffff, &op7));
CallbackContext ctx(7);
user_.Queue(op1->operation(), OperationCallback, &ctx);
user_.Queue(op2->operation(), OperationCallback, &ctx);
user_.Queue(op3->operation(), OperationCallback, &ctx);
user_.Queue(op4->operation(), OperationCallback, &ctx);
user_.Queue(op5->operation(), OperationCallback, &ctx);
user_.Queue(op6->operation(), OperationCallback, &ctx);
user_.Queue(op7->operation(), OperationCallback, &ctx);
EXPECT_OK(sync_completion_wait(&ctx.completion, zx::duration::infinite().get()));
EXPECT_TRUE(op1->private_storage()->completed);
EXPECT_TRUE(op2->private_storage()->completed);
EXPECT_TRUE(op3->private_storage()->completed);
EXPECT_TRUE(op4->private_storage()->completed);
EXPECT_TRUE(op5->private_storage()->completed);
EXPECT_TRUE(op6->private_storage()->completed);
EXPECT_TRUE(op7->private_storage()->completed);
EXPECT_NOT_OK(op1->private_storage()->status);
EXPECT_NOT_OK(op2->private_storage()->status);
EXPECT_OK(op3->private_storage()->status);
EXPECT_NOT_OK(op4->private_storage()->status);
EXPECT_NOT_OK(op5->private_storage()->status);
EXPECT_OK(op6->private_storage()->status);
EXPECT_OK(op7->private_storage()->status);
}
TEST_F(SdmmcBlockDeviceTest, MultiBlockACmd12) {
AddDevice();
sdmmc_.set_host_info({
.caps = SDMMC_HOST_CAP_AUTO_CMD12,
.max_transfer_size = BLOCK_MAX_TRANSFER_UNBOUNDED,
.max_transfer_size_non_dma = 0,
.prefs = 0,
});
EXPECT_OK(dut_.Init());
std::optional<block::Operation<OperationContext>> op1;
ASSERT_NO_FATAL_FAILURES(MakeBlockOp(BLOCK_OP_WRITE, 1, 0, &op1));
std::optional<block::Operation<OperationContext>> op2;
ASSERT_NO_FATAL_FAILURES(MakeBlockOp(BLOCK_OP_WRITE, 5, 0x8000, &op2));
std::optional<block::Operation<OperationContext>> op3;
ASSERT_NO_FATAL_FAILURES(MakeBlockOp(BLOCK_OP_FLUSH, 0, 0, &op3));
std::optional<block::Operation<OperationContext>> op4;
ASSERT_NO_FATAL_FAILURES(MakeBlockOp(BLOCK_OP_READ, 1, 0x400, &op4));
std::optional<block::Operation<OperationContext>> op5;
ASSERT_NO_FATAL_FAILURES(MakeBlockOp(BLOCK_OP_READ, 10, 0x2000, &op5));
CallbackContext ctx(5);
sdmmc_.set_command_callback(SDMMC_READ_MULTIPLE_BLOCK, [](sdmmc_req_t* req) -> void {
EXPECT_TRUE(req->cmd_flags & SDMMC_CMD_AUTO12);
});
sdmmc_.set_command_callback(SDMMC_WRITE_MULTIPLE_BLOCK, [](sdmmc_req_t* req) -> void {
EXPECT_TRUE(req->cmd_flags & SDMMC_CMD_AUTO12);
});
user_.Queue(op1->operation(), OperationCallback, &ctx);
user_.Queue(op2->operation(), OperationCallback, &ctx);
user_.Queue(op3->operation(), OperationCallback, &ctx);
user_.Queue(op4->operation(), OperationCallback, &ctx);
user_.Queue(op5->operation(), OperationCallback, &ctx);
EXPECT_OK(sync_completion_wait(&ctx.completion, zx::duration::infinite().get()));
const std::map<uint32_t, uint32_t> command_counts = sdmmc_.command_counts();
EXPECT_EQ(command_counts.find(SDMMC_STOP_TRANSMISSION), command_counts.end());
}
TEST_F(SdmmcBlockDeviceTest, MultiBlockNoACmd12) {
AddDevice();
sdmmc_.set_host_info({
.caps = 0,
.max_transfer_size = BLOCK_MAX_TRANSFER_UNBOUNDED,
.max_transfer_size_non_dma = 0,
.prefs = 0,
});
EXPECT_OK(dut_.Init());
std::optional<block::Operation<OperationContext>> op1;
ASSERT_NO_FATAL_FAILURES(MakeBlockOp(BLOCK_OP_WRITE, 1, 0, &op1));
std::optional<block::Operation<OperationContext>> op2;
ASSERT_NO_FATAL_FAILURES(MakeBlockOp(BLOCK_OP_WRITE, 5, 0x8000, &op2));
std::optional<block::Operation<OperationContext>> op3;
ASSERT_NO_FATAL_FAILURES(MakeBlockOp(BLOCK_OP_FLUSH, 0, 0, &op3));
std::optional<block::Operation<OperationContext>> op4;
ASSERT_NO_FATAL_FAILURES(MakeBlockOp(BLOCK_OP_READ, 1, 0x400, &op4));
std::optional<block::Operation<OperationContext>> op5;
ASSERT_NO_FATAL_FAILURES(MakeBlockOp(BLOCK_OP_READ, 10, 0x2000, &op5));
CallbackContext ctx(5);
sdmmc_.set_command_callback(SDMMC_READ_MULTIPLE_BLOCK, [](sdmmc_req_t* req) -> void {
EXPECT_FALSE(req->cmd_flags & SDMMC_CMD_AUTO12);
});
sdmmc_.set_command_callback(SDMMC_WRITE_MULTIPLE_BLOCK, [](sdmmc_req_t* req) -> void {
EXPECT_FALSE(req->cmd_flags & SDMMC_CMD_AUTO12);
});
user_.Queue(op1->operation(), OperationCallback, &ctx);
user_.Queue(op2->operation(), OperationCallback, &ctx);
user_.Queue(op3->operation(), OperationCallback, &ctx);
user_.Queue(op4->operation(), OperationCallback, &ctx);
user_.Queue(op5->operation(), OperationCallback, &ctx);
EXPECT_OK(sync_completion_wait(&ctx.completion, zx::duration::infinite().get()));
EXPECT_EQ(sdmmc_.command_counts().at(SDMMC_STOP_TRANSMISSION), 2);
}
TEST_F(SdmmcBlockDeviceTest, ErrorsPropagate) {
AddDevice();
std::optional<block::Operation<OperationContext>> op1;
ASSERT_NO_FATAL_FAILURES(MakeBlockOp(BLOCK_OP_WRITE, 1, FakeSdmmcDevice::kBadRegionStart, &op1));
std::optional<block::Operation<OperationContext>> op2;
ASSERT_NO_FATAL_FAILURES(
MakeBlockOp(BLOCK_OP_WRITE, 5, FakeSdmmcDevice::kBadRegionStart | 0x80, &op2));
std::optional<block::Operation<OperationContext>> op3;
ASSERT_NO_FATAL_FAILURES(MakeBlockOp(BLOCK_OP_FLUSH, 0, 0, &op3));
std::optional<block::Operation<OperationContext>> op4;
ASSERT_NO_FATAL_FAILURES(
MakeBlockOp(BLOCK_OP_READ, 1, FakeSdmmcDevice::kBadRegionStart | 0x40, &op4));
std::optional<block::Operation<OperationContext>> op5;
ASSERT_NO_FATAL_FAILURES(
MakeBlockOp(BLOCK_OP_READ, 10, FakeSdmmcDevice::kBadRegionStart | 0x20, &op5));
CallbackContext ctx(5);
user_.Queue(op1->operation(), OperationCallback, &ctx);
user_.Queue(op2->operation(), OperationCallback, &ctx);
user_.Queue(op3->operation(), OperationCallback, &ctx);
user_.Queue(op4->operation(), OperationCallback, &ctx);
user_.Queue(op5->operation(), OperationCallback, &ctx);
EXPECT_OK(sync_completion_wait(&ctx.completion, zx::duration::infinite().get()));
EXPECT_TRUE(op1->private_storage()->completed);
EXPECT_TRUE(op2->private_storage()->completed);
EXPECT_TRUE(op3->private_storage()->completed);
EXPECT_TRUE(op4->private_storage()->completed);
EXPECT_TRUE(op5->private_storage()->completed);
EXPECT_NOT_OK(op1->private_storage()->status);
EXPECT_NOT_OK(op2->private_storage()->status);
EXPECT_OK(op3->private_storage()->status);
EXPECT_NOT_OK(op4->private_storage()->status);
EXPECT_NOT_OK(op5->private_storage()->status);
}
TEST_F(SdmmcBlockDeviceTest, SendCmd12OnCommandFailure) {
AddDevice();
sdmmc_.set_host_info({
.caps = 0,
.max_transfer_size = BLOCK_MAX_TRANSFER_UNBOUNDED,
.max_transfer_size_non_dma = 0,
.prefs = 0,
});
EXPECT_OK(dut_.Init());
std::optional<block::Operation<OperationContext>> op1;
ASSERT_NO_FATAL_FAILURES(MakeBlockOp(BLOCK_OP_WRITE, 1, FakeSdmmcDevice::kBadRegionStart, &op1));
CallbackContext ctx1(1);
user_.Queue(op1->operation(), OperationCallback, &ctx1);
EXPECT_OK(sync_completion_wait(&ctx1.completion, zx::duration::infinite().get()));
EXPECT_TRUE(op1->private_storage()->completed);
EXPECT_EQ(sdmmc_.command_counts().at(SDMMC_STOP_TRANSMISSION), 1);
sdmmc_.set_host_info({
.caps = SDMMC_HOST_CAP_AUTO_CMD12,
.max_transfer_size = BLOCK_MAX_TRANSFER_UNBOUNDED,
.max_transfer_size_non_dma = 0,
.prefs = 0,
});
EXPECT_OK(dut_.Init());
std::optional<block::Operation<OperationContext>> op2;
ASSERT_NO_FATAL_FAILURES(MakeBlockOp(BLOCK_OP_WRITE, 1, FakeSdmmcDevice::kBadRegionStart, &op2));
CallbackContext ctx2(1);
user_.Queue(op2->operation(), OperationCallback, &ctx2);
EXPECT_OK(sync_completion_wait(&ctx2.completion, zx::duration::infinite().get()));
EXPECT_TRUE(op2->private_storage()->completed);
EXPECT_EQ(sdmmc_.command_counts().at(SDMMC_STOP_TRANSMISSION), 2);
}
TEST_F(SdmmcBlockDeviceTest, DdkLifecycle) {
sdmmc_.set_command_callback(MMC_SEND_EXT_CSD, [](sdmmc_req_t* req) {
uint8_t* const ext_csd = reinterpret_cast<uint8_t*>(req->virt_buffer);
ext_csd[MMC_EXT_CSD_PARTITION_SWITCH_TIME] = 0;
ext_csd[MMC_EXT_CSD_BOOT_SIZE_MULT] = 0;
ext_csd[MMC_EXT_CSD_GENERIC_CMD6_TIME] = 0;
});
AddDevice();
dut_.DdkAsyncRemove();
ASSERT_NO_FATAL_FAILURES(ddk_.Ok());
EXPECT_EQ(ddk_.total_children(), 1);
}
TEST_F(SdmmcBlockDeviceTest, DdkLifecyclePartitionsExistButNotUsed) {
sdmmc_.set_command_callback(MMC_SEND_EXT_CSD, [](sdmmc_req_t* req) {
uint8_t* const ext_csd = reinterpret_cast<uint8_t*>(req->virt_buffer);
ext_csd[MMC_EXT_CSD_PARTITION_CONFIG] = 2;
ext_csd[MMC_EXT_CSD_PARTITION_SWITCH_TIME] = 0;
ext_csd[MMC_EXT_CSD_BOOT_SIZE_MULT] = 1;
ext_csd[MMC_EXT_CSD_GENERIC_CMD6_TIME] = 0;
});
AddDevice();
dut_.DdkAsyncRemove();
ASSERT_NO_FATAL_FAILURES(ddk_.Ok());
EXPECT_EQ(ddk_.total_children(), 1);
}
TEST_F(SdmmcBlockDeviceTest, DdkLifecycleWithPartitions) {
sdmmc_.set_command_callback(MMC_SEND_EXT_CSD, [](sdmmc_req_t* req) {
uint8_t* const ext_csd = reinterpret_cast<uint8_t*>(req->virt_buffer);
ext_csd[MMC_EXT_CSD_PARTITION_CONFIG] = 0xa8;
ext_csd[MMC_EXT_CSD_PARTITION_SWITCH_TIME] = 0;
ext_csd[MMC_EXT_CSD_BOOT_SIZE_MULT] = 1;
ext_csd[MMC_EXT_CSD_GENERIC_CMD6_TIME] = 0;
});
AddDevice();
dut_.DdkAsyncRemove();
ASSERT_NO_FATAL_FAILURES(ddk_.Ok());
EXPECT_EQ(ddk_.total_children(), 3);
}
TEST_F(SdmmcBlockDeviceTest, CompleteTransactions) {
std::optional<block::Operation<OperationContext>> op1;
ASSERT_NO_FATAL_FAILURES(MakeBlockOp(BLOCK_OP_WRITE, 1, 0, &op1));
std::optional<block::Operation<OperationContext>> op2;
ASSERT_NO_FATAL_FAILURES(MakeBlockOp(BLOCK_OP_WRITE, 5, 0x8000, &op2));
std::optional<block::Operation<OperationContext>> op3;
ASSERT_NO_FATAL_FAILURES(MakeBlockOp(BLOCK_OP_FLUSH, 0, 0, &op3));
std::optional<block::Operation<OperationContext>> op4;
ASSERT_NO_FATAL_FAILURES(MakeBlockOp(BLOCK_OP_READ, 1, 0x400, &op4));
std::optional<block::Operation<OperationContext>> op5;
ASSERT_NO_FATAL_FAILURES(MakeBlockOp(BLOCK_OP_READ, 10, 0x2000, &op5));
CallbackContext ctx(5);
{
SdmmcBlockDevice dut(fake_ddk::kFakeParent, SdmmcDevice(sdmmc_.GetClient()));
EXPECT_OK(dut.AddDevice());
fbl::AutoCall stop_threads([&]() { dut.StopWorkerThread(); });
ddk::BlockImplProtocolClient user = GetBlockClient(USER_DATA_PARTITION);
ASSERT_TRUE(user.is_valid());
user.Queue(op1->operation(), OperationCallback, &ctx);
user.Queue(op2->operation(), OperationCallback, &ctx);
user.Queue(op3->operation(), OperationCallback, &ctx);
user.Queue(op4->operation(), OperationCallback, &ctx);
user.Queue(op5->operation(), OperationCallback, &ctx);
}
EXPECT_OK(sync_completion_wait(&ctx.completion, zx::duration::infinite().get()));
EXPECT_TRUE(op1->private_storage()->completed);
EXPECT_TRUE(op2->private_storage()->completed);
EXPECT_TRUE(op3->private_storage()->completed);
EXPECT_TRUE(op4->private_storage()->completed);
EXPECT_TRUE(op5->private_storage()->completed);
}
TEST_F(SdmmcBlockDeviceTest, ProbeMmcSendStatusRetry) {
sdmmc_.set_command_callback(MMC_SEND_EXT_CSD, [](sdmmc_req_t* req) {
uint8_t* const ext_csd = reinterpret_cast<uint8_t*>(req->virt_buffer);
ext_csd[MMC_EXT_CSD_DEVICE_TYPE] = 1 << 4;
ext_csd[MMC_EXT_CSD_GENERIC_CMD6_TIME] = 1;
});
sdmmc_.set_command_callback(SDMMC_SEND_STATUS, [](sdmmc_req_t* req) {
// Fail twice before succeeding.
static uint32_t call_count = 0;
if (++call_count >= 3) {
req->status = ZX_OK;
call_count = 0;
} else {
req->status = ZX_ERR_IO_DATA_INTEGRITY;
}
});
SdmmcBlockDevice dut(nullptr, SdmmcDevice(sdmmc_.GetClient()));
EXPECT_OK(dut.ProbeMmc());
}
TEST_F(SdmmcBlockDeviceTest, ProbeMmcSendStatusFail) {
sdmmc_.set_command_callback(MMC_SEND_EXT_CSD, [](sdmmc_req_t* req) {
uint8_t* const ext_csd = reinterpret_cast<uint8_t*>(req->virt_buffer);
ext_csd[MMC_EXT_CSD_DEVICE_TYPE] = 1 << 4;
ext_csd[MMC_EXT_CSD_GENERIC_CMD6_TIME] = 1;
});
sdmmc_.set_command_callback(SDMMC_SEND_STATUS,
[](sdmmc_req_t* req) { req->status = ZX_ERR_IO_DATA_INTEGRITY; });
SdmmcBlockDevice dut(nullptr, SdmmcDevice(sdmmc_.GetClient()));
EXPECT_NOT_OK(dut.ProbeMmc());
}
TEST_F(SdmmcBlockDeviceTest, QueryBootPartitions) {
AddDevice();
ASSERT_TRUE(boot1_.is_valid());
ASSERT_TRUE(boot2_.is_valid());
size_t boot1_op_size, boot2_op_size;
block_info_t boot1_info, boot2_info;
boot1_.Query(&boot1_info, &boot1_op_size);
boot2_.Query(&boot2_info, &boot2_op_size);
EXPECT_EQ(boot1_info.block_count, (0x10 * 128'000) / FakeSdmmcDevice::kBlockSize);
EXPECT_EQ(boot2_info.block_count, (0x10 * 128'000) / FakeSdmmcDevice::kBlockSize);
EXPECT_EQ(boot1_info.block_size, FakeSdmmcDevice::kBlockSize);
EXPECT_EQ(boot2_info.block_size, FakeSdmmcDevice::kBlockSize);
EXPECT_EQ(boot1_op_size, kBlockOpSize);
EXPECT_EQ(boot2_op_size, kBlockOpSize);
}
TEST_F(SdmmcBlockDeviceTest, AccessBootPartitions) {
AddDevice();
ASSERT_TRUE(boot1_.is_valid());
ASSERT_TRUE(boot2_.is_valid());
std::optional<block::Operation<OperationContext>> op1;
ASSERT_NO_FATAL_FAILURES(MakeBlockOp(BLOCK_OP_WRITE, 1, 0, &op1));
std::optional<block::Operation<OperationContext>> op2;
ASSERT_NO_FATAL_FAILURES(MakeBlockOp(BLOCK_OP_READ, 5, 10, &op2));
std::optional<block::Operation<OperationContext>> op3;
ASSERT_NO_FATAL_FAILURES(MakeBlockOp(BLOCK_OP_WRITE, 10, 500, &op3));
FillVmo(op1->private_storage()->mapper, 1);
FillSdmmc(5, 10);
FillVmo(op3->private_storage()->mapper, 10);
CallbackContext ctx(1);
sdmmc_.set_command_callback(MMC_SWITCH, [](sdmmc_req_t* req) {
const uint32_t index = (req->arg >> 16) & 0xff;
const uint32_t value = (req->arg >> 8) & 0xff;
EXPECT_EQ(index, MMC_EXT_CSD_PARTITION_CONFIG);
EXPECT_EQ(value, 0xa8 | BOOT_PARTITION_1);
});
boot1_.Queue(op1->operation(), OperationCallback, &ctx);
EXPECT_OK(sync_completion_wait(&ctx.completion, zx::duration::infinite().get()));
ctx.expected_operations = 1;
sync_completion_reset(&ctx.completion);
sdmmc_.set_command_callback(MMC_SWITCH, [](sdmmc_req_t* req) {
const uint32_t index = (req->arg >> 16) & 0xff;
const uint32_t value = (req->arg >> 8) & 0xff;
EXPECT_EQ(index, MMC_EXT_CSD_PARTITION_CONFIG);
EXPECT_EQ(value, 0xa8 | BOOT_PARTITION_2);
});
boot2_.Queue(op2->operation(), OperationCallback, &ctx);
EXPECT_OK(sync_completion_wait(&ctx.completion, zx::duration::infinite().get()));
ctx.expected_operations = 1;
sync_completion_reset(&ctx.completion);
sdmmc_.set_command_callback(MMC_SWITCH, [](sdmmc_req_t* req) {
const uint32_t index = (req->arg >> 16) & 0xff;
const uint32_t value = (req->arg >> 8) & 0xff;
EXPECT_EQ(index, MMC_EXT_CSD_PARTITION_CONFIG);
EXPECT_EQ(value, 0xa8 | USER_DATA_PARTITION);
});
user_.Queue(op3->operation(), OperationCallback, &ctx);
EXPECT_OK(sync_completion_wait(&ctx.completion, zx::duration::infinite().get()));
EXPECT_TRUE(op1->private_storage()->completed);
EXPECT_TRUE(op2->private_storage()->completed);
EXPECT_TRUE(op3->private_storage()->completed);
EXPECT_OK(op1->private_storage()->status);
EXPECT_OK(op2->private_storage()->status);
EXPECT_OK(op3->private_storage()->status);
ASSERT_NO_FATAL_FAILURES(CheckSdmmc(1, 0));
ASSERT_NO_FATAL_FAILURES(CheckVmo(op2->private_storage()->mapper, 5));
ASSERT_NO_FATAL_FAILURES(CheckSdmmc(10, 500));
}
TEST_F(SdmmcBlockDeviceTest, BootPartitionRepeatedAccess) {
AddDevice();
ASSERT_TRUE(boot2_.is_valid());
std::optional<block::Operation<OperationContext>> op1;
ASSERT_NO_FATAL_FAILURES(MakeBlockOp(BLOCK_OP_READ, 1, 0, &op1));
std::optional<block::Operation<OperationContext>> op2;
ASSERT_NO_FATAL_FAILURES(MakeBlockOp(BLOCK_OP_WRITE, 5, 10, &op2));
std::optional<block::Operation<OperationContext>> op3;
ASSERT_NO_FATAL_FAILURES(MakeBlockOp(BLOCK_OP_WRITE, 2, 5, &op3));
FillSdmmc(1, 0);
FillVmo(op2->private_storage()->mapper, 5);
FillVmo(op3->private_storage()->mapper, 2);
CallbackContext ctx(1);
sdmmc_.set_command_callback(MMC_SWITCH, [](sdmmc_req_t* req) {
const uint32_t index = (req->arg >> 16) & 0xff;
const uint32_t value = (req->arg >> 8) & 0xff;
EXPECT_EQ(index, MMC_EXT_CSD_PARTITION_CONFIG);
EXPECT_EQ(value, 0xa8 | BOOT_PARTITION_2);
});
boot2_.Queue(op1->operation(), OperationCallback, &ctx);
EXPECT_OK(sync_completion_wait(&ctx.completion, zx::duration::infinite().get()));
ctx.expected_operations = 2;
sync_completion_reset(&ctx.completion);
// Repeated accesses to one partition should not generate more than one MMC_SWITCH command.
sdmmc_.set_command_callback(MMC_SWITCH, [](sdmmc_req_t* req) { FAIL(); });
boot2_.Queue(op2->operation(), OperationCallback, &ctx);
boot2_.Queue(op3->operation(), OperationCallback, &ctx);
EXPECT_OK(sync_completion_wait(&ctx.completion, zx::duration::infinite().get()));
EXPECT_TRUE(op1->private_storage()->completed);
EXPECT_TRUE(op2->private_storage()->completed);
EXPECT_TRUE(op3->private_storage()->completed);
EXPECT_OK(op1->private_storage()->status);
EXPECT_OK(op2->private_storage()->status);
EXPECT_OK(op3->private_storage()->status);
ASSERT_NO_FATAL_FAILURES(CheckVmo(op1->private_storage()->mapper, 1));
ASSERT_NO_FATAL_FAILURES(CheckSdmmc(5, 10));
ASSERT_NO_FATAL_FAILURES(CheckSdmmc(2, 5));
}
TEST_F(SdmmcBlockDeviceTest, AccessBootPartitionOutOfRange) {
AddDevice();
ASSERT_TRUE(boot1_.is_valid());
std::optional<block::Operation<OperationContext>> op1;
ASSERT_NO_FATAL_FAILURES(MakeBlockOp(BLOCK_OP_WRITE, 1, 4000, &op1));
std::optional<block::Operation<OperationContext>> op2;
ASSERT_NO_FATAL_FAILURES(MakeBlockOp(BLOCK_OP_WRITE, 8, 3992, &op2));
std::optional<block::Operation<OperationContext>> op3;
ASSERT_NO_FATAL_FAILURES(MakeBlockOp(BLOCK_OP_READ, 9, 3992, &op3));
std::optional<block::Operation<OperationContext>> op4;
ASSERT_NO_FATAL_FAILURES(MakeBlockOp(BLOCK_OP_WRITE, 16, 3992, &op4));
std::optional<block::Operation<OperationContext>> op5;
ASSERT_NO_FATAL_FAILURES(MakeBlockOp(BLOCK_OP_READ, 0, 2000, &op5));
std::optional<block::Operation<OperationContext>> op6;
ASSERT_NO_FATAL_FAILURES(MakeBlockOp(BLOCK_OP_WRITE, 1, 3999, &op6));
CallbackContext ctx(6);
boot1_.Queue(op1->operation(), OperationCallback, &ctx);
boot1_.Queue(op2->operation(), OperationCallback, &ctx);
boot1_.Queue(op3->operation(), OperationCallback, &ctx);
boot1_.Queue(op4->operation(), OperationCallback, &ctx);
boot1_.Queue(op5->operation(), OperationCallback, &ctx);
boot1_.Queue(op6->operation(), OperationCallback, &ctx);
EXPECT_OK(sync_completion_wait(&ctx.completion, zx::duration::infinite().get()));
EXPECT_TRUE(op1->private_storage()->completed);
EXPECT_TRUE(op2->private_storage()->completed);
EXPECT_TRUE(op3->private_storage()->completed);
EXPECT_TRUE(op4->private_storage()->completed);
EXPECT_TRUE(op5->private_storage()->completed);
EXPECT_TRUE(op6->private_storage()->completed);
EXPECT_NOT_OK(op1->private_storage()->status);
EXPECT_OK(op2->private_storage()->status);
EXPECT_NOT_OK(op3->private_storage()->status);
EXPECT_NOT_OK(op4->private_storage()->status);
EXPECT_OK(op5->private_storage()->status);
EXPECT_OK(op6->private_storage()->status);
}
} // namespace sdmmc