blob: 8bcd01d8c43a8333592eca97db94b0b97fa46912 [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 "sdhci.h"
#include <fuchsia/hardware/sdhci/cpp/banjo-mock.h>
#include <lib/fake-bti/bti.h>
#include <lib/fake_ddk/fake_ddk.h>
#include <lib/mmio-ptr/fake.h>
#include <lib/sync/completion.h>
#include <atomic>
#include <memory>
#include <optional>
#include <vector>
#include <zxtest/zxtest.h>
namespace {
constexpr zx_paddr_t kPageMask = PAGE_SIZE - 1;
} // namespace
namespace sdhci {
class TestSdhci : public Sdhci {
public:
TestSdhci(zx_device_t* parent, ddk::MmioBuffer regs_mmio_buffer, zx::bti bti,
const ddk::SdhciProtocolClient sdhci, uint64_t quirks, uint64_t dma_boundary_alignment)
: Sdhci(parent, std::move(regs_mmio_buffer), std::move(bti), {}, sdhci, quirks,
dma_boundary_alignment) {}
zx_status_t SdmmcRequest(sdmmc_req_t* req) {
blocks_remaining_ = req->blockcount;
current_block_ = 0;
return Sdhci::SdmmcRequest(req);
}
void DdkUnbind(ddk::UnbindTxn txn) {
run_thread_ = false;
Sdhci::DdkUnbind(std::move(txn));
}
uint8_t reset_mask() {
uint8_t ret = reset_mask_;
reset_mask_ = 0;
return ret;
}
void* iobuf_virt() const { return iobuf_.virt(); }
void TriggerCardInterrupt() { card_interrupt_ = true; }
void set_dma_paddrs(const std::vector<zx_paddr_t>& paddrs) { dma_paddrs_ = paddrs; }
protected:
zx_status_t WaitForReset(const SoftwareReset mask) override {
reset_mask_ = mask.reg_value();
return ZX_OK;
}
zx_status_t WaitForInterrupt() override {
auto status = InterruptStatus::Get().FromValue(0).WriteTo(&regs_mmio_buffer_);
while (run_thread_) {
switch (GetRequestStatus()) {
case RequestStatus::COMMAND:
status.set_command_complete(1).WriteTo(&regs_mmio_buffer_);
return ZX_OK;
case RequestStatus::TRANSFER_DATA_DMA:
status.set_transfer_complete(1).WriteTo(&regs_mmio_buffer_);
return ZX_OK;
case RequestStatus::READ_DATA_PIO:
if (++current_block_ == blocks_remaining_) {
status.set_buffer_read_ready(1).set_transfer_complete(1).WriteTo(&regs_mmio_buffer_);
} else {
status.set_buffer_read_ready(1).WriteTo(&regs_mmio_buffer_);
}
return ZX_OK;
case RequestStatus::WRITE_DATA_PIO:
if (++current_block_ == blocks_remaining_) {
status.set_buffer_write_ready(1).set_transfer_complete(1).WriteTo(&regs_mmio_buffer_);
} else {
status.set_buffer_write_ready(1).WriteTo(&regs_mmio_buffer_);
}
return ZX_OK;
case RequestStatus::BUSY_RESPONSE:
status.set_transfer_complete(1).WriteTo(&regs_mmio_buffer_);
return ZX_OK;
default:
break;
}
if (card_interrupt_.exchange(false) &&
InterruptStatusEnable::Get().ReadFrom(&regs_mmio_buffer_).card_interrupt() == 1) {
status.set_card_interrupt(1).WriteTo(&regs_mmio_buffer_);
return ZX_OK;
}
}
return ZX_ERR_CANCELED;
}
zx_status_t PinRequestPages(sdmmc_req_t* req, zx_paddr_t* phys, size_t pagecount) override {
if (dma_paddrs_.size() == 0) {
return Sdhci::PinRequestPages(req, phys, pagecount);
}
if (dma_paddrs_.size() < pagecount) {
return ZX_ERR_INVALID_ARGS;
}
req->pmt = ZX_HANDLE_INVALID;
memcpy(phys, dma_paddrs_.data(), pagecount * sizeof(phys[0]));
return ZX_OK;
}
private:
uint8_t reset_mask_ = 0;
std::atomic<bool> run_thread_ = true;
std::atomic<uint16_t> blocks_remaining_ = 0;
std::atomic<uint16_t> current_block_ = 0;
std::atomic<bool> card_interrupt_ = false;
std::vector<zx_paddr_t> dma_paddrs_;
};
class SdhciTest : public zxtest::Test {
public:
SdhciTest()
: registers_(new uint8_t[kRegisterSetSize]),
mmio_(
{
.vaddr = FakeMmioPtr(registers_.get()),
.offset = 0,
.size = kRegisterSetSize,
.vmo = ZX_HANDLE_INVALID,
},
0) {}
void SetUp() override {
ASSERT_TRUE(registers_);
ASSERT_OK(fake_bti_create(fake_bti_.reset_and_get_address()));
}
protected:
void CreateDut(uint64_t quirks = 0, uint64_t dma_boundary_alignment = 0) {
memset(registers_.get(), 0, kRegisterSetSize);
dut_.emplace(fake_ddk::kFakeParent, ddk::MmioView(mmio_), std::move(fake_bti_),
ddk::SdhciProtocolClient(mock_sdhci_.GetProto()), quirks, dma_boundary_alignment);
HostControllerVersion::Get()
.FromValue(0)
.set_specification_version(HostControllerVersion::kSpecificationVersion300)
.WriteTo(&mmio_);
ClockControl::Get().FromValue(0).set_internal_clock_stable(1).WriteTo(&mmio_);
}
std::unique_ptr<uint8_t[]> registers_;
ddk::MockSdhci mock_sdhci_;
zx::interrupt irq_;
std::optional<TestSdhci> dut_;
ddk::MmioView mmio_;
zx::bti fake_bti_;
};
TEST_F(SdhciTest, DdkLifecycle) {
ASSERT_NO_FATAL_FAILURES(CreateDut());
mock_sdhci_.ExpectGetBaseClock(100'000'000);
EXPECT_OK(dut_->Init());
fake_ddk::Bind bind;
dut_->DdkAdd("sdhci");
dut_->DdkUnbind(ddk::UnbindTxn(fake_ddk::kFakeDevice));
EXPECT_TRUE(bind.Ok());
}
TEST_F(SdhciTest, BaseClockZero) {
ASSERT_NO_FATAL_FAILURES(CreateDut());
mock_sdhci_.ExpectGetBaseClock(0);
EXPECT_NOT_OK(dut_->Init());
}
TEST_F(SdhciTest, BaseClockFromDriver) {
ASSERT_NO_FATAL_FAILURES(CreateDut());
mock_sdhci_.ExpectGetBaseClock(0xabcdef);
EXPECT_OK(dut_->Init());
dut_->DdkUnbind(ddk::UnbindTxn(fake_ddk::kFakeDevice));
EXPECT_EQ(dut_->base_clock(), 0xabcdef);
}
TEST_F(SdhciTest, BaseClockFromHardware) {
ASSERT_NO_FATAL_FAILURES(CreateDut());
Capabilities0::Get().FromValue(0).set_base_clock_frequency(104).WriteTo(&mmio_);
EXPECT_OK(dut_->Init());
dut_->DdkUnbind(ddk::UnbindTxn(fake_ddk::kFakeDevice));
EXPECT_EQ(dut_->base_clock(), 104'000'000);
}
TEST_F(SdhciTest, HostInfo) {
ASSERT_NO_FATAL_FAILURES(CreateDut());
Capabilities1::Get()
.FromValue(0)
.set_sdr50_support(1)
.set_sdr104_support(1)
.set_use_tuning_for_sdr50(1)
.WriteTo(&mmio_);
Capabilities0::Get()
.FromValue(0)
.set_base_clock_frequency(1)
.set_bus_width_8_support(1)
.set_voltage_3v3_support(1)
.set_v3_64_bit_system_address_support(1)
.WriteTo(&mmio_);
EXPECT_OK(dut_->Init());
dut_->DdkUnbind(ddk::UnbindTxn(fake_ddk::kFakeDevice));
sdmmc_host_info_t host_info = {};
EXPECT_OK(dut_->SdmmcHostInfo(&host_info));
EXPECT_EQ(host_info.caps, SDMMC_HOST_CAP_BUS_WIDTH_8 | SDMMC_HOST_CAP_VOLTAGE_330 |
SDMMC_HOST_CAP_AUTO_CMD12 | SDMMC_HOST_CAP_SDR50 |
SDMMC_HOST_CAP_SDR104);
EXPECT_EQ(host_info.prefs, 0);
}
TEST_F(SdhciTest, HostInfoNoDma) {
ASSERT_NO_FATAL_FAILURES(CreateDut(SDHCI_QUIRK_NO_DMA));
Capabilities1::Get().FromValue(0).set_sdr50_support(1).set_ddr50_support(1).WriteTo(&mmio_);
Capabilities0::Get()
.FromValue(0)
.set_base_clock_frequency(1)
.set_bus_width_8_support(1)
.set_voltage_3v3_support(1)
.set_v3_64_bit_system_address_support(1)
.WriteTo(&mmio_);
EXPECT_OK(dut_->Init());
dut_->DdkUnbind(ddk::UnbindTxn(fake_ddk::kFakeDevice));
sdmmc_host_info_t host_info = {};
EXPECT_OK(dut_->SdmmcHostInfo(&host_info));
EXPECT_EQ(host_info.caps, SDMMC_HOST_CAP_BUS_WIDTH_8 | SDMMC_HOST_CAP_VOLTAGE_330 |
SDMMC_HOST_CAP_AUTO_CMD12 | SDMMC_HOST_CAP_DDR50 |
SDMMC_HOST_CAP_SDR50 | SDMMC_HOST_CAP_NO_TUNING_SDR50);
EXPECT_EQ(host_info.prefs, 0);
}
TEST_F(SdhciTest, HostInfoNoTuning) {
ASSERT_NO_FATAL_FAILURES(CreateDut(SDHCI_QUIRK_NON_STANDARD_TUNING));
Capabilities1::Get().FromValue(0).WriteTo(&mmio_);
Capabilities0::Get().FromValue(0).set_base_clock_frequency(1).WriteTo(&mmio_);
EXPECT_OK(dut_->Init());
dut_->DdkUnbind(ddk::UnbindTxn(fake_ddk::kFakeDevice));
sdmmc_host_info_t host_info = {};
EXPECT_OK(dut_->SdmmcHostInfo(&host_info));
EXPECT_EQ(host_info.caps, SDMMC_HOST_CAP_AUTO_CMD12 | SDMMC_HOST_CAP_NO_TUNING_SDR50);
EXPECT_EQ(host_info.prefs, SDMMC_HOST_PREFS_DISABLE_HS400 | SDMMC_HOST_PREFS_DISABLE_HS200);
}
TEST_F(SdhciTest, SetSignalVoltage) {
ASSERT_NO_FATAL_FAILURES(CreateDut());
mock_sdhci_.ExpectGetBaseClock(100'000'000);
Capabilities0::Get().FromValue(0).set_voltage_3v3_support(1).set_voltage_1v8_support(1).WriteTo(
&mmio_);
EXPECT_OK(dut_->Init());
dut_->DdkUnbind(ddk::UnbindTxn(fake_ddk::kFakeDevice));
PresentState::Get().FromValue(0).set_dat_3_0(0b0001).WriteTo(&mmio_);
PowerControl::Get()
.FromValue(0)
.set_sd_bus_voltage_vdd1(PowerControl::kBusVoltage1V8)
.set_sd_bus_power_vdd1(1)
.WriteTo(&mmio_);
EXPECT_OK(dut_->SdmmcSetSignalVoltage(SDMMC_VOLTAGE_V180));
EXPECT_TRUE(HostControl2::Get().ReadFrom(&mmio_).voltage_1v8_signalling_enable());
PowerControl::Get()
.FromValue(0)
.set_sd_bus_voltage_vdd1(PowerControl::kBusVoltage3V3)
.set_sd_bus_power_vdd1(1)
.WriteTo(&mmio_);
EXPECT_OK(dut_->SdmmcSetSignalVoltage(SDMMC_VOLTAGE_V330));
EXPECT_FALSE(HostControl2::Get().ReadFrom(&mmio_).voltage_1v8_signalling_enable());
}
TEST_F(SdhciTest, SetSignalVoltageUnsupported) {
ASSERT_NO_FATAL_FAILURES(CreateDut());
EXPECT_NOT_OK(dut_->SdmmcSetSignalVoltage(SDMMC_VOLTAGE_V330));
}
TEST_F(SdhciTest, SetBusWidth) {
ASSERT_NO_FATAL_FAILURES(CreateDut());
mock_sdhci_.ExpectGetBaseClock(100'000'000);
Capabilities0::Get().FromValue(0).set_bus_width_8_support(1).WriteTo(&mmio_);
EXPECT_OK(dut_->Init());
dut_->DdkUnbind(ddk::UnbindTxn(fake_ddk::kFakeDevice));
auto ctrl1 = HostControl1::Get().FromValue(0);
EXPECT_OK(dut_->SdmmcSetBusWidth(SDMMC_BUS_WIDTH_EIGHT));
EXPECT_TRUE(ctrl1.ReadFrom(&mmio_).extended_data_transfer_width());
EXPECT_FALSE(ctrl1.ReadFrom(&mmio_).data_transfer_width_4bit());
EXPECT_OK(dut_->SdmmcSetBusWidth(SDMMC_BUS_WIDTH_ONE));
EXPECT_FALSE(ctrl1.ReadFrom(&mmio_).extended_data_transfer_width());
EXPECT_FALSE(ctrl1.ReadFrom(&mmio_).data_transfer_width_4bit());
EXPECT_OK(dut_->SdmmcSetBusWidth(SDMMC_BUS_WIDTH_FOUR));
EXPECT_FALSE(ctrl1.ReadFrom(&mmio_).extended_data_transfer_width());
EXPECT_TRUE(ctrl1.ReadFrom(&mmio_).data_transfer_width_4bit());
}
TEST_F(SdhciTest, SetBusWidthNotSupported) {
ASSERT_NO_FATAL_FAILURES(CreateDut());
EXPECT_NOT_OK(dut_->SdmmcSetBusWidth(SDMMC_BUS_WIDTH_EIGHT));
}
TEST_F(SdhciTest, SetBusFreq) {
ASSERT_NO_FATAL_FAILURES(CreateDut());
mock_sdhci_.ExpectGetBaseClock(100'000'000);
EXPECT_OK(dut_->Init());
dut_->DdkUnbind(ddk::UnbindTxn(fake_ddk::kFakeDevice));
auto clock = ClockControl::Get().FromValue(0);
EXPECT_OK(dut_->SdmmcSetBusFreq(12'500'000));
EXPECT_EQ(clock.ReadFrom(&mmio_).frequency_select(), 4);
EXPECT_TRUE(clock.sd_clock_enable());
EXPECT_OK(dut_->SdmmcSetBusFreq(65'190));
EXPECT_EQ(clock.ReadFrom(&mmio_).frequency_select(), 767);
EXPECT_TRUE(clock.sd_clock_enable());
EXPECT_OK(dut_->SdmmcSetBusFreq(100'000'000));
EXPECT_EQ(clock.ReadFrom(&mmio_).frequency_select(), 0);
EXPECT_TRUE(clock.sd_clock_enable());
EXPECT_OK(dut_->SdmmcSetBusFreq(26'000'000));
EXPECT_EQ(clock.ReadFrom(&mmio_).frequency_select(), 2);
EXPECT_TRUE(clock.sd_clock_enable());
EXPECT_OK(dut_->SdmmcSetBusFreq(0));
EXPECT_FALSE(clock.ReadFrom(&mmio_).sd_clock_enable());
}
TEST_F(SdhciTest, SetBusFreqTimeout) {
ASSERT_NO_FATAL_FAILURES(CreateDut());
mock_sdhci_.ExpectGetBaseClock(100'000'000);
EXPECT_OK(dut_->Init());
dut_->DdkUnbind(ddk::UnbindTxn(fake_ddk::kFakeDevice));
ClockControl::Get().FromValue(0).set_internal_clock_stable(1).WriteTo(&mmio_);
EXPECT_OK(dut_->SdmmcSetBusFreq(12'500'000));
ClockControl::Get().FromValue(0).WriteTo(&mmio_);
EXPECT_NOT_OK(dut_->SdmmcSetBusFreq(12'500'000));
}
TEST_F(SdhciTest, SetBusFreqInternalClockEnable) {
ASSERT_NO_FATAL_FAILURES(CreateDut());
mock_sdhci_.ExpectGetBaseClock(100'000'000);
EXPECT_OK(dut_->Init());
dut_->DdkUnbind(ddk::UnbindTxn(fake_ddk::kFakeDevice));
ClockControl::Get()
.FromValue(0)
.set_internal_clock_stable(1)
.set_internal_clock_enable(0)
.WriteTo(&mmio_);
EXPECT_OK(dut_->SdmmcSetBusFreq(12'500'000));
EXPECT_TRUE(ClockControl::Get().ReadFrom(&mmio_).internal_clock_enable());
}
TEST_F(SdhciTest, SetTiming) {
ASSERT_NO_FATAL_FAILURES(CreateDut());
EXPECT_OK(dut_->SdmmcSetTiming(SDMMC_TIMING_HS));
EXPECT_TRUE(HostControl1::Get().ReadFrom(&mmio_).high_speed_enable());
EXPECT_EQ(HostControl2::Get().ReadFrom(&mmio_).uhs_mode_select(), HostControl2::kUhsModeSdr25);
EXPECT_OK(dut_->SdmmcSetTiming(SDMMC_TIMING_LEGACY));
EXPECT_FALSE(HostControl1::Get().ReadFrom(&mmio_).high_speed_enable());
EXPECT_EQ(HostControl2::Get().ReadFrom(&mmio_).uhs_mode_select(), HostControl2::kUhsModeSdr12);
EXPECT_OK(dut_->SdmmcSetTiming(SDMMC_TIMING_HSDDR));
EXPECT_TRUE(HostControl1::Get().ReadFrom(&mmio_).high_speed_enable());
EXPECT_EQ(HostControl2::Get().ReadFrom(&mmio_).uhs_mode_select(), HostControl2::kUhsModeDdr50);
EXPECT_OK(dut_->SdmmcSetTiming(SDMMC_TIMING_SDR25));
EXPECT_TRUE(HostControl1::Get().ReadFrom(&mmio_).high_speed_enable());
EXPECT_EQ(HostControl2::Get().ReadFrom(&mmio_).uhs_mode_select(), HostControl2::kUhsModeSdr25);
EXPECT_OK(dut_->SdmmcSetTiming(SDMMC_TIMING_SDR12));
EXPECT_TRUE(HostControl1::Get().ReadFrom(&mmio_).high_speed_enable());
EXPECT_EQ(HostControl2::Get().ReadFrom(&mmio_).uhs_mode_select(), HostControl2::kUhsModeSdr12);
EXPECT_OK(dut_->SdmmcSetTiming(SDMMC_TIMING_HS400));
EXPECT_TRUE(HostControl1::Get().ReadFrom(&mmio_).high_speed_enable());
EXPECT_EQ(HostControl2::Get().ReadFrom(&mmio_).uhs_mode_select(), HostControl2::kUhsModeHs400);
}
TEST_F(SdhciTest, HwReset) {
ASSERT_NO_FATAL_FAILURES(CreateDut());
mock_sdhci_.ExpectHwReset();
dut_->SdmmcHwReset();
ASSERT_NO_FATAL_FAILURES(mock_sdhci_.VerifyAndClear());
}
TEST_F(SdhciTest, RequestCommandOnly) {
ASSERT_NO_FATAL_FAILURES(CreateDut());
mock_sdhci_.ExpectGetBaseClock(100'000'000);
EXPECT_OK(dut_->Init());
sdmmc_req_t request = {
.cmd_idx = SDMMC_SEND_STATUS,
.cmd_flags = SDMMC_SEND_STATUS_FLAGS,
.arg = 0x7b7d9fbd,
.blockcount = 0,
.blocksize = 0,
.use_dma = false,
.dma_vmo = ZX_HANDLE_INVALID,
.virt_buffer = nullptr,
.virt_size = 0,
.buf_offset = 0,
.pmt = ZX_HANDLE_INVALID,
.probe_tuning_cmd = 0,
.response = {},
.status = ZX_ERR_BAD_STATE,
};
Response::Get(0).FromValue(0xf3bbf2c0).WriteTo(&mmio_);
EXPECT_OK(dut_->SdmmcRequest(&request));
auto command = Command::Get().FromValue(0);
EXPECT_EQ(Argument::Get().ReadFrom(&mmio_).reg_value(), 0x7b7d9fbd);
EXPECT_EQ(command.ReadFrom(&mmio_).command_index(), SDMMC_SEND_STATUS);
EXPECT_EQ(command.command_type(), Command::kCommandTypeNormal);
EXPECT_FALSE(command.data_present());
EXPECT_TRUE(command.command_index_check());
EXPECT_TRUE(command.command_crc_check());
EXPECT_EQ(command.response_type(), Command::kResponseType48Bits);
EXPECT_OK(request.status);
EXPECT_EQ(request.response[0], 0xf3bbf2c0);
request = {
.cmd_idx = SDMMC_SEND_CSD,
.cmd_flags = SDMMC_SEND_CSD_FLAGS,
.arg = 0x9c1dc1ed,
.blockcount = 0,
.blocksize = 0,
.use_dma = false,
.dma_vmo = ZX_HANDLE_INVALID,
.virt_buffer = nullptr,
.virt_size = 0,
.buf_offset = 0,
.pmt = ZX_HANDLE_INVALID,
.probe_tuning_cmd = 0,
.response = {},
.status = ZX_ERR_BAD_STATE,
};
Response::Get(0).FromValue(0x9f93b17d).WriteTo(&mmio_);
Response::Get(1).FromValue(0x89aaba9e).WriteTo(&mmio_);
Response::Get(2).FromValue(0xc14b059e).WriteTo(&mmio_);
Response::Get(3).FromValue(0x7329a9e3).WriteTo(&mmio_);
EXPECT_OK(dut_->SdmmcRequest(&request));
EXPECT_EQ(Argument::Get().ReadFrom(&mmio_).reg_value(), 0x9c1dc1ed);
EXPECT_EQ(command.ReadFrom(&mmio_).command_index(), SDMMC_SEND_CSD);
EXPECT_EQ(command.command_type(), Command::kCommandTypeNormal);
EXPECT_FALSE(command.data_present());
EXPECT_TRUE(command.command_crc_check());
EXPECT_EQ(command.response_type(), Command::kResponseType136Bits);
EXPECT_OK(request.status);
EXPECT_EQ(request.response[0], 0x9f93b17d);
EXPECT_EQ(request.response[1], 0x89aaba9e);
EXPECT_EQ(request.response[2], 0xc14b059e);
EXPECT_EQ(request.response[3], 0x7329a9e3);
dut_->DdkUnbind(ddk::UnbindTxn(fake_ddk::kFakeDevice));
}
TEST_F(SdhciTest, RequestWithData) {
ASSERT_NO_FATAL_FAILURES(CreateDut());
mock_sdhci_.ExpectGetBaseClock(100'000'000);
EXPECT_OK(dut_->Init());
uint32_t buffer[16] = {
// clang-format off
0x178096fb, 0x27328a47, 0x3267ce33, 0x8fccdf57,
0x84d24349, 0x68fd8e47, 0x6b7363a3, 0x5f9fb9b1,
0xfa0263f0, 0x467731aa, 0xf1a95135, 0xe9e7ba6b,
0x2112719a, 0x7ee23bad, 0xb4285417, 0x6db4a2d1,
// clang-format on
};
sdmmc_req_t request = {
.cmd_idx = SDMMC_WRITE_MULTIPLE_BLOCK,
.cmd_flags = SDMMC_WRITE_MULTIPLE_BLOCK_FLAGS,
.arg = 0xfc4e6f56,
.blockcount = 4,
.blocksize = 16,
.use_dma = false,
.dma_vmo = ZX_HANDLE_INVALID,
.virt_buffer = reinterpret_cast<uint8_t*>(buffer),
.virt_size = 0,
.buf_offset = 0,
.pmt = ZX_HANDLE_INVALID,
.probe_tuning_cmd = 0,
.response = {},
.status = ZX_ERR_BAD_STATE,
};
Response::Get(0).FromValue(0x4ea3f1f3).WriteTo(&mmio_);
EXPECT_OK(dut_->SdmmcRequest(&request));
auto transfer_mode = TransferMode::Get().FromValue(0);
auto command = Command::Get().FromValue(0);
EXPECT_EQ(BlockSize::Get().ReadFrom(&mmio_).reg_value(), 16);
EXPECT_EQ(BlockCount::Get().ReadFrom(&mmio_).reg_value(), 4);
EXPECT_EQ(Argument::Get().ReadFrom(&mmio_).reg_value(), 0xfc4e6f56);
EXPECT_TRUE(transfer_mode.ReadFrom(&mmio_).multi_block());
EXPECT_FALSE(transfer_mode.read());
EXPECT_EQ(transfer_mode.auto_cmd_enable(), TransferMode::kAutoCmd12);
EXPECT_TRUE(transfer_mode.block_count_enable());
EXPECT_FALSE(transfer_mode.dma_enable());
EXPECT_EQ(command.ReadFrom(&mmio_).command_index(), SDMMC_WRITE_MULTIPLE_BLOCK);
EXPECT_EQ(command.command_type(), Command::kCommandTypeNormal);
EXPECT_TRUE(command.data_present());
EXPECT_TRUE(command.command_index_check());
EXPECT_TRUE(command.command_crc_check());
EXPECT_EQ(command.response_type(), Command::kResponseType48Bits);
EXPECT_EQ(BufferData::Get().ReadFrom(&mmio_).reg_value(), 0x6db4a2d1);
EXPECT_OK(request.status);
EXPECT_EQ(request.response[0], 0x4ea3f1f3);
request = {
.cmd_idx = SDMMC_READ_MULTIPLE_BLOCK,
.cmd_flags = SDMMC_READ_MULTIPLE_BLOCK_FLAGS,
.arg = 0x55c1c22c,
.blockcount = 4,
.blocksize = 16,
.use_dma = false,
.dma_vmo = ZX_HANDLE_INVALID,
.virt_buffer = reinterpret_cast<uint8_t*>(buffer),
.virt_size = 0,
.buf_offset = 0,
.pmt = ZX_HANDLE_INVALID,
.probe_tuning_cmd = 0,
.response = {},
.status = ZX_ERR_BAD_STATE,
};
Response::Get(0).FromValue(0xa5387c19).WriteTo(&mmio_);
BufferData::Get().FromValue(0xe99dd637).WriteTo(&mmio_);
EXPECT_OK(dut_->SdmmcRequest(&request));
EXPECT_EQ(BlockSize::Get().ReadFrom(&mmio_).reg_value(), 16);
EXPECT_EQ(BlockCount::Get().ReadFrom(&mmio_).reg_value(), 4);
EXPECT_EQ(Argument::Get().ReadFrom(&mmio_).reg_value(), 0x55c1c22c);
EXPECT_TRUE(transfer_mode.ReadFrom(&mmio_).multi_block());
EXPECT_TRUE(transfer_mode.read());
EXPECT_EQ(transfer_mode.auto_cmd_enable(), TransferMode::kAutoCmd12);
EXPECT_TRUE(transfer_mode.block_count_enable());
EXPECT_FALSE(transfer_mode.dma_enable());
EXPECT_EQ(command.ReadFrom(&mmio_).command_index(), SDMMC_READ_MULTIPLE_BLOCK);
EXPECT_EQ(command.command_type(), Command::kCommandTypeNormal);
EXPECT_TRUE(command.data_present());
EXPECT_TRUE(command.command_index_check());
EXPECT_TRUE(command.command_crc_check());
EXPECT_EQ(command.response_type(), Command::kResponseType48Bits);
EXPECT_OK(request.status);
EXPECT_EQ(request.response[0], 0xa5387c19);
for (uint32_t i = 0; i < 16; i++) {
EXPECT_EQ(buffer[i], 0xe99dd637);
}
dut_->DdkUnbind(ddk::UnbindTxn(fake_ddk::kFakeDevice));
}
TEST_F(SdhciTest, RequestAbort) {
ASSERT_NO_FATAL_FAILURES(CreateDut());
mock_sdhci_.ExpectGetBaseClock(100'000'000);
EXPECT_OK(dut_->Init());
uint32_t buffer[4] = {0x178096fb, 0x27328a47, 0x3267ce33, 0x8fccdf57};
sdmmc_req_t request = {
.cmd_idx = SDMMC_WRITE_MULTIPLE_BLOCK,
.cmd_flags = SDMMC_WRITE_MULTIPLE_BLOCK_FLAGS,
.arg = 0,
.blockcount = 4,
.blocksize = 4,
.use_dma = false,
.dma_vmo = ZX_HANDLE_INVALID,
.virt_buffer = reinterpret_cast<uint8_t*>(buffer),
.virt_size = 0,
.buf_offset = 0,
.pmt = ZX_HANDLE_INVALID,
.probe_tuning_cmd = 0,
.response = {},
.status = ZX_ERR_BAD_STATE,
};
dut_->reset_mask();
EXPECT_OK(dut_->SdmmcRequest(&request));
EXPECT_EQ(dut_->reset_mask(), 0);
request.cmd_idx = SDMMC_STOP_TRANSMISSION;
request.cmd_flags = SDMMC_STOP_TRANSMISSION_FLAGS;
request.blockcount = 0;
request.blocksize = 0;
request.virt_buffer = nullptr;
EXPECT_OK(dut_->SdmmcRequest(&request));
EXPECT_EQ(dut_->reset_mask(),
SoftwareReset::Get().FromValue(0).set_reset_dat(1).set_reset_cmd(1).reg_value());
dut_->DdkUnbind(ddk::UnbindTxn(fake_ddk::kFakeDevice));
}
TEST_F(SdhciTest, DmaRequest64Bit) {
ASSERT_NO_FATAL_FAILURES(CreateDut());
mock_sdhci_.ExpectGetBaseClock(100'000'000);
Capabilities0::Get()
.FromValue(0)
.set_adma2_support(1)
.set_v3_64_bit_system_address_support(1)
.WriteTo(&mmio_);
EXPECT_OK(dut_->Init());
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(PAGE_SIZE * 4, 0, &vmo));
sdmmc_req_t request = {
.cmd_idx = SDMMC_WRITE_MULTIPLE_BLOCK,
.cmd_flags = SDMMC_WRITE_MULTIPLE_BLOCK_FLAGS,
.arg = 0,
.blockcount = 4,
.blocksize = PAGE_SIZE,
.use_dma = true,
.dma_vmo = vmo.get(),
.virt_buffer = nullptr,
.virt_size = 0,
.buf_offset = 0,
.pmt = ZX_HANDLE_INVALID,
.probe_tuning_cmd = 0,
.response = {},
.status = ZX_ERR_BAD_STATE,
};
EXPECT_OK(dut_->SdmmcRequest(&request));
EXPECT_EQ(AdmaSystemAddress::Get(0).ReadFrom(&mmio_).reg_value(), PAGE_SIZE);
EXPECT_EQ(AdmaSystemAddress::Get(1).ReadFrom(&mmio_).reg_value(), 0);
const Sdhci::AdmaDescriptor96* const descriptors =
reinterpret_cast<Sdhci::AdmaDescriptor96*>(dut_->iobuf_virt());
uint64_t address;
memcpy(&address, &descriptors[0].address, sizeof(address));
EXPECT_EQ(descriptors[0].attr, 0b100'001);
EXPECT_EQ(address, PAGE_SIZE);
EXPECT_EQ(descriptors[0].length, PAGE_SIZE);
EXPECT_EQ(descriptors[1].attr, 0b100'001);
EXPECT_EQ(descriptors[1].address, PAGE_SIZE);
EXPECT_EQ(descriptors[1].length, PAGE_SIZE);
memcpy(&address, &descriptors[2].address, sizeof(address));
EXPECT_EQ(descriptors[2].attr, 0b100'001);
EXPECT_EQ(address, PAGE_SIZE);
EXPECT_EQ(descriptors[2].length, PAGE_SIZE);
EXPECT_EQ(descriptors[3].attr, 0b100'011);
EXPECT_EQ(descriptors[3].address, PAGE_SIZE);
EXPECT_EQ(descriptors[3].length, PAGE_SIZE);
dut_->DdkUnbind(ddk::UnbindTxn(fake_ddk::kFakeDevice));
}
TEST_F(SdhciTest, DmaRequest32Bit) {
ASSERT_NO_FATAL_FAILURES(CreateDut());
mock_sdhci_.ExpectGetBaseClock(100'000'000);
Capabilities0::Get()
.FromValue(0)
.set_adma2_support(1)
.set_v3_64_bit_system_address_support(0)
.WriteTo(&mmio_);
EXPECT_OK(dut_->Init());
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(PAGE_SIZE * 4, 0, &vmo));
sdmmc_req_t request = {
.cmd_idx = SDMMC_READ_MULTIPLE_BLOCK,
.cmd_flags = SDMMC_READ_MULTIPLE_BLOCK_FLAGS,
.arg = 0,
.blockcount = 4,
.blocksize = PAGE_SIZE,
.use_dma = true,
.dma_vmo = vmo.get(),
.virt_buffer = nullptr,
.virt_size = 0,
.buf_offset = 0,
.pmt = ZX_HANDLE_INVALID,
.probe_tuning_cmd = 0,
.response = {},
.status = ZX_ERR_BAD_STATE,
};
EXPECT_OK(dut_->SdmmcRequest(&request));
EXPECT_EQ(AdmaSystemAddress::Get(0).ReadFrom(&mmio_).reg_value(), PAGE_SIZE);
EXPECT_EQ(AdmaSystemAddress::Get(1).ReadFrom(&mmio_).reg_value(), 0);
const Sdhci::AdmaDescriptor64* const descriptors =
reinterpret_cast<Sdhci::AdmaDescriptor64*>(dut_->iobuf_virt());
EXPECT_EQ(descriptors[0].attr, 0b100'001);
EXPECT_EQ(descriptors[0].address, PAGE_SIZE);
EXPECT_EQ(descriptors[0].length, PAGE_SIZE);
EXPECT_EQ(descriptors[1].attr, 0b100'001);
EXPECT_EQ(descriptors[1].address, PAGE_SIZE);
EXPECT_EQ(descriptors[1].length, PAGE_SIZE);
EXPECT_EQ(descriptors[2].attr, 0b100'001);
EXPECT_EQ(descriptors[2].address, PAGE_SIZE);
EXPECT_EQ(descriptors[2].length, PAGE_SIZE);
EXPECT_EQ(descriptors[3].attr, 0b100'011);
EXPECT_EQ(descriptors[3].address, PAGE_SIZE);
EXPECT_EQ(descriptors[3].length, PAGE_SIZE);
dut_->DdkUnbind(ddk::UnbindTxn(fake_ddk::kFakeDevice));
}
TEST_F(SdhciTest, SdioInBandInterrupt) {
ASSERT_NO_FATAL_FAILURES(CreateDut());
mock_sdhci_.ExpectGetBaseClock(100'000'000);
EXPECT_OK(dut_->Init());
in_band_interrupt_protocol_ops_t callback_ops = {
.callback = [](void* ctx) -> void {
sync_completion_signal(reinterpret_cast<sync_completion_t*>(ctx));
},
};
sync_completion_t callback_called;
in_band_interrupt_protocol_t callback = {
.ops = &callback_ops,
.ctx = &callback_called,
};
EXPECT_OK(dut_->SdmmcRegisterInBandInterrupt(&callback));
dut_->TriggerCardInterrupt();
sync_completion_wait(&callback_called, ZX_TIME_INFINITE);
dut_->DdkUnbind(ddk::UnbindTxn(fake_ddk::kFakeDevice));
}
TEST_F(SdhciTest, DmaSplitOneBoundary) {
ASSERT_NO_FATAL_FAILURES(CreateDut(SDHCI_QUIRK_USE_DMA_BOUNDARY_ALIGNMENT, 0x0800'0000));
mock_sdhci_.ExpectGetBaseClock(100'000'000);
Capabilities0::Get()
.FromValue(0)
.set_adma2_support(1)
.set_v3_64_bit_system_address_support(0)
.WriteTo(&mmio_);
EXPECT_OK(dut_->Init());
constexpr zx_paddr_t kStartAddress = 0xa7ff'ffff & ~kPageMask;
dut_->set_dma_paddrs(std::vector<zx_paddr_t>{
kStartAddress,
kStartAddress + PAGE_SIZE,
kStartAddress + (PAGE_SIZE * 2),
0xb000'0000,
});
sdmmc_req_t request = {
.cmd_idx = SDMMC_READ_MULTIPLE_BLOCK,
.cmd_flags = SDMMC_READ_MULTIPLE_BLOCK_FLAGS,
.arg = 0,
.blockcount = (PAGE_SIZE / 8) + 16, // Two pages plus 256 bytes.
.blocksize = 16,
.use_dma = true,
.dma_vmo = ZX_HANDLE_INVALID,
.virt_buffer = nullptr,
.virt_size = 0,
.buf_offset = PAGE_SIZE - 4, // The first buffer should be split across the 128M boundary.
.pmt = ZX_HANDLE_INVALID,
.probe_tuning_cmd = 0,
.response = {},
.status = ZX_ERR_BAD_STATE,
};
EXPECT_OK(dut_->SdmmcRequest(&request));
EXPECT_EQ(AdmaSystemAddress::Get(0).ReadFrom(&mmio_).reg_value(), PAGE_SIZE);
EXPECT_EQ(AdmaSystemAddress::Get(1).ReadFrom(&mmio_).reg_value(), 0);
const Sdhci::AdmaDescriptor64* const descriptors =
reinterpret_cast<Sdhci::AdmaDescriptor64*>(dut_->iobuf_virt());
EXPECT_EQ(descriptors[0].attr, 0b100'001);
EXPECT_EQ(descriptors[0].address, 0xa7ff'fffc);
EXPECT_EQ(descriptors[0].length, 4);
EXPECT_EQ(descriptors[1].attr, 0b100'001);
EXPECT_EQ(descriptors[1].address, 0xa800'0000);
EXPECT_EQ(descriptors[1].length, PAGE_SIZE * 2);
EXPECT_EQ(descriptors[2].attr, 0b100'011);
EXPECT_EQ(descriptors[2].address, 0xb000'0000);
EXPECT_EQ(descriptors[2].length, 256 - 4);
dut_->DdkUnbind(ddk::UnbindTxn(fake_ddk::kFakeDevice));
}
TEST_F(SdhciTest, DmaSplitManyBoundaries) {
ASSERT_NO_FATAL_FAILURES(CreateDut(SDHCI_QUIRK_USE_DMA_BOUNDARY_ALIGNMENT, 0x100));
mock_sdhci_.ExpectGetBaseClock(100'000'000);
Capabilities0::Get()
.FromValue(0)
.set_adma2_support(1)
.set_v3_64_bit_system_address_support(0)
.WriteTo(&mmio_);
EXPECT_OK(dut_->Init());
dut_->set_dma_paddrs(std::vector<zx_paddr_t>{0xabcd'0000});
sdmmc_req_t request = {
.cmd_idx = SDMMC_READ_MULTIPLE_BLOCK,
.cmd_flags = SDMMC_READ_MULTIPLE_BLOCK_FLAGS,
.arg = 0,
.blockcount = 64,
.blocksize = 16,
.use_dma = true,
.dma_vmo = ZX_HANDLE_INVALID,
.virt_buffer = nullptr,
.virt_size = 0,
.buf_offset = 128,
.pmt = ZX_HANDLE_INVALID,
.probe_tuning_cmd = 0,
.response = {},
.status = ZX_ERR_BAD_STATE,
};
EXPECT_OK(dut_->SdmmcRequest(&request));
EXPECT_EQ(AdmaSystemAddress::Get(0).ReadFrom(&mmio_).reg_value(), PAGE_SIZE);
EXPECT_EQ(AdmaSystemAddress::Get(1).ReadFrom(&mmio_).reg_value(), 0);
const Sdhci::AdmaDescriptor64* const descriptors =
reinterpret_cast<Sdhci::AdmaDescriptor64*>(dut_->iobuf_virt());
EXPECT_EQ(descriptors[0].attr, 0b100'001);
EXPECT_EQ(descriptors[0].address, 0xabcd'0080);
EXPECT_EQ(descriptors[0].length, 128);
EXPECT_EQ(descriptors[1].attr, 0b100'001);
EXPECT_EQ(descriptors[1].address, 0xabcd'0100);
EXPECT_EQ(descriptors[1].length, 256);
EXPECT_EQ(descriptors[2].attr, 0b100'001);
EXPECT_EQ(descriptors[2].address, 0xabcd'0200);
EXPECT_EQ(descriptors[2].length, 256);
EXPECT_EQ(descriptors[3].attr, 0b100'001);
EXPECT_EQ(descriptors[3].address, 0xabcd'0300);
EXPECT_EQ(descriptors[3].length, 256);
EXPECT_EQ(descriptors[4].attr, 0b100'011);
EXPECT_EQ(descriptors[4].address, 0xabcd'0400);
EXPECT_EQ(descriptors[4].length, 128);
dut_->DdkUnbind(ddk::UnbindTxn(fake_ddk::kFakeDevice));
}
TEST_F(SdhciTest, DmaNoBoundaries) {
ASSERT_NO_FATAL_FAILURES(CreateDut());
mock_sdhci_.ExpectGetBaseClock(100'000'000);
Capabilities0::Get()
.FromValue(0)
.set_adma2_support(1)
.set_v3_64_bit_system_address_support(0)
.WriteTo(&mmio_);
EXPECT_OK(dut_->Init());
constexpr zx_paddr_t kStartAddress = 0xa7ff'ffff & ~kPageMask;
dut_->set_dma_paddrs(std::vector<zx_paddr_t>{
kStartAddress,
kStartAddress + PAGE_SIZE,
kStartAddress + (PAGE_SIZE * 2),
0xb000'0000,
});
sdmmc_req_t request = {
.cmd_idx = SDMMC_READ_MULTIPLE_BLOCK,
.cmd_flags = SDMMC_READ_MULTIPLE_BLOCK_FLAGS,
.arg = 0,
.blockcount = (PAGE_SIZE / 8) + 16,
.blocksize = 16,
.use_dma = true,
.dma_vmo = ZX_HANDLE_INVALID,
.virt_buffer = nullptr,
.virt_size = 0,
.buf_offset = PAGE_SIZE - 4,
.pmt = ZX_HANDLE_INVALID,
.probe_tuning_cmd = 0,
.response = {},
.status = ZX_ERR_BAD_STATE,
};
EXPECT_OK(dut_->SdmmcRequest(&request));
EXPECT_EQ(AdmaSystemAddress::Get(0).ReadFrom(&mmio_).reg_value(), PAGE_SIZE);
EXPECT_EQ(AdmaSystemAddress::Get(1).ReadFrom(&mmio_).reg_value(), 0);
const Sdhci::AdmaDescriptor64* const descriptors =
reinterpret_cast<Sdhci::AdmaDescriptor64*>(dut_->iobuf_virt());
EXPECT_EQ(descriptors[0].attr, 0b100'001);
EXPECT_EQ(descriptors[0].address, 0xa7ff'fffc);
EXPECT_EQ(descriptors[0].length, (PAGE_SIZE * 2) + 4);
EXPECT_EQ(descriptors[1].attr, 0b100'011);
EXPECT_EQ(descriptors[1].address, 0xb000'0000);
EXPECT_EQ(descriptors[1].length, 256 - 4);
dut_->DdkUnbind(ddk::UnbindTxn(fake_ddk::kFakeDevice));
}
} // namespace sdhci