blob: d432bea2a3b8d0e1c1636f0062271f3288ada321 [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 "aml-sdmmc.h"
#include <lib/ddk/platform-defs.h>
#include <lib/fake-bti/bti.h>
#include <lib/fake_ddk/fake_ddk.h>
#include <lib/inspect/testing/cpp/zxtest/inspect.h>
#include <lib/mmio-ptr/fake.h>
#include <lib/sdio/hw.h>
#include <lib/sdmmc/hw.h>
#include <threads.h>
#include <vector>
#include <soc/aml-s912/s912-hw.h>
#include <zxtest/zxtest.h>
#include "aml-sdmmc-regs.h"
namespace sdmmc {
class TestAmlSdmmc : public AmlSdmmc {
public:
TestAmlSdmmc(const mmio_buffer_t& mmio, zx::bti bti)
: AmlSdmmc(fake_ddk::kFakeParent, std::move(bti), ddk::MmioBuffer(mmio),
ddk::MmioPinnedBuffer({&mmio, ZX_HANDLE_INVALID, 0x100}),
aml_sdmmc_config_t{
.supports_dma = true,
.min_freq = 400000,
.max_freq = 120000000,
.version_3 = true,
.prefs = 0,
},
zx::interrupt(ZX_HANDLE_INVALID), ddk::GpioProtocolClient()) {}
zx_status_t TestDdkAdd() {
// call parent's bind
return Bind();
}
const inspect::Hierarchy* GetInspectRoot(std::string suffix) {
const zx::vmo inspect_vmo = GetInspectVmo();
if (!inspect_vmo.is_valid()) {
return nullptr;
}
inspector_.ReadInspect(inspect_vmo);
return inspector_.hierarchy().GetByPath({"aml-sdmmc-port" + suffix});
}
void DdkRelease() { AmlSdmmc::DdkRelease(); }
zx_status_t WaitForInterruptImpl() override {
if (request_index_ < request_results_.size() && request_results_[request_index_] == 0) {
// Indicate a receive CRC error.
mmio_.Write32(1, kAmlSdmmcStatusOffset);
successful_transfers_ = 0;
request_index_++;
} else if (interrupt_status_.has_value()) {
mmio_.Write32(interrupt_status_.value(), kAmlSdmmcStatusOffset);
} else {
// Indicate that the request completed successfully.
mmio_.Write32(1 << 13, kAmlSdmmcStatusOffset);
// Each tuning transfer is attempted five times with a short-circuit if one fails.
// Report every successful transfer five times to make the results arrays easier to
// follow.
if (++successful_transfers_ % AML_SDMMC_TUNING_TEST_ATTEMPTS == 0) {
successful_transfers_ = 0;
request_index_++;
}
}
return ZX_OK;
}
void WaitForBus() const override { /* Do nothing, bus is always ready in tests */
}
void SetRequestResults(const char* request_results) {
request_results_.clear();
const size_t results_size = strlen(request_results);
request_results_.reserve(results_size);
for (size_t i = 0; i < results_size; i++) {
ASSERT_TRUE((request_results[i] == '|') || (request_results[i] == '-'));
request_results_.push_back(request_results[i] == '|' ? 1 : 0);
}
request_index_ = 0;
}
void SetRequestInterruptStatus(uint32_t status) { interrupt_status_ = status; }
aml_sdmmc_desc_t* descs() { return AmlSdmmc::descs(); }
private:
std::vector<uint8_t> request_results_;
size_t request_index_ = 0;
uint32_t successful_transfers_ = 0;
// The optional interrupt status to set after a request is completed.
std::optional<uint32_t> interrupt_status_;
inspect::InspectTestHelper inspector_;
};
class AmlSdmmcTest : public zxtest::Test {
public:
AmlSdmmcTest() : mmio_({FakeMmioPtr(&mmio_), 0, 0, ZX_HANDLE_INVALID}) {}
void SetUp() override {
registers_.reset(new uint8_t[S912_SD_EMMC_B_LENGTH]);
memset(registers_.get(), 0, S912_SD_EMMC_B_LENGTH);
mmio_buffer_t mmio_buffer = {
.vaddr = FakeMmioPtr(registers_.get()),
.offset = 0,
.size = S912_SD_EMMC_B_LENGTH,
.vmo = ZX_HANDLE_INVALID,
};
mmio_ = ddk::MmioBuffer(mmio_buffer);
memset(bti_paddrs_, 0, sizeof(bti_paddrs_));
// This is used by AmlSdmmc::Init() to create the descriptor buffer -- can be any nonzero paddr.
bti_paddrs_[0] = zx_system_get_page_size();
zx::bti bti;
ASSERT_OK(fake_bti_create_with_paddrs(bti_paddrs_, countof(bti_paddrs_),
bti.reset_and_get_address()));
dut_ = new TestAmlSdmmc(mmio_buffer, std::move(bti));
dut_->set_board_config({
.supports_dma = true,
.min_freq = 400000,
.max_freq = 120000000,
.version_3 = true,
.prefs = 0,
});
mmio_.Write32(0xff, kAmlSdmmcDelay1Offset);
mmio_.Write32(0xff, kAmlSdmmcDelay2Offset);
mmio_.Write32(0xff, kAmlSdmmcAdjustOffset);
dut_->SdmmcHwReset();
EXPECT_EQ(mmio_.Read32(kAmlSdmmcDelay1Offset), 0);
EXPECT_EQ(mmio_.Read32(kAmlSdmmcDelay2Offset), 0);
EXPECT_EQ(mmio_.Read32(kAmlSdmmcAdjustOffset), 0);
mmio_.Write32(1, kAmlSdmmcCfgOffset); // Set bus width 4.
memcpy(reinterpret_cast<uint8_t*>(registers_.get()) + kAmlSdmmcPingOffset,
aml_sdmmc_tuning_blk_pattern_4bit, sizeof(aml_sdmmc_tuning_blk_pattern_4bit));
}
void TearDown() override {
if (dut_ != nullptr) {
dut_->DdkRelease();
}
}
protected:
static zx_koid_t GetVmoKoid(const zx::vmo& vmo) {
zx_info_handle_basic_t info = {};
size_t actual = 0;
size_t available = 0;
zx_status_t status =
vmo.get_info(ZX_INFO_HANDLE_BASIC, &info, sizeof(info), &actual, &available);
if (status != ZX_OK || actual < 1) {
return ZX_KOID_INVALID;
}
return info.koid;
}
void InitializeContiguousPaddrs(const size_t vmos) {
// Start at 1 because one paddr has already been read to create the DMA descriptor buffer.
for (size_t i = 0; i < vmos; i++) {
bti_paddrs_[i + 1] = (i << 24) | zx_system_get_page_size();
}
}
void InitializeSingleVmoPaddrs(const size_t pages) {
// Start at 1 because one paddr has already been read to create the DMA descriptor buffer.
for (size_t i = 0; i < pages; i++) {
bti_paddrs_[i + 1] = zx_system_get_page_size() * (i + 1);
}
}
void InitializeNonContiguousPaddrs(const size_t vmos) {
for (size_t i = 0; i < vmos; i++) {
bti_paddrs_[i + 1] = zx_system_get_page_size() * (i + 1) * 2;
}
}
zx_paddr_t bti_paddrs_[64] = {};
ddk::MmioBuffer mmio_;
TestAmlSdmmc* dut_ = nullptr;
private:
std::unique_ptr<uint8_t[]> registers_;
};
TEST_F(AmlSdmmcTest, DdkLifecycle) {
fake_ddk::Bind ddk;
EXPECT_OK(dut_->TestDdkAdd());
dut_->DdkAsyncRemove();
EXPECT_TRUE(ddk.Ok());
}
TEST_F(AmlSdmmcTest, InitV3) {
dut_->set_board_config({
.supports_dma = false,
.min_freq = 400000,
.max_freq = 120000000,
.version_3 = true,
.prefs = 0,
});
AmlSdmmcClock::Get().FromValue(0).WriteTo(&mmio_);
ASSERT_OK(dut_->Init({}));
EXPECT_EQ(AmlSdmmcClock::Get().ReadFrom(&mmio_).reg_value(), AmlSdmmcClockV3::Get()
.FromValue(0)
.set_cfg_div(60)
.set_cfg_src(0)
.set_cfg_co_phase(2)
.set_cfg_tx_phase(0)
.set_cfg_rx_phase(0)
.set_cfg_always_on(1)
.reg_value());
}
TEST_F(AmlSdmmcTest, InitV2) {
dut_->set_board_config({
.supports_dma = false,
.min_freq = 400000,
.max_freq = 120000000,
.version_3 = false,
.prefs = 0,
});
AmlSdmmcClock::Get().FromValue(0).WriteTo(&mmio_);
ASSERT_OK(dut_->Init({}));
EXPECT_EQ(AmlSdmmcClock::Get().ReadFrom(&mmio_).reg_value(), AmlSdmmcClockV2::Get()
.FromValue(0)
.set_cfg_div(60)
.set_cfg_src(0)
.set_cfg_co_phase(2)
.set_cfg_tx_phase(0)
.set_cfg_rx_phase(0)
.set_cfg_always_on(1)
.reg_value());
}
TEST_F(AmlSdmmcTest, TuningV3) {
dut_->set_board_config({
.supports_dma = false,
.min_freq = 400000,
.max_freq = 120000000,
.version_3 = true,
.prefs = 0,
});
ASSERT_OK(dut_->Init({}));
AmlSdmmcClock::Get().FromValue(0).set_cfg_div(10).WriteTo(&mmio_);
AmlSdmmcCfg::Get().ReadFrom(&mmio_).set_bus_width(AmlSdmmcCfg::kBusWidth4Bit).WriteTo(&mmio_);
auto adjust = AmlSdmmcAdjust::Get().FromValue(0);
auto adjust_v2 = AmlSdmmcAdjustV2::Get().FromValue(0);
adjust.set_adj_fixed(0).set_adj_delay(0x3f).WriteTo(&mmio_);
adjust_v2.set_adj_fixed(0).set_adj_delay(0x3f).WriteTo(&mmio_);
EXPECT_OK(dut_->SdmmcPerformTuning(SD_SEND_TUNING_BLOCK));
adjust.ReadFrom(&mmio_);
adjust_v2.ReadFrom(&mmio_);
EXPECT_EQ(adjust.adj_fixed(), 1);
EXPECT_EQ(adjust.adj_delay(), 0);
}
TEST_F(AmlSdmmcTest, TuningV2) {
dut_->set_board_config({
.supports_dma = false,
.min_freq = 400000,
.max_freq = 120000000,
.version_3 = false,
.prefs = 0,
});
ASSERT_OK(dut_->Init({}));
AmlSdmmcClock::Get().FromValue(0).set_cfg_div(10).WriteTo(&mmio_);
AmlSdmmcCfg::Get().ReadFrom(&mmio_).set_bus_width(AmlSdmmcCfg::kBusWidth4Bit).WriteTo(&mmio_);
auto adjust = AmlSdmmcAdjust::Get().FromValue(0);
auto adjust_v2 = AmlSdmmcAdjustV2::Get().FromValue(0);
adjust.set_adj_fixed(0).set_adj_delay(0x3f).WriteTo(&mmio_);
adjust_v2.set_adj_fixed(0).set_adj_delay(0x3f).WriteTo(&mmio_);
EXPECT_OK(dut_->SdmmcPerformTuning(SD_SEND_TUNING_BLOCK));
adjust.ReadFrom(&mmio_);
adjust_v2.ReadFrom(&mmio_);
EXPECT_EQ(adjust_v2.adj_fixed(), 1);
EXPECT_EQ(adjust_v2.adj_delay(), 0);
}
TEST_F(AmlSdmmcTest, TuningAllPass) {
ASSERT_OK(dut_->Init({}));
AmlSdmmcCfg::Get().ReadFrom(&mmio_).set_bus_width(AmlSdmmcCfg::kBusWidth4Bit).WriteTo(&mmio_);
auto clock = AmlSdmmcClock::Get().ReadFrom(&mmio_).set_cfg_div(10).WriteTo(&mmio_);
auto adjust = AmlSdmmcAdjust::Get().FromValue(0).set_adj_delay(0x3f).WriteTo(&mmio_);
auto delay1 = AmlSdmmcDelay1::Get().FromValue(0).WriteTo(&mmio_);
auto delay2 = AmlSdmmcDelay2::Get().FromValue(0).WriteTo(&mmio_);
EXPECT_OK(dut_->SdmmcPerformTuning(SD_SEND_TUNING_BLOCK));
clock.ReadFrom(&mmio_);
adjust.ReadFrom(&mmio_);
delay1.ReadFrom(&mmio_);
delay2.ReadFrom(&mmio_);
EXPECT_EQ(clock.cfg_tx_phase(), 0);
EXPECT_EQ(adjust.adj_delay(), 0);
EXPECT_EQ(delay1.dly_0(), 32);
EXPECT_EQ(delay1.dly_1(), 32);
EXPECT_EQ(delay1.dly_2(), 32);
EXPECT_EQ(delay1.dly_3(), 32);
EXPECT_EQ(delay1.dly_4(), 32);
EXPECT_EQ(delay2.dly_5(), 32);
EXPECT_EQ(delay2.dly_6(), 32);
EXPECT_EQ(delay2.dly_7(), 32);
EXPECT_EQ(delay2.dly_8(), 32);
EXPECT_EQ(delay2.dly_9(), 32);
}
TEST_F(AmlSdmmcTest, AdjDelayTuningNoWindowWrap) {
dut_->SetRequestResults(
"-|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
"-|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
"-|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||");
ASSERT_OK(dut_->Init({.did = PDEV_DID_AMLOGIC_SDMMC_B}));
AmlSdmmcCfg::Get().ReadFrom(&mmio_).set_bus_width(AmlSdmmcCfg::kBusWidth4Bit).WriteTo(&mmio_);
auto clock = AmlSdmmcClock::Get().ReadFrom(&mmio_).set_cfg_div(10).WriteTo(&mmio_);
auto adjust = AmlSdmmcAdjust::Get().FromValue(0).set_adj_delay(0x3f).WriteTo(&mmio_);
EXPECT_OK(dut_->SdmmcPerformTuning(SD_SEND_TUNING_BLOCK));
clock.ReadFrom(&mmio_);
adjust.ReadFrom(&mmio_);
EXPECT_EQ(clock.cfg_tx_phase(), 0);
EXPECT_EQ(adjust.adj_delay(), 6);
const auto* root = dut_->GetInspectRoot("B");
ASSERT_NOT_NULL(root);
const auto* adj_delay = root->node().get_property<inspect::UintPropertyValue>("adj_delay");
ASSERT_NOT_NULL(adj_delay);
EXPECT_EQ(adj_delay->value(), 6);
}
TEST_F(AmlSdmmcTest, AdjDelayTuningLargestWindowChosen) {
dut_->SetRequestResults(
"-|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
"-|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||");
ASSERT_OK(dut_->Init({.did = PDEV_DID_AMLOGIC_SDMMC_A}));
AmlSdmmcCfg::Get().ReadFrom(&mmio_).set_bus_width(AmlSdmmcCfg::kBusWidth4Bit).WriteTo(&mmio_);
auto clock = AmlSdmmcClock::Get().ReadFrom(&mmio_).set_cfg_div(10).WriteTo(&mmio_);
auto adjust = AmlSdmmcAdjust::Get().FromValue(0).set_adj_delay(0x3f).WriteTo(&mmio_);
EXPECT_OK(dut_->SdmmcPerformTuning(SD_SEND_TUNING_BLOCK));
clock.ReadFrom(&mmio_);
adjust.ReadFrom(&mmio_);
EXPECT_EQ(clock.cfg_tx_phase(), 0);
EXPECT_EQ(adjust.adj_delay(), 7);
const auto* root = dut_->GetInspectRoot("A");
ASSERT_NOT_NULL(root);
const auto* adj_delay = root->node().get_property<inspect::UintPropertyValue>("adj_delay");
ASSERT_NOT_NULL(adj_delay);
EXPECT_EQ(adj_delay->value(), 7);
}
TEST_F(AmlSdmmcTest, AdjDelayTuningWindowWrap) {
dut_->SetRequestResults(
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
"-|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
"-|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
"-|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
"-|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||");
ASSERT_OK(dut_->Init({.did = PDEV_DID_AMLOGIC_SDMMC_C}));
AmlSdmmcCfg::Get().ReadFrom(&mmio_).set_bus_width(AmlSdmmcCfg::kBusWidth4Bit).WriteTo(&mmio_);
auto clock = AmlSdmmcClock::Get().ReadFrom(&mmio_).set_cfg_div(10).WriteTo(&mmio_);
auto adjust = AmlSdmmcAdjust::Get().FromValue(0).set_adj_delay(0x3f).WriteTo(&mmio_);
EXPECT_OK(dut_->SdmmcPerformTuning(SD_SEND_TUNING_BLOCK));
clock.ReadFrom(&mmio_);
adjust.ReadFrom(&mmio_);
EXPECT_EQ(clock.cfg_tx_phase(), 0);
EXPECT_EQ(adjust.adj_delay(), 0);
const auto* root = dut_->GetInspectRoot("C");
ASSERT_NOT_NULL(root);
const auto* adj_delay = root->node().get_property<inspect::UintPropertyValue>("adj_delay");
ASSERT_NOT_NULL(adj_delay);
EXPECT_EQ(adj_delay->value(), 0);
}
TEST_F(AmlSdmmcTest, AdjDelayTuningAllFail) {
dut_->SetRequestResults(
"----------------------------------------------------------------"
"----------------------------------------------------------------"
"----------------------------------------------------------------"
"----------------------------------------------------------------"
"----------------------------------------------------------------"
"----------------------------------------------------------------"
"----------------------------------------------------------------"
"----------------------------------------------------------------"
"----------------------------------------------------------------"
"----------------------------------------------------------------");
ASSERT_OK(dut_->Init({.did = PDEV_DID_AMLOGIC_SDMMC_B}));
AmlSdmmcClock::Get().FromValue(0).set_cfg_div(10).WriteTo(&mmio_);
AmlSdmmcCfg::Get().ReadFrom(&mmio_).set_bus_width(AmlSdmmcCfg::kBusWidth4Bit).WriteTo(&mmio_);
EXPECT_NOT_OK(dut_->SdmmcPerformTuning(SD_SEND_TUNING_BLOCK));
const auto* root = dut_->GetInspectRoot("B");
ASSERT_NOT_NULL(root);
const auto* tuning_results =
root->node().get_property<inspect::StringPropertyValue>("tuning_results");
ASSERT_NOT_NULL(tuning_results);
EXPECT_STR_EQ(tuning_results->value(), "failed");
}
TEST_F(AmlSdmmcTest, DelayLineTuningNoWindowWrap) {
dut_->SetRequestResults(
// Best window: start 12, size 10, delay 17.
"|-----------||||||||||---------------------||-------------------"
"----------------------------------------------------------------"
"----------------------------------------------------------------"
"----------------------------------------------------------------"
"----------------------------------------------------------------"
"----------------------------------------------------------------"
"----------------------------------------------------------------"
"----------------------------------------------------------------"
"----------------------------------------------------------------"
"----------------------------------------------------------------");
ASSERT_OK(dut_->Init({.did = PDEV_DID_AMLOGIC_SDMMC_B}));
AmlSdmmcClock::Get().FromValue(0).set_cfg_div(10).WriteTo(&mmio_);
AmlSdmmcCfg::Get().ReadFrom(&mmio_).set_bus_width(AmlSdmmcCfg::kBusWidth4Bit).WriteTo(&mmio_);
auto delay1 = AmlSdmmcDelay1::Get().FromValue(0).WriteTo(&mmio_);
auto delay2 = AmlSdmmcDelay2::Get().FromValue(0).WriteTo(&mmio_);
EXPECT_OK(dut_->SdmmcPerformTuning(SD_SEND_TUNING_BLOCK));
delay1.ReadFrom(&mmio_);
delay2.ReadFrom(&mmio_);
EXPECT_EQ(delay1.dly_0(), 17);
EXPECT_EQ(delay1.dly_1(), 17);
EXPECT_EQ(delay1.dly_2(), 17);
EXPECT_EQ(delay1.dly_3(), 17);
EXPECT_EQ(delay1.dly_4(), 17);
EXPECT_EQ(delay2.dly_5(), 17);
EXPECT_EQ(delay2.dly_6(), 17);
EXPECT_EQ(delay2.dly_7(), 17);
EXPECT_EQ(delay2.dly_8(), 17);
EXPECT_EQ(delay2.dly_9(), 17);
const auto* root = dut_->GetInspectRoot("B");
ASSERT_NOT_NULL(root);
const auto* delay_lines = root->node().get_property<inspect::UintPropertyValue>("delay_lines");
ASSERT_NOT_NULL(delay_lines);
EXPECT_EQ(delay_lines->value(), 17);
const auto* tuning_results =
root->node().get_property<inspect::StringPropertyValue>("tuning_results_adj_delay_0");
ASSERT_NOT_NULL(tuning_results);
EXPECT_STR_EQ(tuning_results->value(),
"|-----------||||||||||---------------------||-------------------");
const auto* max_delay = root->node().get_property<inspect::UintPropertyValue>("max_delay");
ASSERT_NOT_NULL(max_delay);
EXPECT_EQ(max_delay->value(), 64);
const auto* distance =
root->node().get_property<inspect::UintPropertyValue>("distance_to_failing_point");
ASSERT_NOT_NULL(distance);
EXPECT_EQ(distance->value(), 5);
}
TEST_F(AmlSdmmcTest, DelayLineTuningWindowWrap) {
dut_->SetRequestResults(
// Best window: start 54, size 25, delay 2.
"|||||||||||||||-----------|||||||||||||||||||---------||||||||||"
"----------------------------------------------------------------"
"----------------------------------------------------------------"
"----------------------------------------------------------------"
"----------------------------------------------------------------"
"----------------------------------------------------------------"
"----------------------------------------------------------------"
"----------------------------------------------------------------"
"----------------------------------------------------------------"
"----------------------------------------------------------------");
ASSERT_OK(dut_->Init({}));
AmlSdmmcClock::Get().FromValue(0).set_cfg_div(10).WriteTo(&mmio_);
AmlSdmmcCfg::Get().ReadFrom(&mmio_).set_bus_width(AmlSdmmcCfg::kBusWidth4Bit).WriteTo(&mmio_);
auto delay1 = AmlSdmmcDelay1::Get().FromValue(0).WriteTo(&mmio_);
auto delay2 = AmlSdmmcDelay2::Get().FromValue(0).WriteTo(&mmio_);
EXPECT_OK(dut_->SdmmcPerformTuning(SD_SEND_TUNING_BLOCK));
delay1.ReadFrom(&mmio_);
delay2.ReadFrom(&mmio_);
EXPECT_EQ(delay1.dly_0(), 2);
EXPECT_EQ(delay1.dly_1(), 2);
EXPECT_EQ(delay1.dly_2(), 2);
EXPECT_EQ(delay1.dly_3(), 2);
EXPECT_EQ(delay1.dly_4(), 2);
EXPECT_EQ(delay2.dly_5(), 2);
EXPECT_EQ(delay2.dly_6(), 2);
EXPECT_EQ(delay2.dly_7(), 2);
EXPECT_EQ(delay2.dly_8(), 2);
EXPECT_EQ(delay2.dly_9(), 2);
const auto* root = dut_->GetInspectRoot("-unknown");
ASSERT_NOT_NULL(root);
const auto* delay_lines = root->node().get_property<inspect::UintPropertyValue>("delay_lines");
ASSERT_NOT_NULL(delay_lines);
EXPECT_EQ(delay_lines->value(), 2);
const auto* tuning_results =
root->node().get_property<inspect::StringPropertyValue>("tuning_results_adj_delay_0");
ASSERT_NOT_NULL(tuning_results);
EXPECT_STR_EQ(tuning_results->value(),
"|||||||||||||||-----------|||||||||||||||||||---------||||||||||");
const auto* max_delay = root->node().get_property<inspect::UintPropertyValue>("max_delay");
ASSERT_NOT_NULL(max_delay);
EXPECT_EQ(max_delay->value(), 64);
const auto* distance =
root->node().get_property<inspect::UintPropertyValue>("distance_to_failing_point");
ASSERT_NOT_NULL(distance);
EXPECT_EQ(distance->value(), 13);
}
TEST_F(AmlSdmmcTest, DelayLineTuningAllFail) {
dut_->SetRequestResults(
"----------------------------------------------------------------"
"----------------------------------------------------------------"
"----------------------------------------------------------------"
"----------------------------------------------------------------"
"----------------------------------------------------------------"
"----------------------------------------------------------------"
"----------------------------------------------------------------"
"----------------------------------------------------------------"
"----------------------------------------------------------------"
"----------------------------------------------------------------");
ASSERT_OK(dut_->Init({}));
AmlSdmmcClock::Get().FromValue(0).set_cfg_div(10).WriteTo(&mmio_);
AmlSdmmcCfg::Get().ReadFrom(&mmio_).set_bus_width(AmlSdmmcCfg::kBusWidth4Bit).WriteTo(&mmio_);
EXPECT_NOT_OK(dut_->SdmmcPerformTuning(SD_SEND_TUNING_BLOCK));
const auto* root = dut_->GetInspectRoot("-unknown");
ASSERT_NOT_NULL(root);
const auto* tuning_results =
root->node().get_property<inspect::StringPropertyValue>("tuning_results_adj_delay_0");
ASSERT_NOT_NULL(tuning_results);
EXPECT_STR_EQ(tuning_results->value(),
"----------------------------------------------------------------");
const auto* max_delay = root->node().get_property<inspect::UintPropertyValue>("max_delay");
ASSERT_NOT_NULL(max_delay);
EXPECT_EQ(max_delay->value(), 64);
}
TEST_F(AmlSdmmcTest, NewDelayLineTuningAllPass) {
dut_->set_board_config({
.supports_dma = true,
.min_freq = 400000,
.max_freq = 120000000,
.version_3 = true,
.prefs = 0,
.use_new_tuning = true,
});
ASSERT_OK(dut_->Init({}));
AmlSdmmcClock::Get().FromValue(0).set_cfg_div(10).WriteTo(&mmio_);
AmlSdmmcCfg::Get().ReadFrom(&mmio_).set_bus_width(AmlSdmmcCfg::kBusWidth4Bit).WriteTo(&mmio_);
auto adjust = AmlSdmmcAdjust::Get().FromValue(0).set_adj_delay(0x3f).WriteTo(&mmio_);
auto delay1 = AmlSdmmcDelay1::Get().FromValue(0).WriteTo(&mmio_);
auto delay2 = AmlSdmmcDelay2::Get().FromValue(0).WriteTo(&mmio_);
// The new tuning method should fail because no transfers failed. The old tuning method should be
// used as a fallback.
EXPECT_OK(dut_->SdmmcPerformTuning(SD_SEND_TUNING_BLOCK));
adjust.ReadFrom(&mmio_);
delay1.ReadFrom(&mmio_);
delay2.ReadFrom(&mmio_);
EXPECT_EQ(adjust.adj_delay(), 0);
EXPECT_EQ(delay1.dly_0(), 32);
EXPECT_EQ(delay1.dly_1(), 32);
EXPECT_EQ(delay1.dly_2(), 32);
EXPECT_EQ(delay1.dly_3(), 32);
EXPECT_EQ(delay1.dly_4(), 32);
EXPECT_EQ(delay2.dly_5(), 32);
EXPECT_EQ(delay2.dly_6(), 32);
EXPECT_EQ(delay2.dly_7(), 32);
EXPECT_EQ(delay2.dly_8(), 32);
EXPECT_EQ(delay2.dly_9(), 32);
const auto* root = dut_->GetInspectRoot("-unknown");
ASSERT_NOT_NULL(root);
const auto* adj_delay = root->node().get_property<inspect::UintPropertyValue>("adj_delay");
ASSERT_NOT_NULL(adj_delay);
EXPECT_EQ(adj_delay->value(), 0);
const auto* delay_lines = root->node().get_property<inspect::UintPropertyValue>("delay_lines");
ASSERT_NOT_NULL(delay_lines);
EXPECT_EQ(delay_lines->value(), 32);
const auto* tuning_results =
root->node().get_property<inspect::StringPropertyValue>("tuning_results_adj_delay_0");
ASSERT_NOT_NULL(tuning_results);
EXPECT_STR_EQ(tuning_results->value(),
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||");
const auto* distance =
root->node().get_property<inspect::UintPropertyValue>("distance_to_failing_point");
ASSERT_NOT_NULL(distance);
EXPECT_EQ(distance->value(), 63);
}
TEST_F(AmlSdmmcTest, NewDelayLineTuningFallBackToOld) {
dut_->SetRequestResults(
"-----------|||||||||||||||||||||||||||||||||||||||||||||||||----"
"-------------------------------|||||||||||||||||||||||||||||||||"
"||||||||||-----------------------------------------|||||||||||||"
"||||||||||||||||||||||||||||||----------------------------------"
"||||||||||||||||||||||||||||||||||||||||||||||||||--------------"
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
"-----------|||||||||||||||||||||||||||||||||||||||||||||||||||||"
"-------------------------------|||||||||||||||||||||||||||||||||"
"||||||||||||||||||||-------------------------------|||||||||||||"
"||||||||||||||||||||||||||||||||||||||||------------------------");
dut_->set_board_config({
.supports_dma = true,
.min_freq = 400000,
.max_freq = 120000000,
.version_3 = true,
.prefs = 0,
.use_new_tuning = true,
});
ASSERT_OK(dut_->Init({}));
AmlSdmmcClock::Get().FromValue(0).set_cfg_div(10).WriteTo(&mmio_);
AmlSdmmcCfg::Get().ReadFrom(&mmio_).set_bus_width(AmlSdmmcCfg::kBusWidth4Bit).WriteTo(&mmio_);
auto adjust = AmlSdmmcAdjust::Get().FromValue(0).set_adj_delay(0x3f).WriteTo(&mmio_);
auto delay1 = AmlSdmmcDelay1::Get().FromValue(0).WriteTo(&mmio_);
auto delay2 = AmlSdmmcDelay2::Get().FromValue(0).WriteTo(&mmio_);
// The new tuning method should fail because the chosen adj_delay had some delay line failures.
// The old tuning method should be used as a fallback.
EXPECT_OK(dut_->SdmmcPerformTuning(SD_SEND_TUNING_BLOCK));
adjust.ReadFrom(&mmio_);
delay1.ReadFrom(&mmio_);
delay2.ReadFrom(&mmio_);
EXPECT_EQ(adjust.adj_delay(), 4);
EXPECT_EQ(delay1.dly_0(), 25);
EXPECT_EQ(delay1.dly_1(), 25);
EXPECT_EQ(delay1.dly_2(), 25);
EXPECT_EQ(delay1.dly_3(), 25);
EXPECT_EQ(delay1.dly_4(), 25);
EXPECT_EQ(delay2.dly_5(), 25);
EXPECT_EQ(delay2.dly_6(), 25);
EXPECT_EQ(delay2.dly_7(), 25);
EXPECT_EQ(delay2.dly_8(), 25);
EXPECT_EQ(delay2.dly_9(), 25);
const auto* root = dut_->GetInspectRoot("-unknown");
ASSERT_NOT_NULL(root);
const auto* adj_delay = root->node().get_property<inspect::UintPropertyValue>("adj_delay");
ASSERT_NOT_NULL(adj_delay);
EXPECT_EQ(adj_delay->value(), 4);
const auto* delay_lines = root->node().get_property<inspect::UintPropertyValue>("delay_lines");
ASSERT_NOT_NULL(delay_lines);
EXPECT_EQ(delay_lines->value(), 25);
const auto* tuning_results =
root->node().get_property<inspect::StringPropertyValue>("tuning_results_adj_delay_4");
ASSERT_NOT_NULL(tuning_results);
EXPECT_STR_EQ(tuning_results->value(),
"||||||||||||||||||||||||||||||||||||||||||||||||||--------------");
const auto* distance =
root->node().get_property<inspect::UintPropertyValue>("distance_to_failing_point");
ASSERT_NOT_NULL(distance);
EXPECT_EQ(distance->value(), 25);
}
TEST_F(AmlSdmmcTest, NewDelayLineTuningCheckOldUseNew) {
dut_->SetRequestResults(
// New tuning: adj_delay=2 delay=0 distance=32
// Old tuning: adj_delay=3 delay=31 distance=31
"----------------||||||||||||||||||||||||||||||||||||||||||||||||"
"-|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
"||||||||||||||||||||||||||||||||-|||||||||||||||||||||||||||||||"
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||--"
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||");
dut_->set_board_config({
.supports_dma = true,
.min_freq = 400000,
.max_freq = 120000000,
.version_3 = true,
.prefs = 0,
.use_new_tuning = true,
});
ASSERT_OK(dut_->Init({}));
AmlSdmmcClock::Get().FromValue(0).set_cfg_div(5).WriteTo(&mmio_);
AmlSdmmcCfg::Get().ReadFrom(&mmio_).set_bus_width(AmlSdmmcCfg::kBusWidth4Bit).WriteTo(&mmio_);
auto adjust = AmlSdmmcAdjust::Get().FromValue(0).set_adj_delay(0x3f).WriteTo(&mmio_);
auto delay1 = AmlSdmmcDelay1::Get().FromValue(0).WriteTo(&mmio_);
auto delay2 = AmlSdmmcDelay2::Get().FromValue(0).WriteTo(&mmio_);
// The old tuning method should be checked because the chosen adj_delay had some delay line
// failures, but the new method should be used because it is further from a failing point.
EXPECT_OK(dut_->SdmmcPerformTuning(SD_SEND_TUNING_BLOCK));
adjust.ReadFrom(&mmio_);
delay1.ReadFrom(&mmio_);
delay2.ReadFrom(&mmio_);
EXPECT_EQ(adjust.adj_delay(), 2);
EXPECT_EQ(delay1.dly_0(), 0);
EXPECT_EQ(delay1.dly_1(), 0);
EXPECT_EQ(delay1.dly_2(), 0);
EXPECT_EQ(delay1.dly_3(), 0);
EXPECT_EQ(delay1.dly_4(), 0);
EXPECT_EQ(delay2.dly_5(), 0);
EXPECT_EQ(delay2.dly_6(), 0);
EXPECT_EQ(delay2.dly_7(), 0);
EXPECT_EQ(delay2.dly_8(), 0);
EXPECT_EQ(delay2.dly_9(), 0);
const auto* root = dut_->GetInspectRoot("-unknown");
ASSERT_NOT_NULL(root);
const auto* adj_delay = root->node().get_property<inspect::UintPropertyValue>("adj_delay");
ASSERT_NOT_NULL(adj_delay);
EXPECT_EQ(adj_delay->value(), 2);
const auto* delay_lines = root->node().get_property<inspect::UintPropertyValue>("delay_lines");
ASSERT_NOT_NULL(delay_lines);
EXPECT_EQ(delay_lines->value(), 0);
const auto* tuning_results =
root->node().get_property<inspect::StringPropertyValue>("tuning_results_adj_delay_2");
ASSERT_NOT_NULL(tuning_results);
EXPECT_STR_EQ(tuning_results->value(),
"||||||||||||||||||||||||||||||||-|||||||||||||||||||||||||||||||");
const auto* distance =
root->node().get_property<inspect::UintPropertyValue>("distance_to_failing_point");
ASSERT_NOT_NULL(distance);
EXPECT_EQ(distance->value(), 32);
}
TEST_F(AmlSdmmcTest, NewDelayLineTuningEvenDivider) {
dut_->SetRequestResults(
// Largest failing window: adj_delay 8, middle delay 25
"||||||||||||||||||||||||||||||||||||||||||||||||||--------------"
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
"-|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
"---------------------|||||||||||||||||||||||||||||||||||||||||||"
"||||||||||-------------------------------|||||||||||||||||||||||"
"||||||||||||||||||||||||||||||-------------------------------|||");
dut_->set_board_config({
.supports_dma = true,
.min_freq = 400000,
.max_freq = 120000000,
.version_3 = true,
.prefs = 0,
.use_new_tuning = true,
});
ASSERT_OK(dut_->Init({}));
AmlSdmmcClock::Get().FromValue(0).set_cfg_div(10).WriteTo(&mmio_);
AmlSdmmcCfg::Get().ReadFrom(&mmio_).set_bus_width(AmlSdmmcCfg::kBusWidth4Bit).WriteTo(&mmio_);
auto adjust = AmlSdmmcAdjust::Get().FromValue(0).set_adj_delay(0x3f).WriteTo(&mmio_);
auto delay1 = AmlSdmmcDelay1::Get().FromValue(0).WriteTo(&mmio_);
auto delay2 = AmlSdmmcDelay2::Get().FromValue(0).WriteTo(&mmio_);
EXPECT_OK(dut_->SdmmcPerformTuning(SD_SEND_TUNING_BLOCK));
adjust.ReadFrom(&mmio_);
delay1.ReadFrom(&mmio_);
delay2.ReadFrom(&mmio_);
EXPECT_EQ(adjust.adj_delay(), 3);
EXPECT_EQ(delay1.dly_0(), 25);
EXPECT_EQ(delay1.dly_1(), 25);
EXPECT_EQ(delay1.dly_2(), 25);
EXPECT_EQ(delay1.dly_3(), 25);
EXPECT_EQ(delay1.dly_4(), 25);
EXPECT_EQ(delay2.dly_5(), 25);
EXPECT_EQ(delay2.dly_6(), 25);
EXPECT_EQ(delay2.dly_7(), 25);
EXPECT_EQ(delay2.dly_8(), 25);
EXPECT_EQ(delay2.dly_9(), 25);
const auto* root = dut_->GetInspectRoot("-unknown");
ASSERT_NOT_NULL(root);
const auto* adj_delay = root->node().get_property<inspect::UintPropertyValue>("adj_delay");
ASSERT_NOT_NULL(adj_delay);
EXPECT_EQ(adj_delay->value(), 3);
const auto* delay_lines = root->node().get_property<inspect::UintPropertyValue>("delay_lines");
ASSERT_NOT_NULL(delay_lines);
EXPECT_EQ(delay_lines->value(), 25);
const auto* tuning_results =
root->node().get_property<inspect::StringPropertyValue>("tuning_results_adj_delay_4");
ASSERT_NOT_NULL(tuning_results);
EXPECT_STR_EQ(tuning_results->value(),
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||");
const auto* distance =
root->node().get_property<inspect::UintPropertyValue>("distance_to_failing_point");
ASSERT_NOT_NULL(distance);
EXPECT_EQ(distance->value(), 63);
}
TEST_F(AmlSdmmcTest, NewDelayLineTuningOddDivider) {
dut_->SetRequestResults(
// Largest failing window: adj_delay 3, first delay 0
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
"-----------|||||||||||||||||||||||||||||||||||||||||||||||||||||"
"-------------------------------|||||||||||||||||||||||||||||||||"
"||||||||||||||||||||-------------------------------|||||||||||||"
"||||||||||||||||||||||||||||||||||||||||------------------------"
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||----"
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||");
dut_->set_board_config({
.supports_dma = true,
.min_freq = 400000,
.max_freq = 120000000,
.version_3 = true,
.prefs = 0,
.use_new_tuning = true,
});
ASSERT_OK(dut_->Init({}));
AmlSdmmcClock::Get().FromValue(0).set_cfg_div(9).WriteTo(&mmio_);
AmlSdmmcCfg::Get().ReadFrom(&mmio_).set_bus_width(AmlSdmmcCfg::kBusWidth4Bit).WriteTo(&mmio_);
auto adjust = AmlSdmmcAdjust::Get().FromValue(0).set_adj_delay(0x3f).WriteTo(&mmio_);
auto delay1 = AmlSdmmcDelay1::Get().FromValue(0).WriteTo(&mmio_);
auto delay2 = AmlSdmmcDelay2::Get().FromValue(0).WriteTo(&mmio_);
EXPECT_OK(dut_->SdmmcPerformTuning(SD_SEND_TUNING_BLOCK));
adjust.ReadFrom(&mmio_);
delay1.ReadFrom(&mmio_);
delay2.ReadFrom(&mmio_);
EXPECT_EQ(adjust.adj_delay(), 7);
EXPECT_EQ(delay1.dly_0(), 0);
EXPECT_EQ(delay1.dly_1(), 0);
EXPECT_EQ(delay1.dly_2(), 0);
EXPECT_EQ(delay1.dly_3(), 0);
EXPECT_EQ(delay1.dly_4(), 0);
EXPECT_EQ(delay2.dly_5(), 0);
EXPECT_EQ(delay2.dly_6(), 0);
EXPECT_EQ(delay2.dly_7(), 0);
EXPECT_EQ(delay2.dly_8(), 0);
EXPECT_EQ(delay2.dly_9(), 0);
const auto* root = dut_->GetInspectRoot("-unknown");
ASSERT_NOT_NULL(root);
const auto* adj_delay = root->node().get_property<inspect::UintPropertyValue>("adj_delay");
ASSERT_NOT_NULL(adj_delay);
EXPECT_EQ(adj_delay->value(), 7);
const auto* delay_lines = root->node().get_property<inspect::UintPropertyValue>("delay_lines");
ASSERT_NOT_NULL(delay_lines);
EXPECT_EQ(delay_lines->value(), 0);
const auto* tuning_results =
root->node().get_property<inspect::StringPropertyValue>("tuning_results_adj_delay_7");
ASSERT_NOT_NULL(tuning_results);
EXPECT_STR_EQ(tuning_results->value(),
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||");
const auto* distance =
root->node().get_property<inspect::UintPropertyValue>("distance_to_failing_point");
ASSERT_NOT_NULL(distance);
EXPECT_EQ(distance->value(), 63);
}
TEST_F(AmlSdmmcTest, NewDelayLineTuningCorrectFailingWindowIfLastOne) {
dut_->SetRequestResults(
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||----"
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||");
dut_->set_board_config({
.supports_dma = true,
.min_freq = 400000,
.max_freq = 120000000,
.version_3 = true,
.prefs = 0,
.use_new_tuning = true,
});
ASSERT_OK(dut_->Init({}));
AmlSdmmcClock::Get().FromValue(0).set_cfg_div(5).WriteTo(&mmio_);
AmlSdmmcCfg::Get().ReadFrom(&mmio_).set_bus_width(AmlSdmmcCfg::kBusWidth4Bit).WriteTo(&mmio_);
auto adjust = AmlSdmmcAdjust::Get().FromValue(0).set_adj_delay(0x3f).WriteTo(&mmio_);
auto delay1 = AmlSdmmcDelay1::Get().FromValue(0).WriteTo(&mmio_);
auto delay2 = AmlSdmmcDelay2::Get().FromValue(0).WriteTo(&mmio_);
// The old tuning method should be checked because the chosen adj_delay had some delay line
// failures, but the new method should be used because it is further from a failing point.
EXPECT_OK(dut_->SdmmcPerformTuning(SD_SEND_TUNING_BLOCK));
adjust.ReadFrom(&mmio_);
delay1.ReadFrom(&mmio_);
delay2.ReadFrom(&mmio_);
EXPECT_EQ(adjust.adj_delay(), 2);
EXPECT_EQ(delay1.dly_0(), 60);
EXPECT_EQ(delay1.dly_1(), 60);
EXPECT_EQ(delay1.dly_2(), 60);
EXPECT_EQ(delay1.dly_3(), 60);
EXPECT_EQ(delay1.dly_4(), 60);
EXPECT_EQ(delay2.dly_5(), 60);
EXPECT_EQ(delay2.dly_6(), 60);
EXPECT_EQ(delay2.dly_7(), 60);
EXPECT_EQ(delay2.dly_8(), 60);
EXPECT_EQ(delay2.dly_9(), 60);
const auto* root = dut_->GetInspectRoot("-unknown");
ASSERT_NOT_NULL(root);
const auto* adj_delay = root->node().get_property<inspect::UintPropertyValue>("adj_delay");
ASSERT_NOT_NULL(adj_delay);
EXPECT_EQ(adj_delay->value(), 2);
const auto* delay_lines = root->node().get_property<inspect::UintPropertyValue>("delay_lines");
ASSERT_NOT_NULL(delay_lines);
EXPECT_EQ(delay_lines->value(), 60);
}
TEST_F(AmlSdmmcTest, SetBusFreq) {
ASSERT_OK(dut_->Init({}));
{
const auto* root = dut_->GetInspectRoot("-unknown");
ASSERT_NOT_NULL(root);
const auto* bus_freq =
root->node().get_property<inspect::UintPropertyValue>("bus_clock_frequency");
ASSERT_NOT_NULL(bus_freq);
EXPECT_EQ(bus_freq->value(), 400'000);
}
AmlSdmmcCfg::Get().ReadFrom(&mmio_).set_bus_width(AmlSdmmcCfg::kBusWidth4Bit).WriteTo(&mmio_);
auto clock = AmlSdmmcClock::Get().FromValue(0).WriteTo(&mmio_);
EXPECT_OK(dut_->SdmmcSetBusFreq(100'000'000));
EXPECT_EQ(clock.ReadFrom(&mmio_).cfg_div(), 10);
EXPECT_EQ(clock.cfg_src(), 1);
{
const auto* root = dut_->GetInspectRoot("-unknown");
ASSERT_NOT_NULL(root);
const auto* bus_freq =
root->node().get_property<inspect::UintPropertyValue>("bus_clock_frequency");
ASSERT_NOT_NULL(bus_freq);
EXPECT_EQ(bus_freq->value(), 100'000'000);
}
// Greater than the max, will be capped at 120 MHz.
EXPECT_OK(dut_->SdmmcSetBusFreq(200'000'000));
EXPECT_EQ(clock.ReadFrom(&mmio_).cfg_div(), 9);
EXPECT_EQ(clock.cfg_src(), 1);
{
const auto* root = dut_->GetInspectRoot("-unknown");
ASSERT_NOT_NULL(root);
const auto* bus_freq =
root->node().get_property<inspect::UintPropertyValue>("bus_clock_frequency");
ASSERT_NOT_NULL(bus_freq);
EXPECT_EQ(bus_freq->value(), 111'111'111);
}
EXPECT_OK(dut_->SdmmcSetBusFreq(0));
EXPECT_EQ(clock.ReadFrom(&mmio_).cfg_div(), 0);
{
const auto* root = dut_->GetInspectRoot("-unknown");
ASSERT_NOT_NULL(root);
const auto* bus_freq =
root->node().get_property<inspect::UintPropertyValue>("bus_clock_frequency");
ASSERT_NOT_NULL(bus_freq);
EXPECT_EQ(bus_freq->value(), 0);
}
EXPECT_OK(dut_->SdmmcSetBusFreq(54'000'000));
EXPECT_EQ(clock.ReadFrom(&mmio_).cfg_div(), 19);
EXPECT_EQ(clock.cfg_src(), 1);
{
const auto* root = dut_->GetInspectRoot("-unknown");
ASSERT_NOT_NULL(root);
const auto* bus_freq =
root->node().get_property<inspect::UintPropertyValue>("bus_clock_frequency");
ASSERT_NOT_NULL(bus_freq);
EXPECT_EQ(bus_freq->value(), 52'631'578);
}
EXPECT_OK(dut_->SdmmcSetBusFreq(400'000));
EXPECT_EQ(clock.ReadFrom(&mmio_).cfg_div(), 60);
EXPECT_EQ(clock.cfg_src(), 0);
{
const auto* root = dut_->GetInspectRoot("-unknown");
ASSERT_NOT_NULL(root);
const auto* bus_freq =
root->node().get_property<inspect::UintPropertyValue>("bus_clock_frequency");
ASSERT_NOT_NULL(bus_freq);
EXPECT_EQ(bus_freq->value(), 400'000);
}
}
TEST_F(AmlSdmmcTest, ClearStatus) {
ASSERT_OK(dut_->Init({}));
// Set end_of_chain to indicate we're done and to have something to clear
dut_->SetRequestInterruptStatus(1 << 13);
sdmmc_req_t request;
memset(&request, 0, sizeof(request));
EXPECT_OK(dut_->SdmmcRequest(&request));
auto status = AmlSdmmcStatus::Get().FromValue(0);
EXPECT_EQ(AmlSdmmcStatus::kClearStatus, status.ReadFrom(&mmio_).reg_value());
}
TEST_F(AmlSdmmcTest, TxCrcError) {
ASSERT_OK(dut_->Init({}));
// Set TX CRC error bit (8) and desc_busy bit (30)
dut_->SetRequestInterruptStatus(1 << 8 | 1 << 30);
sdmmc_req_t request;
memset(&request, 0, sizeof(request));
EXPECT_EQ(ZX_ERR_IO_DATA_INTEGRITY, dut_->SdmmcRequest(&request));
auto start = AmlSdmmcStart::Get().FromValue(0);
// The desc busy bit should now have been cleared because of the error
EXPECT_EQ(0, start.ReadFrom(&mmio_).desc_busy());
}
TEST_F(AmlSdmmcTest, RequestsFailAfterSuspend) {
ASSERT_OK(dut_->Init({}));
sdmmc_req_t request;
memset(&request, 0, sizeof(request));
EXPECT_OK(dut_->SdmmcRequest(&request));
ddk::SuspendTxn txn(fake_ddk::kFakeDevice, 0, false, 0);
dut_->DdkSuspend(std::move(txn));
EXPECT_NOT_OK(dut_->SdmmcRequest(&request));
}
TEST_F(AmlSdmmcTest, UnownedVmosBlockMode) {
ASSERT_OK(dut_->Init({}));
InitializeContiguousPaddrs(10);
zx::vmo vmos[10] = {};
sdmmc_buffer_region_t buffers[10];
for (uint32_t i = 0; i < countof(vmos); i++) {
ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &vmos[i]));
buffers[i] = {
.buffer =
{
.vmo = vmos[i].get(),
},
.type = SDMMC_BUFFER_TYPE_VMO_HANDLE,
.offset = i * 16,
.size = 32 * (i + 2),
};
}
sdmmc_req_new_t request = {
.cmd_idx = SDMMC_READ_MULTIPLE_BLOCK,
.cmd_flags = SDMMC_READ_MULTIPLE_BLOCK_FLAGS,
.arg = 0x1234abcd,
.blocksize = 32,
.suppress_error_messages = false,
.client_id = 0,
.buffers_list = buffers,
.buffers_count = countof(buffers),
};
uint32_t response[4] = {};
AmlSdmmcCmdResp::Get().FromValue(0xfedc9876).WriteTo(&mmio_);
EXPECT_OK(dut_->SdmmcRequestNew(&request, response));
EXPECT_EQ(response[0], 0xfedc9876);
const aml_sdmmc_desc_t* descs = dut_->descs();
auto expected_desc_cfg = AmlSdmmcCmdCfg::Get()
.FromValue(0)
.set_len(2)
.set_block_mode(1)
.set_timeout(AmlSdmmcCmdCfg::kDefaultCmdTimeout)
.set_data_io(1)
.set_data_wr(0)
.set_resp_num(1)
.set_cmd_idx(SDMMC_READ_MULTIPLE_BLOCK)
.set_owner(1);
EXPECT_EQ(descs[0].cmd_info, expected_desc_cfg.reg_value());
EXPECT_EQ(descs[0].cmd_arg, 0x1234abcd);
EXPECT_EQ(descs[0].data_addr, zx_system_get_page_size());
EXPECT_EQ(descs[0].resp_addr, 0);
for (uint32_t i = 1; i < countof(vmos); i++) {
expected_desc_cfg.set_len(i + 2).set_no_resp(1).set_no_cmd(1).set_resp_num(0).set_cmd_idx(0);
if (i == countof(vmos) - 1) {
expected_desc_cfg.set_end_of_chain(1);
}
EXPECT_EQ(descs[i].cmd_info, expected_desc_cfg.reg_value());
EXPECT_EQ(descs[i].cmd_arg, 0);
EXPECT_EQ(descs[i].data_addr, (i << 24) | (zx_system_get_page_size() + (i * 16)));
EXPECT_EQ(descs[i].resp_addr, 0);
}
}
TEST_F(AmlSdmmcTest, UnownedVmosNotBlockSizeMultiple) {
ASSERT_OK(dut_->Init({}));
InitializeContiguousPaddrs(10);
zx::vmo vmos[10] = {};
sdmmc_buffer_region_t buffers[10];
for (uint32_t i = 0; i < countof(vmos); i++) {
ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &vmos[i]));
buffers[i] = {
.buffer =
{
.vmo = vmos[i].get(),
},
.type = SDMMC_BUFFER_TYPE_VMO_HANDLE,
.offset = 0,
.size = 32 * (i + 2),
};
}
buffers[5].size = 25;
sdmmc_req_new_t request = {
.cmd_idx = SDMMC_READ_MULTIPLE_BLOCK,
.cmd_flags = SDMMC_READ_MULTIPLE_BLOCK_FLAGS,
.arg = 0x1234abcd,
.blocksize = 32,
.suppress_error_messages = false,
.client_id = 0,
.buffers_list = buffers,
.buffers_count = countof(buffers),
};
uint32_t response[4] = {};
EXPECT_NOT_OK(dut_->SdmmcRequestNew(&request, response));
}
TEST_F(AmlSdmmcTest, UnownedVmosByteMode) {
ASSERT_OK(dut_->Init({}));
InitializeContiguousPaddrs(10);
zx::vmo vmos[10] = {};
sdmmc_buffer_region_t buffers[10];
for (uint32_t i = 0; i < countof(vmos); i++) {
ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &vmos[i]));
buffers[i] = {
.buffer =
{
.vmo = vmos[i].get(),
},
.type = SDMMC_BUFFER_TYPE_VMO_HANDLE,
.offset = i * 4,
.size = 50,
};
}
sdmmc_req_new_t request = {
.cmd_idx = SDMMC_READ_MULTIPLE_BLOCK,
.cmd_flags = SDMMC_READ_MULTIPLE_BLOCK_FLAGS,
.arg = 0x1234abcd,
.blocksize = 50,
.suppress_error_messages = false,
.client_id = 0,
.buffers_list = buffers,
.buffers_count = countof(buffers),
};
uint32_t response[4] = {};
AmlSdmmcCmdResp::Get().FromValue(0xfedc9876).WriteTo(&mmio_);
EXPECT_OK(dut_->SdmmcRequestNew(&request, response));
EXPECT_EQ(response[0], 0xfedc9876);
const aml_sdmmc_desc_t* descs = dut_->descs();
auto expected_desc_cfg = AmlSdmmcCmdCfg::Get()
.FromValue(0)
.set_len(50)
.set_timeout(AmlSdmmcCmdCfg::kDefaultCmdTimeout)
.set_data_io(1)
.set_data_wr(0)
.set_resp_num(1)
.set_cmd_idx(SDMMC_READ_MULTIPLE_BLOCK)
.set_owner(1);
EXPECT_EQ(descs[0].cmd_info, expected_desc_cfg.reg_value());
EXPECT_EQ(descs[0].cmd_arg, 0x1234abcd);
EXPECT_EQ(descs[0].data_addr, zx_system_get_page_size());
EXPECT_EQ(descs[0].resp_addr, 0);
for (uint32_t i = 1; i < countof(vmos); i++) {
expected_desc_cfg.set_len(50).set_no_resp(1).set_no_cmd(1).set_resp_num(0).set_cmd_idx(0);
if (i == countof(vmos) - 1) {
expected_desc_cfg.set_end_of_chain(1);
}
EXPECT_EQ(descs[i].cmd_info, expected_desc_cfg.reg_value());
EXPECT_EQ(descs[i].cmd_arg, 0);
EXPECT_EQ(descs[i].data_addr, (i << 24) | (zx_system_get_page_size() + (i * 4)));
EXPECT_EQ(descs[i].resp_addr, 0);
}
}
TEST_F(AmlSdmmcTest, UnownedVmoByteModeMultiBlock) {
ASSERT_OK(dut_->Init({}));
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &vmo));
InitializeContiguousPaddrs(1);
sdmmc_buffer_region_t buffer = {
.buffer =
{
.vmo = vmo.get(),
},
.type = SDMMC_BUFFER_TYPE_VMO_HANDLE,
.offset = 0,
.size = 400,
};
sdmmc_req_new_t request = {
.cmd_idx = SDMMC_READ_MULTIPLE_BLOCK,
.cmd_flags = SDMMC_READ_MULTIPLE_BLOCK_FLAGS,
.arg = 0x1234abcd,
.blocksize = 100,
.suppress_error_messages = false,
.client_id = 0,
.buffers_list = &buffer,
.buffers_count = 1,
};
uint32_t response[4] = {};
AmlSdmmcCmdResp::Get().FromValue(0xfedc9876).WriteTo(&mmio_);
EXPECT_OK(dut_->SdmmcRequestNew(&request, response));
EXPECT_EQ(response[0], 0xfedc9876);
const aml_sdmmc_desc_t* descs = dut_->descs();
auto expected_desc_cfg = AmlSdmmcCmdCfg::Get()
.FromValue(0)
.set_len(100)
.set_timeout(AmlSdmmcCmdCfg::kDefaultCmdTimeout)
.set_data_io(1)
.set_data_wr(0)
.set_resp_num(1)
.set_cmd_idx(SDMMC_READ_MULTIPLE_BLOCK)
.set_owner(1);
EXPECT_EQ(descs[0].cmd_info, expected_desc_cfg.reg_value());
EXPECT_EQ(descs[0].cmd_arg, 0x1234abcd);
EXPECT_EQ(descs[0].data_addr, zx_system_get_page_size());
EXPECT_EQ(descs[0].resp_addr, 0);
for (uint32_t i = 1; i < 4; i++) {
expected_desc_cfg.set_no_resp(1).set_no_cmd(1).set_resp_num(0).set_cmd_idx(0);
if (i == 3) {
expected_desc_cfg.set_end_of_chain(1);
}
EXPECT_EQ(descs[i].cmd_info, expected_desc_cfg.reg_value());
EXPECT_EQ(descs[i].cmd_arg, 0);
EXPECT_EQ(descs[i].data_addr, zx_system_get_page_size() + (i * 100));
EXPECT_EQ(descs[i].resp_addr, 0);
}
}
TEST_F(AmlSdmmcTest, UnownedVmoOffsetNotAligned) {
ASSERT_OK(dut_->Init({}));
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &vmo));
InitializeContiguousPaddrs(1);
sdmmc_buffer_region_t buffer = {
.buffer =
{
.vmo = vmo.get(),
},
.type = SDMMC_BUFFER_TYPE_VMO_HANDLE,
.offset = 3,
.size = 64,
};
sdmmc_req_new_t request = {
.cmd_idx = SDMMC_READ_MULTIPLE_BLOCK,
.cmd_flags = SDMMC_READ_MULTIPLE_BLOCK_FLAGS,
.arg = 0x1234abcd,
.blocksize = 32,
.suppress_error_messages = false,
.client_id = 0,
.buffers_list = &buffer,
.buffers_count = 1,
};
uint32_t response[4] = {};
AmlSdmmcCmdResp::Get().FromValue(0xfedc9876).WriteTo(&mmio_);
EXPECT_NOT_OK(dut_->SdmmcRequestNew(&request, response));
}
TEST_F(AmlSdmmcTest, UnownedVmoSingleBufferMultipleDescriptors) {
ASSERT_OK(dut_->Init({}));
zx::vmo vmo;
const size_t pages = ((32 * 514) / zx_system_get_page_size()) + 1;
ASSERT_OK(zx::vmo::create(pages * zx_system_get_page_size(), 0, &vmo));
InitializeSingleVmoPaddrs(pages);
sdmmc_buffer_region_t buffer = {
.buffer =
{
.vmo = vmo.get(),
},
.type = SDMMC_BUFFER_TYPE_VMO_HANDLE,
.offset = 16,
.size = 32 * 513,
};
sdmmc_req_new_t request = {
.cmd_idx = SDMMC_READ_MULTIPLE_BLOCK,
.cmd_flags = SDMMC_READ_MULTIPLE_BLOCK_FLAGS,
.arg = 0x1234abcd,
.blocksize = 32,
.suppress_error_messages = false,
.client_id = 0,
.buffers_list = &buffer,
.buffers_count = 1,
};
uint32_t response[4] = {};
AmlSdmmcCmdResp::Get().FromValue(0xfedc9876).WriteTo(&mmio_);
EXPECT_OK(dut_->SdmmcRequestNew(&request, response));
EXPECT_EQ(response[0], 0xfedc9876);
const aml_sdmmc_desc_t* descs = dut_->descs();
auto expected_desc_cfg = AmlSdmmcCmdCfg::Get()
.FromValue(0)
.set_len(511)
.set_block_mode(1)
.set_timeout(AmlSdmmcCmdCfg::kDefaultCmdTimeout)
.set_data_io(1)
.set_data_wr(0)
.set_resp_num(1)
.set_cmd_idx(SDMMC_READ_MULTIPLE_BLOCK)
.set_owner(1);
EXPECT_EQ(descs[0].cmd_info, expected_desc_cfg.reg_value());
EXPECT_EQ(descs[0].cmd_arg, 0x1234abcd);
EXPECT_EQ(descs[0].data_addr, zx_system_get_page_size() + 16);
EXPECT_EQ(descs[0].resp_addr, 0);
expected_desc_cfg.set_len(2)
.set_end_of_chain(1)
.set_no_resp(1)
.set_no_cmd(1)
.set_resp_num(0)
.set_cmd_idx(0);
EXPECT_EQ(descs[1].cmd_info, expected_desc_cfg.reg_value());
EXPECT_EQ(descs[1].cmd_arg, 0);
EXPECT_EQ(descs[1].data_addr, zx_system_get_page_size() + (511 * 32) + 16);
EXPECT_EQ(descs[1].resp_addr, 0);
}
TEST_F(AmlSdmmcTest, UnownedVmoSingleBufferNotPageAligned) {
ASSERT_OK(dut_->Init({}));
zx::vmo vmo;
const size_t pages = ((32 * 514) / zx_system_get_page_size()) + 1;
ASSERT_OK(zx::vmo::create(pages * zx_system_get_page_size(), 0, &vmo));
InitializeNonContiguousPaddrs(pages);
sdmmc_buffer_region_t buffer = {
.buffer =
{
.vmo = vmo.get(),
},
.type = SDMMC_BUFFER_TYPE_VMO_HANDLE,
.offset = 16,
.size = 32 * 513,
};
sdmmc_req_new_t request = {
.cmd_idx = SDMMC_READ_MULTIPLE_BLOCK,
.cmd_flags = SDMMC_READ_MULTIPLE_BLOCK_FLAGS,
.arg = 0x1234abcd,
.blocksize = 32,
.suppress_error_messages = false,
.client_id = 0,
.buffers_list = &buffer,
.buffers_count = 1,
};
uint32_t response[4] = {};
AmlSdmmcCmdResp::Get().FromValue(0xfedc9876).WriteTo(&mmio_);
EXPECT_NOT_OK(dut_->SdmmcRequestNew(&request, response));
}
TEST_F(AmlSdmmcTest, UnownedVmoSingleBufferPageAligned) {
ASSERT_OK(dut_->Init({}));
zx::vmo vmo;
const size_t pages = ((32 * 514) / zx_system_get_page_size()) + 1;
ASSERT_OK(zx::vmo::create(pages * zx_system_get_page_size(), 0, &vmo));
InitializeNonContiguousPaddrs(pages);
sdmmc_buffer_region_t buffer = {
.buffer =
{
.vmo = vmo.get(),
},
.type = SDMMC_BUFFER_TYPE_VMO_HANDLE,
.offset = 32,
.size = 32 * 513,
};
sdmmc_req_new_t request = {
.cmd_idx = SDMMC_READ_MULTIPLE_BLOCK,
.cmd_flags = SDMMC_READ_MULTIPLE_BLOCK_FLAGS,
.arg = 0x1234abcd,
.blocksize = 32,
.suppress_error_messages = false,
.client_id = 0,
.buffers_list = &buffer,
.buffers_count = 1,
};
uint32_t response[4] = {};
AmlSdmmcCmdResp::Get().FromValue(0xfedc9876).WriteTo(&mmio_);
EXPECT_OK(dut_->SdmmcRequestNew(&request, response));
EXPECT_EQ(response[0], 0xfedc9876);
const aml_sdmmc_desc_t* descs = dut_->descs();
auto expected_desc_cfg = AmlSdmmcCmdCfg::Get()
.FromValue(0)
.set_len(127)
.set_block_mode(1)
.set_timeout(AmlSdmmcCmdCfg::kDefaultCmdTimeout)
.set_data_io(1)
.set_data_wr(0)
.set_resp_num(1)
.set_cmd_idx(SDMMC_READ_MULTIPLE_BLOCK)
.set_owner(1);
EXPECT_EQ(descs[0].cmd_info, expected_desc_cfg.reg_value());
EXPECT_EQ(descs[0].cmd_arg, 0x1234abcd);
EXPECT_EQ(descs[0].data_addr, (zx_system_get_page_size() * 2) + 32);
EXPECT_EQ(descs[0].resp_addr, 0);
for (uint32_t i = 1; i < 5; i++) {
expected_desc_cfg.set_len(128).set_no_resp(1).set_no_cmd(1).set_resp_num(0).set_cmd_idx(0);
if (i == 4) {
expected_desc_cfg.set_len(2).set_end_of_chain(1);
}
EXPECT_EQ(descs[i].cmd_info, expected_desc_cfg.reg_value());
EXPECT_EQ(descs[i].cmd_arg, 0);
EXPECT_EQ(descs[i].data_addr, zx_system_get_page_size() * (i + 1) * 2);
EXPECT_EQ(descs[i].resp_addr, 0);
}
}
TEST_F(AmlSdmmcTest, OwnedVmosBlockMode) {
ASSERT_OK(dut_->Init({}));
InitializeContiguousPaddrs(10);
sdmmc_buffer_region_t buffers[10];
for (uint32_t i = 0; i < countof(buffers); i++) {
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &vmo));
EXPECT_OK(dut_->SdmmcRegisterVmo(i, 0, std::move(vmo), i * 64, 512, SDMMC_VMO_RIGHT_WRITE));
buffers[i] = {
.buffer =
{
.vmo_id = i,
},
.type = SDMMC_BUFFER_TYPE_VMO_ID,
.offset = i * 16,
.size = 32 * (i + 2),
};
}
zx::vmo vmo;
EXPECT_NOT_OK(dut_->SdmmcUnregisterVmo(3, 1, &vmo));
sdmmc_req_new_t request = {
.cmd_idx = SDMMC_READ_MULTIPLE_BLOCK,
.cmd_flags = SDMMC_READ_MULTIPLE_BLOCK_FLAGS,
.arg = 0x1234abcd,
.blocksize = 32,
.suppress_error_messages = false,
.client_id = 0,
.buffers_list = buffers,
.buffers_count = countof(buffers),
};
uint32_t response[4] = {};
AmlSdmmcCmdResp::Get().FromValue(0xfedc9876).WriteTo(&mmio_);
EXPECT_OK(dut_->SdmmcRequestNew(&request, response));
EXPECT_EQ(response[0], 0xfedc9876);
const aml_sdmmc_desc_t* descs = dut_->descs();
auto expected_desc_cfg = AmlSdmmcCmdCfg::Get()
.FromValue(0)
.set_len(2)
.set_block_mode(1)
.set_timeout(AmlSdmmcCmdCfg::kDefaultCmdTimeout)
.set_data_io(1)
.set_data_wr(0)
.set_resp_num(1)
.set_cmd_idx(SDMMC_READ_MULTIPLE_BLOCK)
.set_owner(1);
EXPECT_EQ(descs[0].cmd_info, expected_desc_cfg.reg_value());
EXPECT_EQ(descs[0].cmd_arg, 0x1234abcd);
EXPECT_EQ(descs[0].data_addr, zx_system_get_page_size());
EXPECT_EQ(descs[0].resp_addr, 0);
for (uint32_t i = 1; i < countof(buffers); i++) {
expected_desc_cfg.set_len(i + 2).set_no_resp(1).set_no_cmd(1).set_resp_num(0).set_cmd_idx(0);
if (i == countof(buffers) - 1) {
expected_desc_cfg.set_end_of_chain(1);
}
EXPECT_EQ(descs[i].cmd_info, expected_desc_cfg.reg_value());
EXPECT_EQ(descs[i].cmd_arg, 0);
EXPECT_EQ(descs[i].data_addr, (i << 24) | (zx_system_get_page_size() + (i * 80)));
EXPECT_EQ(descs[i].resp_addr, 0);
}
request.client_id = 7;
EXPECT_NOT_OK(dut_->SdmmcRequestNew(&request, response));
EXPECT_OK(dut_->SdmmcUnregisterVmo(3, 0, &vmo));
EXPECT_NOT_OK(dut_->SdmmcRegisterVmo(2, 0, std::move(vmo), 0, 512, SDMMC_VMO_RIGHT_WRITE));
request.client_id = 0;
EXPECT_NOT_OK(dut_->SdmmcRequestNew(&request, response));
}
TEST_F(AmlSdmmcTest, OwnedVmosNotBlockSizeMultiple) {
ASSERT_OK(dut_->Init({}));
InitializeContiguousPaddrs(10);
sdmmc_buffer_region_t buffers[10];
for (uint32_t i = 0; i < countof(buffers); i++) {
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &vmo));
EXPECT_OK(dut_->SdmmcRegisterVmo(i, 0, std::move(vmo), i * 64, 512, SDMMC_VMO_RIGHT_WRITE));
buffers[i] = {
.buffer =
{
.vmo_id = i,
},
.type = SDMMC_BUFFER_TYPE_VMO_ID,
.offset = 0,
.size = 32 * (i + 2),
};
}
buffers[5].size = 25;
sdmmc_req_new_t request = {
.cmd_idx = SDMMC_READ_MULTIPLE_BLOCK,
.cmd_flags = SDMMC_READ_MULTIPLE_BLOCK_FLAGS,
.arg = 0x1234abcd,
.blocksize = 32,
.suppress_error_messages = false,
.client_id = 0,
.buffers_list = buffers,
.buffers_count = countof(buffers),
};
uint32_t response[4] = {};
EXPECT_NOT_OK(dut_->SdmmcRequestNew(&request, response));
}
TEST_F(AmlSdmmcTest, OwnedVmosByteMode) {
ASSERT_OK(dut_->Init({}));
InitializeContiguousPaddrs(10);
sdmmc_buffer_region_t buffers[10];
for (uint32_t i = 0; i < countof(buffers); i++) {
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &vmo));
EXPECT_OK(dut_->SdmmcRegisterVmo(i, 0, std::move(vmo), i * 64, 512, SDMMC_VMO_RIGHT_WRITE));
buffers[i] = {
.buffer =
{
.vmo_id = i,
},
.type = SDMMC_BUFFER_TYPE_VMO_ID,
.offset = i * 4,
.size = 50,
};
}
sdmmc_req_new_t request = {
.cmd_idx = SDMMC_READ_MULTIPLE_BLOCK,
.cmd_flags = SDMMC_READ_MULTIPLE_BLOCK_FLAGS,
.arg = 0x1234abcd,
.blocksize = 50,
.suppress_error_messages = false,
.client_id = 0,
.buffers_list = buffers,
.buffers_count = countof(buffers),
};
uint32_t response[4] = {};
AmlSdmmcCmdResp::Get().FromValue(0xfedc9876).WriteTo(&mmio_);
EXPECT_OK(dut_->SdmmcRequestNew(&request, response));
EXPECT_EQ(response[0], 0xfedc9876);
const aml_sdmmc_desc_t* descs = dut_->descs();
auto expected_desc_cfg = AmlSdmmcCmdCfg::Get()
.FromValue(0)
.set_len(50)
.set_timeout(AmlSdmmcCmdCfg::kDefaultCmdTimeout)
.set_data_io(1)
.set_data_wr(0)
.set_resp_num(1)
.set_cmd_idx(SDMMC_READ_MULTIPLE_BLOCK)
.set_owner(1);
EXPECT_EQ(descs[0].cmd_info, expected_desc_cfg.reg_value());
EXPECT_EQ(descs[0].cmd_arg, 0x1234abcd);
EXPECT_EQ(descs[0].data_addr, zx_system_get_page_size());
EXPECT_EQ(descs[0].resp_addr, 0);
for (uint32_t i = 1; i < countof(buffers); i++) {
expected_desc_cfg.set_len(50).set_no_resp(1).set_no_cmd(1).set_resp_num(0).set_cmd_idx(0);
if (i == countof(buffers) - 1) {
expected_desc_cfg.set_end_of_chain(1);
}
EXPECT_EQ(descs[i].cmd_info, expected_desc_cfg.reg_value());
EXPECT_EQ(descs[i].cmd_arg, 0);
EXPECT_EQ(descs[i].data_addr, (i << 24) | (zx_system_get_page_size() + (i * 68)));
EXPECT_EQ(descs[i].resp_addr, 0);
}
}
TEST_F(AmlSdmmcTest, OwnedVmoByteModeMultiBlock) {
ASSERT_OK(dut_->Init({}));
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &vmo));
InitializeContiguousPaddrs(1);
EXPECT_OK(dut_->SdmmcRegisterVmo(1, 0, std::move(vmo), 0, 512, SDMMC_VMO_RIGHT_WRITE));
sdmmc_buffer_region_t buffer = {
.buffer =
{
.vmo_id = 1,
},
.type = SDMMC_BUFFER_TYPE_VMO_ID,
.offset = 0,
.size = 400,
};
sdmmc_req_new_t request = {
.cmd_idx = SDMMC_READ_MULTIPLE_BLOCK,
.cmd_flags = SDMMC_READ_MULTIPLE_BLOCK_FLAGS,
.arg = 0x1234abcd,
.blocksize = 100,
.suppress_error_messages = false,
.client_id = 0,
.buffers_list = &buffer,
.buffers_count = 1,
};
uint32_t response[4] = {};
AmlSdmmcCmdResp::Get().FromValue(0xfedc9876).WriteTo(&mmio_);
EXPECT_OK(dut_->SdmmcRequestNew(&request, response));
EXPECT_EQ(response[0], 0xfedc9876);
const aml_sdmmc_desc_t* descs = dut_->descs();
auto expected_desc_cfg = AmlSdmmcCmdCfg::Get()
.FromValue(0)
.set_len(100)
.set_timeout(AmlSdmmcCmdCfg::kDefaultCmdTimeout)
.set_data_io(1)
.set_data_wr(0)
.set_resp_num(1)
.set_cmd_idx(SDMMC_READ_MULTIPLE_BLOCK)
.set_owner(1);
EXPECT_EQ(descs[0].cmd_info, expected_desc_cfg.reg_value());
EXPECT_EQ(descs[0].cmd_arg, 0x1234abcd);
EXPECT_EQ(descs[0].data_addr, zx_system_get_page_size());
EXPECT_EQ(descs[0].resp_addr, 0);
for (uint32_t i = 1; i < 4; i++) {
expected_desc_cfg.set_no_resp(1).set_no_cmd(1).set_resp_num(0).set_cmd_idx(0);
if (i == 3) {
expected_desc_cfg.set_end_of_chain(1);
}
EXPECT_EQ(descs[i].cmd_info, expected_desc_cfg.reg_value());
EXPECT_EQ(descs[i].cmd_arg, 0);
EXPECT_EQ(descs[i].data_addr, zx_system_get_page_size() + (i * 100));
EXPECT_EQ(descs[i].resp_addr, 0);
}
}
TEST_F(AmlSdmmcTest, OwnedVmoOffsetNotAligned) {
ASSERT_OK(dut_->Init({}));
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &vmo));
InitializeContiguousPaddrs(1);
EXPECT_OK(dut_->SdmmcRegisterVmo(1, 0, std::move(vmo), 2, 512, SDMMC_VMO_RIGHT_WRITE));
sdmmc_buffer_region_t buffer = {
.buffer =
{
.vmo_id = 1,
},
.type = SDMMC_BUFFER_TYPE_VMO_ID,
.offset = 32,
.size = 64,
};
sdmmc_req_new_t request = {
.cmd_idx = SDMMC_READ_MULTIPLE_BLOCK,
.cmd_flags = SDMMC_READ_MULTIPLE_BLOCK_FLAGS,
.arg = 0x1234abcd,
.blocksize = 32,
.suppress_error_messages = false,
.client_id = 0,
.buffers_list = &buffer,
.buffers_count = 1,
};
uint32_t response[4] = {};
AmlSdmmcCmdResp::Get().FromValue(0xfedc9876).WriteTo(&mmio_);
EXPECT_NOT_OK(dut_->SdmmcRequestNew(&request, response));
}
TEST_F(AmlSdmmcTest, OwnedVmoSingleBufferMultipleDescriptors) {
ASSERT_OK(dut_->Init({}));
zx::vmo vmo;
const size_t pages = ((32 * 514) / zx_system_get_page_size()) + 1;
ASSERT_OK(zx::vmo::create(pages * zx_system_get_page_size(), 0, &vmo));
InitializeSingleVmoPaddrs(pages);
EXPECT_OK(dut_->SdmmcRegisterVmo(1, 0, std::move(vmo), 8, (pages * zx_system_get_page_size()) - 8,
SDMMC_VMO_RIGHT_WRITE));
sdmmc_buffer_region_t buffer = {
.buffer =
{
.vmo_id = 1,
},
.type = SDMMC_BUFFER_TYPE_VMO_ID,
.offset = 8,
.size = 32 * 513,
};
sdmmc_req_new_t request = {
.cmd_idx = SDMMC_READ_MULTIPLE_BLOCK,
.cmd_flags = SDMMC_READ_MULTIPLE_BLOCK_FLAGS,
.arg = 0x1234abcd,
.blocksize = 32,
.suppress_error_messages = false,
.client_id = 0,
.buffers_list = &buffer,
.buffers_count = 1,
};
uint32_t response[4] = {};
AmlSdmmcCmdResp::Get().FromValue(0xfedc9876).WriteTo(&mmio_);
EXPECT_OK(dut_->SdmmcRequestNew(&request, response));
EXPECT_EQ(response[0], 0xfedc9876);
const aml_sdmmc_desc_t* descs = dut_->descs();
auto expected_desc_cfg = AmlSdmmcCmdCfg::Get()
.FromValue(0)
.set_len(511)
.set_block_mode(1)
.set_timeout(AmlSdmmcCmdCfg::kDefaultCmdTimeout)
.set_data_io(1)
.set_data_wr(0)
.set_resp_num(1)
.set_cmd_idx(SDMMC_READ_MULTIPLE_BLOCK)
.set_owner(1);
EXPECT_EQ(descs[0].cmd_info, expected_desc_cfg.reg_value());
EXPECT_EQ(descs[0].cmd_arg, 0x1234abcd);
EXPECT_EQ(descs[0].data_addr, zx_system_get_page_size() + 16);
EXPECT_EQ(descs[0].resp_addr, 0);
expected_desc_cfg.set_len(1)
.set_len(2)
.set_end_of_chain(1)
.set_no_resp(1)
.set_no_cmd(1)
.set_resp_num(0)
.set_cmd_idx(0);
EXPECT_EQ(descs[1].cmd_info, expected_desc_cfg.reg_value());
EXPECT_EQ(descs[1].cmd_arg, 0);
EXPECT_EQ(descs[1].data_addr, zx_system_get_page_size() + (511 * 32) + 16);
EXPECT_EQ(descs[1].resp_addr, 0);
}
TEST_F(AmlSdmmcTest, OwnedVmoSingleBufferNotPageAligned) {
ASSERT_OK(dut_->Init({}));
zx::vmo vmo;
const size_t pages = ((32 * 514) / zx_system_get_page_size()) + 1;
ASSERT_OK(zx::vmo::create(pages * zx_system_get_page_size(), 0, &vmo));
InitializeNonContiguousPaddrs(pages);
EXPECT_OK(dut_->SdmmcRegisterVmo(1, 0, std::move(vmo), 8, (pages * zx_system_get_page_size()) - 8,
SDMMC_VMO_RIGHT_WRITE));
sdmmc_buffer_region_t buffer = {
.buffer =
{
.vmo = 1,
},
.type = SDMMC_BUFFER_TYPE_VMO_ID,
.offset = 8,
.size = 32 * 513,
};
sdmmc_req_new_t request = {
.cmd_idx = SDMMC_READ_MULTIPLE_BLOCK,
.cmd_flags = SDMMC_READ_MULTIPLE_BLOCK_FLAGS,
.arg = 0x1234abcd,
.blocksize = 32,
.suppress_error_messages = false,
.client_id = 0,
.buffers_list = &buffer,
.buffers_count = 1,
};
uint32_t response[4] = {};
AmlSdmmcCmdResp::Get().FromValue(0xfedc9876).WriteTo(&mmio_);
EXPECT_NOT_OK(dut_->SdmmcRequestNew(&request, response));
}
TEST_F(AmlSdmmcTest, OwnedVmoSingleBufferPageAligned) {
ASSERT_OK(dut_->Init({}));
zx::vmo vmo;
const size_t pages = ((32 * 514) / zx_system_get_page_size()) + 1;
ASSERT_OK(zx::vmo::create(pages * zx_system_get_page_size(), 0, &vmo));
InitializeNonContiguousPaddrs(pages);
EXPECT_OK(dut_->SdmmcRegisterVmo(
1, 0, std::move(vmo), 16, (pages * zx_system_get_page_size()) - 16, SDMMC_VMO_RIGHT_WRITE));
sdmmc_buffer_region_t buffer = {
.buffer =
{
.vmo = 1,
},
.type = SDMMC_BUFFER_TYPE_VMO_ID,
.offset = 16,
.size = 32 * 513,
};
sdmmc_req_new_t request = {
.cmd_idx = SDMMC_READ_MULTIPLE_BLOCK,
.cmd_flags = SDMMC_READ_MULTIPLE_BLOCK_FLAGS,
.arg = 0x1234abcd,
.blocksize = 32,
.suppress_error_messages = false,
.client_id = 0,
.buffers_list = &buffer,
.buffers_count = 1,
};
uint32_t response[4] = {};
AmlSdmmcCmdResp::Get().FromValue(0xfedc9876).WriteTo(&mmio_);
EXPECT_OK(dut_->SdmmcRequestNew(&request, response));
EXPECT_EQ(response[0], 0xfedc9876);
const aml_sdmmc_desc_t* descs = dut_->descs();
auto expected_desc_cfg = AmlSdmmcCmdCfg::Get()
.FromValue(0)
.set_len(127)
.set_block_mode(1)
.set_timeout(AmlSdmmcCmdCfg::kDefaultCmdTimeout)
.set_data_io(1)
.set_data_wr(0)
.set_resp_num(1)
.set_cmd_idx(SDMMC_READ_MULTIPLE_BLOCK)
.set_owner(1);
EXPECT_EQ(descs[0].cmd_info, expected_desc_cfg.reg_value());
EXPECT_EQ(descs[0].cmd_arg, 0x1234abcd);
EXPECT_EQ(descs[0].data_addr, (zx_system_get_page_size() * 2) + 32);
EXPECT_EQ(descs[0].resp_addr, 0);
for (uint32_t i = 1; i < 5; i++) {
expected_desc_cfg.set_len(128).set_no_resp(1).set_no_cmd(1).set_resp_num(0).set_cmd_idx(0);
if (i == 4) {
expected_desc_cfg.set_len(2).set_end_of_chain(1);
}
EXPECT_EQ(descs[i].cmd_info, expected_desc_cfg.reg_value());
EXPECT_EQ(descs[i].cmd_arg, 0);
EXPECT_EQ(descs[i].data_addr, zx_system_get_page_size() * (i + 1) * 2);
EXPECT_EQ(descs[i].resp_addr, 0);
}
}
TEST_F(AmlSdmmcTest, OwnedVmoWritePastEnd) {
ASSERT_OK(dut_->Init({}));
zx::vmo vmo;
const size_t pages = ((32 * 514) / zx_system_get_page_size()) + 1;
ASSERT_OK(zx::vmo::create(pages * zx_system_get_page_size(), 0, &vmo));
InitializeNonContiguousPaddrs(pages);
EXPECT_OK(dut_->SdmmcRegisterVmo(1, 0, std::move(vmo), 32, 32 * 384, SDMMC_VMO_RIGHT_WRITE));
sdmmc_buffer_region_t buffer = {
.buffer =
{
.vmo = 1,
},
.type = SDMMC_BUFFER_TYPE_VMO_ID,
.offset = 32,
.size = 32 * 383,
};
sdmmc_req_new_t request = {
.cmd_idx = SDMMC_READ_MULTIPLE_BLOCK,
.cmd_flags = SDMMC_READ_MULTIPLE_BLOCK_FLAGS,
.arg = 0x1234abcd,
.blocksize = 32,
.suppress_error_messages = false,
.client_id = 0,
.buffers_list = &buffer,
.buffers_count = 1,
};
uint32_t response[4] = {};
AmlSdmmcCmdResp::Get().FromValue(0xfedc9876).WriteTo(&mmio_);
EXPECT_OK(dut_->SdmmcRequestNew(&request, response));
EXPECT_EQ(response[0], 0xfedc9876);
const aml_sdmmc_desc_t* descs = dut_->descs();
auto expected_desc_cfg = AmlSdmmcCmdCfg::Get()
.FromValue(0)
.set_len(126)
.set_block_mode(1)
.set_timeout(AmlSdmmcCmdCfg::kDefaultCmdTimeout)
.set_data_io(1)
.set_data_wr(0)
.set_resp_num(1)
.set_cmd_idx(SDMMC_READ_MULTIPLE_BLOCK)
.set_owner(1);
EXPECT_EQ(descs[0].cmd_info, expected_desc_cfg.reg_value());
EXPECT_EQ(descs[0].cmd_arg, 0x1234abcd);
EXPECT_EQ(descs[0].data_addr, (zx_system_get_page_size() * 2) + 64);
EXPECT_EQ(descs[0].resp_addr, 0);
for (uint32_t i = 1; i < 4; i++) {
expected_desc_cfg.set_len(128).set_no_resp(1).set_no_cmd(1).set_resp_num(0).set_cmd_idx(0);
if (i == 3) {
expected_desc_cfg.set_len(1).set_end_of_chain(1);
}
EXPECT_EQ(descs[i].cmd_info, expected_desc_cfg.reg_value());
EXPECT_EQ(descs[i].cmd_arg, 0);
EXPECT_EQ(descs[i].data_addr, zx_system_get_page_size() * (i + 1) * 2);
EXPECT_EQ(descs[i].resp_addr, 0);
}
buffer.size = 32 * 384;
EXPECT_NOT_OK(dut_->SdmmcRequestNew(&request, response));
}
TEST_F(AmlSdmmcTest, SeparateClientVmoSpaces) {
ASSERT_OK(dut_->Init({}));
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &vmo));
const zx_koid_t vmo1_koid = GetVmoKoid(vmo);
EXPECT_NE(vmo1_koid, ZX_KOID_INVALID);
EXPECT_OK(dut_->SdmmcRegisterVmo(1, 0, std::move(vmo), 0, zx_system_get_page_size(),
SDMMC_VMO_RIGHT_WRITE));
ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &vmo));
const zx_koid_t vmo2_koid = GetVmoKoid(vmo);
EXPECT_NE(vmo2_koid, ZX_KOID_INVALID);
EXPECT_OK(dut_->SdmmcRegisterVmo(2, 0, std::move(vmo), 0, zx_system_get_page_size(),
SDMMC_VMO_RIGHT_WRITE));
ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &vmo));
EXPECT_NOT_OK(dut_->SdmmcRegisterVmo(1, 0, std::move(vmo), 0, zx_system_get_page_size(),
SDMMC_VMO_RIGHT_WRITE));
ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &vmo));
EXPECT_NOT_OK(dut_->SdmmcRegisterVmo(1, 8, std::move(vmo), 0, zx_system_get_page_size(),
SDMMC_VMO_RIGHT_WRITE));
ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &vmo));
const zx_koid_t vmo3_koid = GetVmoKoid(vmo);
EXPECT_NE(vmo3_koid, ZX_KOID_INVALID);
EXPECT_OK(dut_->SdmmcRegisterVmo(1, 1, std::move(vmo), 0, zx_system_get_page_size(),
SDMMC_VMO_RIGHT_WRITE));
EXPECT_OK(dut_->SdmmcUnregisterVmo(1, 0, &vmo));
EXPECT_EQ(GetVmoKoid(vmo), vmo1_koid);
EXPECT_OK(dut_->SdmmcUnregisterVmo(2, 0, &vmo));
EXPECT_EQ(GetVmoKoid(vmo), vmo2_koid);
EXPECT_OK(dut_->SdmmcUnregisterVmo(1, 1, &vmo));
EXPECT_EQ(GetVmoKoid(vmo), vmo3_koid);
EXPECT_NOT_OK(dut_->SdmmcUnregisterVmo(1, 0, &vmo));
EXPECT_NOT_OK(dut_->SdmmcUnregisterVmo(2, 0, &vmo));
EXPECT_NOT_OK(dut_->SdmmcUnregisterVmo(1, 1, &vmo));
}
TEST_F(AmlSdmmcTest, RequestWithOwnedAndUnownedVmos) {
ASSERT_OK(dut_->Init({}));
InitializeContiguousPaddrs(10);
zx::vmo vmos[5] = {};
sdmmc_buffer_region_t buffers[10];
for (uint32_t i = 0; i < 5; i++) {
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &vmo));
ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &vmos[i]));
EXPECT_OK(dut_->SdmmcRegisterVmo(i, 0, std::move(vmo), i * 64, 512, SDMMC_VMO_RIGHT_WRITE));
buffers[i * 2] = {
.buffer =
{
.vmo_id = i,
},
.type = SDMMC_BUFFER_TYPE_VMO_ID,
.offset = i * 16,
.size = 32 * (i + 2),
};
buffers[(i * 2) + 1] = {
.buffer =
{
.vmo = vmos[i].get(),
},
.type = SDMMC_BUFFER_TYPE_VMO_HANDLE,
.offset = i * 16,
.size = 32 * (i + 2),
};
}
zx::vmo vmo;
EXPECT_NOT_OK(dut_->SdmmcUnregisterVmo(3, 1, &vmo));
sdmmc_req_new_t request = {
.cmd_idx = SDMMC_READ_MULTIPLE_BLOCK,
.cmd_flags = SDMMC_READ_MULTIPLE_BLOCK_FLAGS,
.arg = 0x1234abcd,
.blocksize = 32,
.suppress_error_messages = false,
.client_id = 0,
.buffers_list = buffers,
.buffers_count = countof(buffers),
};
uint32_t response[4] = {};
AmlSdmmcCmdResp::Get().FromValue(0xfedc9876).WriteTo(&mmio_);
EXPECT_OK(dut_->SdmmcRequestNew(&request, response));
EXPECT_EQ(response[0], 0xfedc9876);
const aml_sdmmc_desc_t* descs = dut_->descs();
auto expected_desc_cfg = AmlSdmmcCmdCfg::Get()
.FromValue(0)
.set_len(2)
.set_block_mode(1)
.set_timeout(AmlSdmmcCmdCfg::kDefaultCmdTimeout)
.set_data_io(1)
.set_data_wr(0)
.set_resp_num(1)
.set_cmd_idx(SDMMC_READ_MULTIPLE_BLOCK)
.set_owner(1);
EXPECT_EQ(descs[0].cmd_info, expected_desc_cfg.reg_value());
EXPECT_EQ(descs[0].cmd_arg, 0x1234abcd);
EXPECT_EQ(descs[0].data_addr, zx_system_get_page_size());
EXPECT_EQ(descs[0].resp_addr, 0);
expected_desc_cfg.set_no_resp(1).set_no_cmd(1).set_resp_num(0).set_cmd_idx(0);
EXPECT_EQ(descs[1].cmd_info, expected_desc_cfg.reg_value());
EXPECT_EQ(descs[1].cmd_arg, 0);
EXPECT_EQ(descs[1].data_addr, (5 << 24) | zx_system_get_page_size());
EXPECT_EQ(descs[1].resp_addr, 0);
expected_desc_cfg.set_len(3);
EXPECT_EQ(descs[2].cmd_info, expected_desc_cfg.reg_value());
EXPECT_EQ(descs[2].cmd_arg, 0);
EXPECT_EQ(descs[2].data_addr, (1 << 24) | (zx_system_get_page_size() + 64 + 16));
EXPECT_EQ(descs[2].resp_addr, 0);
EXPECT_EQ(descs[3].cmd_info, expected_desc_cfg.reg_value());
EXPECT_EQ(descs[3].cmd_arg, 0);
EXPECT_EQ(descs[3].data_addr, (6 << 24) | (zx_system_get_page_size() + 16));
EXPECT_EQ(descs[3].resp_addr, 0);
expected_desc_cfg.set_len(4);
EXPECT_EQ(descs[4].cmd_info, expected_desc_cfg.reg_value());
EXPECT_EQ(descs[4].cmd_arg, 0);
EXPECT_EQ(descs[4].data_addr, (2 << 24) | (zx_system_get_page_size() + 128 + 32));
EXPECT_EQ(descs[4].resp_addr, 0);
EXPECT_EQ(descs[5].cmd_info, expected_desc_cfg.reg_value());
EXPECT_EQ(descs[5].cmd_arg, 0);
EXPECT_EQ(descs[5].data_addr, (7 << 24) | (zx_system_get_page_size() + 32));
EXPECT_EQ(descs[5].resp_addr, 0);
expected_desc_cfg.set_len(5);
EXPECT_EQ(descs[6].cmd_info, expected_desc_cfg.reg_value());
EXPECT_EQ(descs[6].cmd_arg, 0);
EXPECT_EQ(descs[6].data_addr, (3 << 24) | (zx_system_get_page_size() + 192 + 48));
EXPECT_EQ(descs[6].resp_addr, 0);
EXPECT_EQ(descs[7].cmd_info, expected_desc_cfg.reg_value());
EXPECT_EQ(descs[7].cmd_arg, 0);
EXPECT_EQ(descs[7].data_addr, (8 << 24) | (zx_system_get_page_size() + 48));
EXPECT_EQ(descs[7].resp_addr, 0);
expected_desc_cfg.set_len(6);
EXPECT_EQ(descs[8].cmd_info, expected_desc_cfg.reg_value());
EXPECT_EQ(descs[8].cmd_arg, 0);
EXPECT_EQ(descs[8].data_addr, (4 << 24) | (zx_system_get_page_size() + 256 + 64));
EXPECT_EQ(descs[8].resp_addr, 0);
expected_desc_cfg.set_end_of_chain(1);
EXPECT_EQ(descs[9].cmd_info, expected_desc_cfg.reg_value());
EXPECT_EQ(descs[9].cmd_arg, 0);
EXPECT_EQ(descs[9].data_addr, (9 << 24) | (zx_system_get_page_size() + 64));
EXPECT_EQ(descs[9].resp_addr, 0);
}
TEST_F(AmlSdmmcTest, ResetCmdInfoBits) {
ASSERT_OK(dut_->Init({}));
bti_paddrs_[1] = 0x1897'7000;
bti_paddrs_[2] = 0x1997'8000;
bti_paddrs_[3] = 0x1997'e000;
// Make sure the appropriate cmd_info bits get cleared.
dut_->descs()[0].cmd_info = 0xffff'ffff;
dut_->descs()[1].cmd_info = 0xffff'ffff;
dut_->descs()[2].cmd_info = 0xffff'ffff;
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(zx_system_get_page_size() * 3, 0, &vmo));
EXPECT_OK(dut_->SdmmcRegisterVmo(1, 2, std::move(vmo), 0, zx_system_get_page_size() * 3,
SDMMC_VMO_RIGHT_WRITE));
sdmmc_buffer_region_t buffer = {
.buffer = {.vmo_id = 1},
.type = SDMMC_BUFFER_TYPE_VMO_ID,
.offset = 0,
.size = 10752,
};
sdmmc_req_new_t request = {
.cmd_idx = SDIO_IO_RW_DIRECT_EXTENDED,
.cmd_flags = SDIO_IO_RW_DIRECT_EXTENDED_FLAGS | SDMMC_CMD_READ,
.arg = 0x29000015,
.blocksize = 512,
.suppress_error_messages = false,
.client_id = 2,
.buffers_list = &buffer,
.buffers_count = 1,
};
uint32_t response[4] = {};
AmlSdmmcCfg::Get().ReadFrom(&mmio_).set_blk_len(0).WriteTo(&mmio_);
EXPECT_OK(dut_->SdmmcRequestNew(&request, response));
EXPECT_EQ(AmlSdmmcCfg::Get().ReadFrom(&mmio_).blk_len(), 9);
const aml_sdmmc_desc_t* descs = dut_->descs();
auto expected_desc_cfg = AmlSdmmcCmdCfg::Get()
.FromValue(0)
.set_len(8)
.set_block_mode(1)
.set_timeout(AmlSdmmcCmdCfg::kDefaultCmdTimeout)
.set_data_io(1)
.set_data_wr(0)
.set_resp_num(1)
.set_cmd_idx(SDIO_IO_RW_DIRECT_EXTENDED)
.set_owner(1);
EXPECT_EQ(descs[0].cmd_info, expected_desc_cfg.reg_value());
EXPECT_EQ(descs[0].cmd_arg, 0x29000015);
EXPECT_EQ(descs[0].data_addr, 0x1897'7000);
EXPECT_EQ(descs[0].resp_addr, 0);
expected_desc_cfg.set_no_resp(1).set_no_cmd(1).set_resp_num(0).set_cmd_idx(0);
EXPECT_EQ(descs[1].cmd_info, expected_desc_cfg.reg_value());
EXPECT_EQ(descs[1].cmd_arg, 0);
EXPECT_EQ(descs[1].data_addr, 0x1997'8000);
EXPECT_EQ(descs[1].resp_addr, 0);
expected_desc_cfg.set_len(5).set_end_of_chain(1);
EXPECT_EQ(descs[2].cmd_info, expected_desc_cfg.reg_value());
EXPECT_EQ(descs[2].cmd_arg, 0);
EXPECT_EQ(descs[2].data_addr, 0x1997'e000);
EXPECT_EQ(descs[2].resp_addr, 0);
}
TEST_F(AmlSdmmcTest, WriteToReadOnlyVmo) {
ASSERT_OK(dut_->Init({}));
InitializeContiguousPaddrs(10);
sdmmc_buffer_region_t buffers[10];
for (uint32_t i = 0; i < countof(buffers); i++) {
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &vmo));
const uint32_t vmo_rights = SDMMC_VMO_RIGHT_READ | (i == 5 ? 0 : SDMMC_VMO_RIGHT_WRITE);
EXPECT_OK(dut_->SdmmcRegisterVmo(i, 0, std::move(vmo), i * 64, 512, vmo_rights));
buffers[i] = {
.buffer =
{
.vmo_id = i,
},
.type = SDMMC_BUFFER_TYPE_VMO_ID,
.offset = 0,
.size = 32 * (i + 2),
};
}
sdmmc_req_new_t request = {
.cmd_idx = SDIO_IO_RW_DIRECT_EXTENDED,
.cmd_flags = SDIO_IO_RW_DIRECT_EXTENDED_FLAGS | SDMMC_CMD_READ,
.arg = 0x29000015,
.blocksize = 32,
.suppress_error_messages = false,
.client_id = 0,
.buffers_list = buffers,
.buffers_count = countof(buffers),
};
uint32_t response[4] = {};
EXPECT_NOT_OK(dut_->SdmmcRequestNew(&request, response));
}
TEST_F(AmlSdmmcTest, ReadFromWriteOnlyVmo) {
ASSERT_OK(dut_->Init({}));
InitializeContiguousPaddrs(10);
sdmmc_buffer_region_t buffers[10];
for (uint32_t i = 0; i < countof(buffers); i++) {
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &vmo));
const uint32_t vmo_rights = SDMMC_VMO_RIGHT_WRITE | (i == 5 ? 0 : SDMMC_VMO_RIGHT_READ);
EXPECT_OK(dut_->SdmmcRegisterVmo(i, 0, std::move(vmo), i * 64, 512, vmo_rights));
buffers[i] = {
.buffer =
{
.vmo_id = i,
},
.type = SDMMC_BUFFER_TYPE_VMO_ID,
.offset = 0,
.size = 32 * (i + 2),
};
}
sdmmc_req_new_t request = {
.cmd_idx = SDIO_IO_RW_DIRECT_EXTENDED,
.cmd_flags = SDIO_IO_RW_DIRECT_EXTENDED_FLAGS,
.arg = 0x29000015,
.blocksize = 32,
.suppress_error_messages = false,
.client_id = 0,
.buffers_list = buffers,
.buffers_count = countof(buffers),
};
uint32_t response[4] = {};
EXPECT_NOT_OK(dut_->SdmmcRequestNew(&request, response));
}
TEST_F(AmlSdmmcTest, ConsecutiveErrorLogging) {
ASSERT_OK(dut_->Init({}));
// First data error.
dut_->SetRequestInterruptStatus(1 << 8);
sdmmc_req_t request;
memset(&request, 0, sizeof(request));
EXPECT_EQ(ZX_ERR_IO_DATA_INTEGRITY, dut_->SdmmcRequest(&request));
// First cmd error.
dut_->SetRequestInterruptStatus(1 << 11);
memset(&request, 0, sizeof(request));
EXPECT_EQ(ZX_ERR_TIMED_OUT, dut_->SdmmcRequest(&request));
// Second data error.
dut_->SetRequestInterruptStatus(1 << 7);
memset(&request, 0, sizeof(request));
EXPECT_EQ(ZX_ERR_IO_DATA_INTEGRITY, dut_->SdmmcRequest(&request));
// Second cmd error.
dut_->SetRequestInterruptStatus(1 << 11);
memset(&request, 0, sizeof(request));
EXPECT_EQ(ZX_ERR_TIMED_OUT, dut_->SdmmcRequest(&request));
// cmd/data goes through.
dut_->SetRequestInterruptStatus(1 << 13);
memset(&request, 0, sizeof(request));
request.cmd_flags = SDMMC_RESP_DATA_PRESENT; // Must be set to clear the data error count.
request.blocksize = 32;
request.blockcount = 1;
request.use_dma = false;
uint8_t buffer[32];
request.virt_buffer = buffer;
request.virt_size = sizeof(buffer);
EXPECT_EQ(ZX_OK, dut_->SdmmcRequest(&request));
// Third data error.
dut_->SetRequestInterruptStatus(1 << 7);
memset(&request, 0, sizeof(request));
EXPECT_EQ(ZX_ERR_IO_DATA_INTEGRITY, dut_->SdmmcRequest(&request));
// Third cmd error.
dut_->SetRequestInterruptStatus(1 << 11);
memset(&request, 0, sizeof(request));
EXPECT_EQ(ZX_ERR_TIMED_OUT, dut_->SdmmcRequest(&request));
}
} // namespace sdmmc