| // 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/mmio-ptr/fake.h> |
| #include <lib/sync/completion.h> |
| |
| #include <atomic> |
| #include <memory> |
| #include <optional> |
| #include <vector> |
| |
| #include <zxtest/zxtest.h> |
| |
| #include "src/devices/lib/mmio/test-helper.h" |
| #include "src/devices/testing/mock-ddk/mock-device.h" |
| |
| // Stub out vmo_op_range to allow tests to use fake VMOs. |
| __EXPORT |
| zx_status_t zx_vmo_op_range(zx_handle_t handle, uint32_t op, uint64_t offset, uint64_t size, |
| void* buffer, size_t buffer_size) { |
| return ZX_OK; |
| } |
| |
| namespace { |
| |
| zx_paddr_t PageMask() { return static_cast<uintptr_t>(zx_system_get_page_size()) - 1; } |
| |
| } // namespace |
| |
| namespace sdhci { |
| |
| class TestSdhci : public Sdhci { |
| public: |
| TestSdhci(zx_device_t* parent, fdf::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) { return ZX_ERR_NOT_SUPPORTED; } |
| |
| zx_status_t SdmmcRequest(const sdmmc_req_t* req, uint32_t out_response[4]) { |
| cpp20::span<const sdmmc_buffer_region_t> buffers(req->buffers_list, req->buffers_count); |
| size_t bytes = 0; |
| for (const sdmmc_buffer_region_t& buffer : buffers) { |
| bytes += buffer.size; |
| } |
| blocks_remaining_ = req->blocksize ? static_cast<uint16_t>(bytes / req->blocksize) : 0; |
| current_block_ = 0; |
| return Sdhci::SdmmcRequest(req, out_response); |
| } |
| |
| 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 InjectTransferError() { inject_error_ = true; } |
| |
| 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(®s_mmio_buffer_); |
| |
| while (run_thread_) { |
| switch (GetRequestStatus()) { |
| case RequestStatus::COMMAND: |
| status.set_command_complete(1).WriteTo(®s_mmio_buffer_); |
| return ZX_OK; |
| case RequestStatus::TRANSFER_DATA_DMA: |
| status.set_transfer_complete(1); |
| if (inject_error_) { |
| status.set_error(1).set_data_crc_error(1); |
| } |
| status.WriteTo(®s_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(®s_mmio_buffer_); |
| } else { |
| status.set_buffer_read_ready(1).WriteTo(®s_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(®s_mmio_buffer_); |
| } else { |
| status.set_buffer_write_ready(1).WriteTo(®s_mmio_buffer_); |
| } |
| return ZX_OK; |
| case RequestStatus::BUSY_RESPONSE: |
| status.set_transfer_complete(1).WriteTo(®s_mmio_buffer_); |
| return ZX_OK; |
| default: |
| break; |
| } |
| |
| if (card_interrupt_.exchange(false) && |
| InterruptStatusEnable::Get().ReadFrom(®s_mmio_buffer_).card_interrupt() == 1) { |
| status.set_card_interrupt(1).WriteTo(®s_mmio_buffer_); |
| return ZX_OK; |
| } |
| } |
| |
| return ZX_ERR_CANCELED; |
| } |
| |
| 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::atomic<bool> inject_error_ = false; |
| }; |
| |
| class SdhciTest : public zxtest::Test { |
| public: |
| SdhciTest() : mmio_(fdf_testing::CreateMmioBuffer(kRegisterSetSize)) {} |
| |
| protected: |
| void SetUp() { root_ = MockDevice::FakeRootParent(); } |
| |
| void CreateDut(std::vector<zx_paddr_t> dma_paddrs, uint64_t quirks = 0, |
| uint64_t dma_boundary_alignment = 0) { |
| zx::bti fake_bti; |
| dma_paddrs_ = std::move(dma_paddrs); |
| ASSERT_OK(fake_bti_create_with_paddrs(dma_paddrs_.data(), dma_paddrs_.size(), |
| fake_bti.reset_and_get_address())); |
| |
| bti_ = fake_bti.borrow(); |
| dut_ = new TestSdhci(root_.get(), mmio_.View(0), std::move(fake_bti), |
| ddk::SdhciProtocolClient(mock_sdhci_.GetProto()), quirks, |
| dma_boundary_alignment); |
| |
| dut_->DdkAdd("sdhci"); |
| HostControllerVersion::Get() |
| .FromValue(0) |
| .set_specification_version(HostControllerVersion::kSpecificationVersion300) |
| .WriteTo(&mmio_); |
| ClockControl::Get().FromValue(0).set_internal_clock_stable(1).WriteTo(&mmio_); |
| } |
| |
| void CreateDut(uint64_t quirks = 0, uint64_t dma_boundary_alignment = 0) { |
| CreateDut({}, quirks, dma_boundary_alignment); |
| } |
| |
| void ExpectPmoCount(uint64_t count) { |
| zx_info_bti_t bti_info; |
| EXPECT_OK(bti_->get_info(ZX_INFO_BTI, &bti_info, sizeof(bti_info), nullptr, nullptr)); |
| EXPECT_EQ(bti_info.pmo_count, count); |
| } |
| |
| ddk::MockSdhci mock_sdhci_; |
| zx::interrupt irq_; |
| std::vector<zx_paddr_t> dma_paddrs_; |
| std::shared_ptr<MockDevice> root_; |
| TestSdhci* dut_; // Managed by root_. |
| fdf::MmioBuffer mmio_; |
| zx::unowned_bti bti_; |
| }; |
| |
| TEST_F(SdhciTest, DdkLifecycle) { |
| ASSERT_NO_FATAL_FAILURE(CreateDut()); |
| mock_sdhci_.ExpectGetBaseClock(100'000'000); |
| EXPECT_OK(dut_->Init()); // Not DdkInit. |
| dut_->DdkUnbind(ddk::UnbindTxn(dut_->zxdev())); |
| root_->GetLatestChild()->WaitUntilUnbindReplyCalled(); |
| } |
| |
| TEST_F(SdhciTest, BaseClockZero) { |
| ASSERT_NO_FATAL_FAILURE(CreateDut()); |
| |
| mock_sdhci_.ExpectGetBaseClock(0); |
| EXPECT_NOT_OK(dut_->Init()); |
| } |
| |
| TEST_F(SdhciTest, BaseClockFromDriver) { |
| ASSERT_NO_FATAL_FAILURE(CreateDut()); |
| |
| mock_sdhci_.ExpectGetBaseClock(0xabcdef); |
| EXPECT_OK(dut_->Init()); |
| dut_->DdkUnbind(ddk::UnbindTxn(dut_->zxdev())); |
| |
| EXPECT_EQ(dut_->base_clock(), 0xabcdef); |
| } |
| |
| TEST_F(SdhciTest, BaseClockFromHardware) { |
| ASSERT_NO_FATAL_FAILURE(CreateDut()); |
| |
| Capabilities0::Get().FromValue(0).set_base_clock_frequency(104).WriteTo(&mmio_); |
| EXPECT_OK(dut_->Init()); |
| dut_->DdkUnbind(ddk::UnbindTxn(dut_->zxdev())); |
| |
| EXPECT_EQ(dut_->base_clock(), 104'000'000); |
| } |
| |
| TEST_F(SdhciTest, HostInfo) { |
| ASSERT_NO_FATAL_FAILURE(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(dut_->zxdev())); |
| |
| 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); |
| } |
| |
| TEST_F(SdhciTest, HostInfoNoDma) { |
| ASSERT_NO_FATAL_FAILURE(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(dut_->zxdev())); |
| |
| 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); |
| } |
| |
| TEST_F(SdhciTest, HostInfoNoTuning) { |
| ASSERT_NO_FATAL_FAILURE(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(dut_->zxdev())); |
| |
| 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); |
| } |
| |
| TEST_F(SdhciTest, SetSignalVoltage) { |
| ASSERT_NO_FATAL_FAILURE(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(dut_->zxdev())); |
| |
| 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_FAILURE(CreateDut()); |
| |
| EXPECT_NOT_OK(dut_->SdmmcSetSignalVoltage(SDMMC_VOLTAGE_V330)); |
| } |
| |
| TEST_F(SdhciTest, SetBusWidth) { |
| ASSERT_NO_FATAL_FAILURE(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(dut_->zxdev())); |
| |
| 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_FAILURE(CreateDut()); |
| |
| EXPECT_NOT_OK(dut_->SdmmcSetBusWidth(SDMMC_BUS_WIDTH_EIGHT)); |
| } |
| |
| TEST_F(SdhciTest, SetBusFreq) { |
| ASSERT_NO_FATAL_FAILURE(CreateDut()); |
| |
| mock_sdhci_.ExpectGetBaseClock(100'000'000); |
| EXPECT_OK(dut_->Init()); |
| dut_->DdkUnbind(ddk::UnbindTxn(dut_->zxdev())); |
| |
| 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_FAILURE(CreateDut()); |
| |
| mock_sdhci_.ExpectGetBaseClock(100'000'000); |
| EXPECT_OK(dut_->Init()); |
| dut_->DdkUnbind(ddk::UnbindTxn(dut_->zxdev())); |
| |
| 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_FAILURE(CreateDut()); |
| |
| mock_sdhci_.ExpectGetBaseClock(100'000'000); |
| EXPECT_OK(dut_->Init()); |
| dut_->DdkUnbind(ddk::UnbindTxn(dut_->zxdev())); |
| |
| 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_FAILURE(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_FAILURE(CreateDut()); |
| |
| mock_sdhci_.ExpectHwReset(); |
| dut_->SdmmcHwReset(); |
| ASSERT_NO_FATAL_FAILURE(mock_sdhci_.VerifyAndClear()); |
| } |
| |
| TEST_F(SdhciTest, RequestCommandOnly) { |
| ASSERT_NO_FATAL_FAILURE(CreateDut()); |
| |
| mock_sdhci_.ExpectGetBaseClock(100'000'000); |
| Capabilities0::Get().FromValue(0).set_adma2_support(1).WriteTo(&mmio_); |
| EXPECT_OK(dut_->Init()); |
| |
| sdmmc_req_t request = { |
| .cmd_idx = SDMMC_SEND_STATUS, |
| .cmd_flags = SDMMC_SEND_STATUS_FLAGS, |
| .arg = 0x7b7d9fbd, |
| .buffers_count = 0, |
| }; |
| |
| Response::Get(0).FromValue(0xf3bbf2c0).WriteTo(&mmio_); |
| uint32_t response[4] = {}; |
| EXPECT_OK(dut_->SdmmcRequest(&request, response)); |
| |
| 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_EQ(response[0], 0xf3bbf2c0); |
| |
| request = { |
| .cmd_idx = SDMMC_SEND_CSD, |
| .cmd_flags = SDMMC_SEND_CSD_FLAGS, |
| .arg = 0x9c1dc1ed, |
| .buffers_count = 0, |
| }; |
| |
| 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, response)); |
| |
| 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_EQ(response[0], 0x9f93b17d); |
| EXPECT_EQ(response[1], 0x89aaba9e); |
| EXPECT_EQ(response[2], 0xc14b059e); |
| EXPECT_EQ(response[3], 0x7329a9e3); |
| |
| dut_->DdkUnbind(ddk::UnbindTxn(dut_->zxdev())); |
| } |
| |
| TEST_F(SdhciTest, RequestAbort) { |
| ASSERT_NO_FATAL_FAILURE(CreateDut()); |
| |
| mock_sdhci_.ExpectGetBaseClock(100'000'000); |
| Capabilities0::Get().FromValue(0).set_adma2_support(1).WriteTo(&mmio_); |
| EXPECT_OK(dut_->Init()); |
| |
| zx::vmo vmo; |
| ASSERT_OK(zx::vmo::create(1024, 0, &vmo)); |
| |
| const sdmmc_buffer_region_t buffer = { |
| .buffer = |
| { |
| .vmo = vmo.get(), |
| }, |
| .type = SDMMC_BUFFER_TYPE_VMO_HANDLE, |
| .offset = 0, |
| .size = 1024, |
| }; |
| |
| sdmmc_req_t request = { |
| .cmd_idx = SDMMC_WRITE_MULTIPLE_BLOCK, |
| .cmd_flags = SDMMC_WRITE_MULTIPLE_BLOCK_FLAGS, |
| .arg = 0, |
| .blocksize = 4, |
| .buffers_list = &buffer, |
| .buffers_count = 1, |
| }; |
| |
| dut_->reset_mask(); |
| |
| uint32_t unused_response[4]; |
| EXPECT_OK(dut_->SdmmcRequest(&request, unused_response)); |
| EXPECT_EQ(dut_->reset_mask(), 0); |
| |
| request.cmd_idx = SDMMC_STOP_TRANSMISSION; |
| request.cmd_flags = SDMMC_STOP_TRANSMISSION_FLAGS; |
| request.blocksize = 0; |
| request.buffers_list = nullptr; |
| request.buffers_count = 0; |
| EXPECT_OK(dut_->SdmmcRequest(&request, unused_response)); |
| EXPECT_EQ(dut_->reset_mask(), |
| SoftwareReset::Get().FromValue(0).set_reset_dat(1).set_reset_cmd(1).reg_value()); |
| |
| dut_->DdkUnbind(ddk::UnbindTxn(dut_->zxdev())); |
| } |
| |
| TEST_F(SdhciTest, SdioInBandInterrupt) { |
| ASSERT_NO_FATAL_FAILURE(CreateDut()); |
| |
| mock_sdhci_.ExpectGetBaseClock(100'000'000); |
| Capabilities0::Get().FromValue(0).set_adma2_support(1).WriteTo(&mmio_); |
| 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); |
| sync_completion_reset(&callback_called); |
| |
| sdmmc_req_t request = { |
| .cmd_idx = SDMMC_SEND_CSD, |
| .cmd_flags = SDMMC_SEND_CSD_FLAGS, |
| .arg = 0x9c1dc1ed, |
| .buffers_count = 0, |
| }; |
| uint32_t unused_response[4]; |
| EXPECT_OK(dut_->SdmmcRequest(&request, unused_response)); |
| |
| dut_->SdmmcAckInBandInterrupt(); |
| |
| // Verify that the card interrupt remains enabled after other interrupts have been disabled, such |
| // as after a commend. |
| dut_->TriggerCardInterrupt(); |
| sync_completion_wait(&callback_called, ZX_TIME_INFINITE); |
| |
| dut_->DdkUnbind(ddk::UnbindTxn(dut_->zxdev())); |
| } |
| |
| TEST_F(SdhciTest, DmaRequest64Bit) { |
| ASSERT_NO_FATAL_FAILURE(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()); |
| |
| for (int i = 0; i < 4; i++) { |
| zx::vmo vmo; |
| ASSERT_OK(zx::vmo::create(512 * 16, 0, &vmo)); |
| EXPECT_OK(dut_->SdmmcRegisterVmo(i, 3, std::move(vmo), 64 * i, 512 * 12, SDMMC_VMO_RIGHT_READ)); |
| } |
| |
| const sdmmc_buffer_region_t buffers[4] = { |
| { |
| .buffer = |
| { |
| .vmo_id = 1, |
| }, |
| .type = SDMMC_BUFFER_TYPE_VMO_ID, |
| .offset = 16, |
| .size = 512, |
| }, |
| { |
| .buffer = |
| { |
| .vmo_id = 0, |
| }, |
| .type = SDMMC_BUFFER_TYPE_VMO_ID, |
| .offset = 32, |
| .size = 512 * 3, |
| }, |
| { |
| .buffer = |
| { |
| .vmo_id = 3, |
| }, |
| .type = SDMMC_BUFFER_TYPE_VMO_ID, |
| .offset = 48, |
| .size = 512 * 10, |
| }, |
| { |
| .buffer = |
| { |
| .vmo_id = 2, |
| }, |
| .type = SDMMC_BUFFER_TYPE_VMO_ID, |
| .offset = 80, |
| .size = 512 * 7, |
| }, |
| }; |
| |
| const sdmmc_req_t request = { |
| .cmd_idx = SDMMC_WRITE_MULTIPLE_BLOCK, |
| .cmd_flags = SDMMC_WRITE_MULTIPLE_BLOCK_FLAGS, |
| .arg = 0x1234abcd, |
| .blocksize = 512, |
| .suppress_error_messages = false, |
| .client_id = 3, |
| .buffers_list = buffers, |
| .buffers_count = std::size(buffers), |
| }; |
| uint32_t response[4] = {}; |
| EXPECT_OK(dut_->SdmmcRequest(&request, response)); |
| |
| EXPECT_EQ(AdmaSystemAddress::Get(0).ReadFrom(&mmio_).reg_value(), zx_system_get_page_size()); |
| EXPECT_EQ(AdmaSystemAddress::Get(1).ReadFrom(&mmio_).reg_value(), 0); |
| |
| const auto* 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, zx_system_get_page_size() + 80); |
| EXPECT_EQ(descriptors[0].length, 512); |
| |
| memcpy(&address, &descriptors[1].address, sizeof(address)); |
| EXPECT_EQ(descriptors[1].attr, 0b100'001); |
| EXPECT_EQ(address, zx_system_get_page_size() + 32); |
| EXPECT_EQ(descriptors[1].length, 512 * 3); |
| |
| // Buffer is greater than one page and gets split across two descriptors. |
| memcpy(&address, &descriptors[2].address, sizeof(address)); |
| EXPECT_EQ(descriptors[2].attr, 0b100'001); |
| EXPECT_EQ(address, zx_system_get_page_size() + 240); |
| EXPECT_EQ(descriptors[2].length, zx_system_get_page_size() - 240); |
| |
| memcpy(&address, &descriptors[3].address, sizeof(address)); |
| EXPECT_EQ(descriptors[3].attr, 0b100'001); |
| EXPECT_EQ(address, zx_system_get_page_size()); |
| EXPECT_EQ(descriptors[3].length, (512 * 10) - zx_system_get_page_size() + 240); |
| |
| memcpy(&address, &descriptors[4].address, sizeof(address)); |
| EXPECT_EQ(descriptors[4].attr, 0b100'011); |
| EXPECT_EQ(address, zx_system_get_page_size() + 208); |
| EXPECT_EQ(descriptors[4].length, 512 * 7); |
| |
| dut_->DdkUnbind(ddk::UnbindTxn(dut_->zxdev())); |
| } |
| |
| TEST_F(SdhciTest, DmaRequest32Bit) { |
| ASSERT_NO_FATAL_FAILURE(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()); |
| |
| for (int i = 0; i < 4; i++) { |
| zx::vmo vmo; |
| ASSERT_OK(zx::vmo::create(512 * 16, 0, &vmo)); |
| EXPECT_OK( |
| dut_->SdmmcRegisterVmo(i, 3, std::move(vmo), 64 * i, 512 * 12, SDMMC_VMO_RIGHT_WRITE)); |
| } |
| |
| const sdmmc_buffer_region_t buffers[4] = { |
| { |
| .buffer = |
| { |
| .vmo_id = 1, |
| }, |
| .type = SDMMC_BUFFER_TYPE_VMO_ID, |
| .offset = 16, |
| .size = 512, |
| }, |
| { |
| .buffer = |
| { |
| .vmo_id = 0, |
| }, |
| .type = SDMMC_BUFFER_TYPE_VMO_ID, |
| .offset = 32, |
| .size = 512 * 3, |
| }, |
| { |
| .buffer = |
| { |
| .vmo_id = 3, |
| }, |
| .type = SDMMC_BUFFER_TYPE_VMO_ID, |
| .offset = 48, |
| .size = 512 * 10, |
| }, |
| { |
| .buffer = |
| { |
| .vmo_id = 2, |
| }, |
| .type = SDMMC_BUFFER_TYPE_VMO_ID, |
| .offset = 80, |
| .size = 512 * 7, |
| }, |
| }; |
| |
| const sdmmc_req_t request = { |
| .cmd_idx = SDMMC_READ_MULTIPLE_BLOCK, |
| .cmd_flags = SDMMC_READ_MULTIPLE_BLOCK_FLAGS, |
| .arg = 0x1234abcd, |
| .blocksize = 512, |
| .suppress_error_messages = false, |
| .client_id = 3, |
| .buffers_list = buffers, |
| .buffers_count = std::size(buffers), |
| }; |
| uint32_t response[4] = {}; |
| EXPECT_OK(dut_->SdmmcRequest(&request, response)); |
| |
| EXPECT_EQ(AdmaSystemAddress::Get(0).ReadFrom(&mmio_).reg_value(), zx_system_get_page_size()); |
| EXPECT_EQ(AdmaSystemAddress::Get(1).ReadFrom(&mmio_).reg_value(), 0); |
| |
| const auto* const descriptors = reinterpret_cast<Sdhci::AdmaDescriptor64*>(dut_->iobuf_virt()); |
| |
| EXPECT_EQ(descriptors[0].attr, 0b100'001); |
| EXPECT_EQ(descriptors[0].address, zx_system_get_page_size() + 80); |
| EXPECT_EQ(descriptors[0].length, 512); |
| |
| EXPECT_EQ(descriptors[1].attr, 0b100'001); |
| EXPECT_EQ(descriptors[1].address, zx_system_get_page_size() + 32); |
| EXPECT_EQ(descriptors[1].length, 512 * 3); |
| |
| // Buffer is greater than one page and gets split across two descriptors. |
| EXPECT_EQ(descriptors[2].attr, 0b100'001); |
| EXPECT_EQ(descriptors[2].address, zx_system_get_page_size() + 240); |
| EXPECT_EQ(descriptors[2].length, zx_system_get_page_size() - 240); |
| |
| EXPECT_EQ(descriptors[3].attr, 0b100'001); |
| EXPECT_EQ(descriptors[3].address, zx_system_get_page_size()); |
| EXPECT_EQ(descriptors[3].length, (512 * 10) - zx_system_get_page_size() + 240); |
| |
| EXPECT_EQ(descriptors[4].attr, 0b100'011); |
| EXPECT_EQ(descriptors[4].address, zx_system_get_page_size() + 208); |
| EXPECT_EQ(descriptors[4].length, 512 * 7); |
| |
| dut_->DdkUnbind(ddk::UnbindTxn(dut_->zxdev())); |
| } |
| |
| TEST_F(SdhciTest, DmaSplitOneBoundary) { |
| constexpr zx_paddr_t kDescriptorAddress = 0xc000'0000; |
| const zx_paddr_t kStartAddress = 0xa7ff'ffff & ~PageMask(); |
| |
| ASSERT_NO_FATAL_FAILURE(CreateDut( |
| { |
| kDescriptorAddress, |
| kStartAddress, |
| kStartAddress + zx_system_get_page_size(), |
| kStartAddress + (zx_system_get_page_size() * 2), |
| 0xb000'0000, |
| }, |
| 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()); |
| |
| zx::vmo vmo; |
| ASSERT_OK(zx::vmo::create(zx_system_get_page_size() * 4, 0, &vmo)); |
| ASSERT_OK(dut_->SdmmcRegisterVmo(0, 0, std::move(vmo), 0, zx_system_get_page_size() * 4, |
| SDMMC_VMO_RIGHT_WRITE)); |
| |
| const sdmmc_buffer_region_t buffer = { |
| .buffer = |
| { |
| .vmo_id = 0, |
| }, |
| .type = SDMMC_BUFFER_TYPE_VMO_ID, |
| // The first buffer should be split across the 128M boundary. |
| .offset = zx_system_get_page_size() - 4, |
| // Two pages plus 256 bytes. |
| .size = (zx_system_get_page_size() * 2) + 256, |
| }; |
| |
| const sdmmc_req_t request = { |
| .cmd_idx = SDMMC_READ_MULTIPLE_BLOCK, |
| .cmd_flags = SDMMC_READ_MULTIPLE_BLOCK_FLAGS, |
| .arg = 0x1234abcd, |
| .blocksize = 16, |
| .suppress_error_messages = false, |
| .client_id = 0, |
| .buffers_list = &buffer, |
| .buffers_count = 1, |
| }; |
| uint32_t response[4] = {}; |
| EXPECT_OK(dut_->SdmmcRequest(&request, response)); |
| |
| EXPECT_EQ(AdmaSystemAddress::Get(0).ReadFrom(&mmio_).reg_value(), kDescriptorAddress); |
| 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, zx_system_get_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(dut_->zxdev())); |
| } |
| |
| TEST_F(SdhciTest, DmaSplitManyBoundaries) { |
| constexpr zx_paddr_t kDescriptorAddress = 0xc000'0000; |
| ASSERT_NO_FATAL_FAILURE(CreateDut( |
| { |
| kDescriptorAddress, |
| 0xabcd'0000, |
| }, |
| 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()); |
| |
| zx::vmo vmo; |
| ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &vmo)); |
| ASSERT_OK(dut_->SdmmcRegisterVmo(0, 0, std::move(vmo), 0, zx_system_get_page_size(), |
| SDMMC_VMO_RIGHT_WRITE)); |
| |
| const sdmmc_buffer_region_t buffer = { |
| .buffer = |
| { |
| .vmo_id = 0, |
| }, |
| .type = SDMMC_BUFFER_TYPE_VMO_ID, |
| .offset = 128, |
| .size = 16 * 64, |
| }; |
| |
| const sdmmc_req_t request = { |
| .cmd_idx = SDMMC_READ_MULTIPLE_BLOCK, |
| .cmd_flags = SDMMC_READ_MULTIPLE_BLOCK_FLAGS, |
| .arg = 0x1234abcd, |
| .blocksize = 16, |
| .suppress_error_messages = false, |
| .client_id = 0, |
| .buffers_list = &buffer, |
| .buffers_count = 1, |
| }; |
| uint32_t response[4] = {}; |
| EXPECT_OK(dut_->SdmmcRequest(&request, response)); |
| |
| EXPECT_EQ(AdmaSystemAddress::Get(0).ReadFrom(&mmio_).reg_value(), kDescriptorAddress); |
| 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(dut_->zxdev())); |
| } |
| |
| TEST_F(SdhciTest, DmaNoBoundaries) { |
| constexpr zx_paddr_t kDescriptorAddress = 0xc000'0000; |
| const zx_paddr_t kStartAddress = 0xa7ff'ffff & ~PageMask(); |
| |
| ASSERT_NO_FATAL_FAILURE(CreateDut({ |
| kDescriptorAddress, |
| kStartAddress, |
| kStartAddress + zx_system_get_page_size(), |
| kStartAddress + (zx_system_get_page_size() * 2), |
| 0xb000'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()); |
| |
| zx::vmo vmo; |
| ASSERT_OK(zx::vmo::create(zx_system_get_page_size() * 4, 0, &vmo)); |
| ASSERT_OK(dut_->SdmmcRegisterVmo(0, 0, std::move(vmo), 0, zx_system_get_page_size() * 4, |
| SDMMC_VMO_RIGHT_WRITE)); |
| |
| const sdmmc_buffer_region_t buffer = { |
| .buffer = |
| { |
| .vmo_id = 0, |
| }, |
| .type = SDMMC_BUFFER_TYPE_VMO_ID, |
| .offset = zx_system_get_page_size() - 4, |
| .size = (zx_system_get_page_size() * 2) + 256, |
| }; |
| |
| const sdmmc_req_t request = { |
| .cmd_idx = SDMMC_READ_MULTIPLE_BLOCK, |
| .cmd_flags = SDMMC_READ_MULTIPLE_BLOCK_FLAGS, |
| .arg = 0x1234abcd, |
| .blocksize = 16, |
| .suppress_error_messages = false, |
| .client_id = 0, |
| .buffers_list = &buffer, |
| .buffers_count = 1, |
| }; |
| uint32_t response[4] = {}; |
| EXPECT_OK(dut_->SdmmcRequest(&request, response)); |
| |
| EXPECT_EQ(AdmaSystemAddress::Get(0).ReadFrom(&mmio_).reg_value(), kDescriptorAddress); |
| 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, (zx_system_get_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(dut_->zxdev())); |
| } |
| |
| TEST_F(SdhciTest, CommandSettingsMultiBlock) { |
| ASSERT_NO_FATAL_FAILURE(CreateDut(SDHCI_QUIRK_STRIP_RESPONSE_CRC_PRESERVE_ORDER)); |
| |
| 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(zx_system_get_page_size(), 0, &vmo)); |
| EXPECT_OK(dut_->SdmmcRegisterVmo(0, 0, std::move(vmo), 0, zx_system_get_page_size(), |
| SDMMC_VMO_RIGHT_READ)); |
| |
| const sdmmc_buffer_region_t buffer = { |
| .buffer = |
| { |
| .vmo_id = 0, |
| }, |
| .type = SDMMC_BUFFER_TYPE_VMO_ID, |
| .offset = 0, |
| .size = 1024, |
| }; |
| |
| const sdmmc_req_t request = { |
| .cmd_idx = SDMMC_WRITE_MULTIPLE_BLOCK, |
| .cmd_flags = SDMMC_WRITE_MULTIPLE_BLOCK_FLAGS, |
| .arg = 0x1234'abcd, |
| .blocksize = 512, |
| .suppress_error_messages = false, |
| .client_id = 0, |
| .buffers_list = &buffer, |
| .buffers_count = 1, |
| }; |
| |
| Response::Get(0).FromValue(0).set_reg_value(0xabcd'1234).WriteTo(&mmio_); |
| Response::Get(1).FromValue(0).set_reg_value(0xa5a5'a5a5).WriteTo(&mmio_); |
| Response::Get(2).FromValue(0).set_reg_value(0x1122'3344).WriteTo(&mmio_); |
| Response::Get(3).FromValue(0).set_reg_value(0xaabb'ccdd).WriteTo(&mmio_); |
| |
| uint32_t response[4] = {}; |
| EXPECT_OK(dut_->SdmmcRequest(&request, response)); |
| |
| EXPECT_EQ(response[0], 0xabcd'1234); |
| EXPECT_EQ(response[1], 0); |
| EXPECT_EQ(response[2], 0); |
| EXPECT_EQ(response[3], 0); |
| |
| const Command command = Command::Get().ReadFrom(&mmio_); |
| EXPECT_EQ(command.response_type(), Command::kResponseType48Bits); |
| EXPECT_TRUE(command.command_crc_check()); |
| EXPECT_TRUE(command.command_index_check()); |
| EXPECT_TRUE(command.data_present()); |
| EXPECT_EQ(command.command_type(), Command::kCommandTypeNormal); |
| EXPECT_EQ(command.command_index(), SDMMC_WRITE_MULTIPLE_BLOCK); |
| |
| const TransferMode transfer_mode = TransferMode::Get().ReadFrom(&mmio_); |
| EXPECT_TRUE(transfer_mode.dma_enable()); |
| EXPECT_TRUE(transfer_mode.block_count_enable()); |
| EXPECT_EQ(transfer_mode.auto_cmd_enable(), TransferMode::kAutoCmdDisable); |
| EXPECT_FALSE(transfer_mode.read()); |
| EXPECT_TRUE(transfer_mode.multi_block()); |
| |
| EXPECT_EQ(BlockSize::Get().ReadFrom(&mmio_).reg_value(), 512); |
| EXPECT_EQ(BlockCount::Get().ReadFrom(&mmio_).reg_value(), 2); |
| EXPECT_EQ(Argument::Get().ReadFrom(&mmio_).reg_value(), 0x1234'abcd); |
| |
| dut_->DdkUnbind(ddk::UnbindTxn(dut_->zxdev())); |
| } |
| |
| TEST_F(SdhciTest, CommandSettingsSingleBlock) { |
| ASSERT_NO_FATAL_FAILURE(CreateDut(SDHCI_QUIRK_STRIP_RESPONSE_CRC_PRESERVE_ORDER)); |
| |
| 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(zx_system_get_page_size(), 0, &vmo)); |
| EXPECT_OK(dut_->SdmmcRegisterVmo(0, 0, std::move(vmo), 0, zx_system_get_page_size(), |
| SDMMC_VMO_RIGHT_WRITE)); |
| |
| const sdmmc_buffer_region_t buffer = { |
| .buffer = |
| { |
| .vmo_id = 0, |
| }, |
| .type = SDMMC_BUFFER_TYPE_VMO_ID, |
| .offset = 0, |
| .size = 128, |
| }; |
| |
| const sdmmc_req_t request = { |
| .cmd_idx = SDMMC_READ_BLOCK, |
| .cmd_flags = SDMMC_READ_BLOCK_FLAGS, |
| .arg = 0x1234'abcd, |
| .blocksize = 128, |
| .suppress_error_messages = false, |
| .client_id = 0, |
| .buffers_list = &buffer, |
| .buffers_count = 1, |
| }; |
| |
| Response::Get(0).FromValue(0).set_reg_value(0xabcd'1234).WriteTo(&mmio_); |
| Response::Get(1).FromValue(0).set_reg_value(0xa5a5'a5a5).WriteTo(&mmio_); |
| Response::Get(2).FromValue(0).set_reg_value(0x1122'3344).WriteTo(&mmio_); |
| Response::Get(3).FromValue(0).set_reg_value(0xaabb'ccdd).WriteTo(&mmio_); |
| |
| uint32_t response[4] = {}; |
| EXPECT_OK(dut_->SdmmcRequest(&request, response)); |
| |
| EXPECT_EQ(response[0], 0xabcd'1234); |
| EXPECT_EQ(response[1], 0); |
| EXPECT_EQ(response[2], 0); |
| EXPECT_EQ(response[3], 0); |
| |
| const Command command = Command::Get().ReadFrom(&mmio_); |
| EXPECT_EQ(command.response_type(), Command::kResponseType48Bits); |
| EXPECT_TRUE(command.command_crc_check()); |
| EXPECT_TRUE(command.command_index_check()); |
| EXPECT_TRUE(command.data_present()); |
| EXPECT_EQ(command.command_type(), Command::kCommandTypeNormal); |
| EXPECT_EQ(command.command_index(), SDMMC_READ_BLOCK); |
| |
| const TransferMode transfer_mode = TransferMode::Get().ReadFrom(&mmio_); |
| EXPECT_TRUE(transfer_mode.dma_enable()); |
| EXPECT_FALSE(transfer_mode.block_count_enable()); |
| EXPECT_EQ(transfer_mode.auto_cmd_enable(), TransferMode::kAutoCmdDisable); |
| EXPECT_TRUE(transfer_mode.read()); |
| EXPECT_FALSE(transfer_mode.multi_block()); |
| |
| EXPECT_EQ(BlockSize::Get().ReadFrom(&mmio_).reg_value(), 128); |
| EXPECT_EQ(BlockCount::Get().ReadFrom(&mmio_).reg_value(), 1); |
| EXPECT_EQ(Argument::Get().ReadFrom(&mmio_).reg_value(), 0x1234'abcd); |
| |
| dut_->DdkUnbind(ddk::UnbindTxn(dut_->zxdev())); |
| } |
| |
| TEST_F(SdhciTest, CommandSettingsBusyResponse) { |
| ASSERT_NO_FATAL_FAILURE(CreateDut(SDHCI_QUIRK_STRIP_RESPONSE_CRC_PRESERVE_ORDER)); |
| |
| 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()); |
| |
| const sdmmc_req_t request = { |
| .cmd_idx = 55, |
| .cmd_flags = SDMMC_RESP_LEN_48B | SDMMC_CMD_TYPE_NORMAL | SDMMC_RESP_CRC_CHECK | |
| SDMMC_RESP_CMD_IDX_CHECK, |
| .arg = 0x1234'abcd, |
| .blocksize = 0, |
| .suppress_error_messages = false, |
| .client_id = 0, |
| .buffers_list = nullptr, |
| .buffers_count = 0, |
| }; |
| |
| Response::Get(0).FromValue(0).set_reg_value(0xabcd'1234).WriteTo(&mmio_); |
| Response::Get(1).FromValue(0).set_reg_value(0xa5a5'a5a5).WriteTo(&mmio_); |
| Response::Get(2).FromValue(0).set_reg_value(0x1122'3344).WriteTo(&mmio_); |
| Response::Get(3).FromValue(0).set_reg_value(0xaabb'ccdd).WriteTo(&mmio_); |
| |
| uint32_t response[4] = {}; |
| EXPECT_OK(dut_->SdmmcRequest(&request, response)); |
| |
| EXPECT_EQ(response[0], 0xabcd'1234); |
| EXPECT_EQ(response[1], 0); |
| EXPECT_EQ(response[2], 0); |
| EXPECT_EQ(response[3], 0); |
| |
| const Command command = Command::Get().ReadFrom(&mmio_); |
| EXPECT_EQ(command.response_type(), Command::kResponseType48BitsWithBusy); |
| EXPECT_TRUE(command.command_crc_check()); |
| EXPECT_TRUE(command.command_index_check()); |
| EXPECT_FALSE(command.data_present()); |
| EXPECT_EQ(command.command_type(), Command::kCommandTypeNormal); |
| EXPECT_EQ(command.command_index(), 55); |
| |
| const TransferMode transfer_mode = TransferMode::Get().ReadFrom(&mmio_); |
| EXPECT_FALSE(transfer_mode.dma_enable()); |
| EXPECT_FALSE(transfer_mode.block_count_enable()); |
| EXPECT_EQ(transfer_mode.auto_cmd_enable(), TransferMode::kAutoCmdDisable); |
| EXPECT_FALSE(transfer_mode.read()); |
| EXPECT_FALSE(transfer_mode.multi_block()); |
| |
| EXPECT_EQ(BlockSize::Get().ReadFrom(&mmio_).reg_value(), 0); |
| EXPECT_EQ(BlockCount::Get().ReadFrom(&mmio_).reg_value(), 0); |
| EXPECT_EQ(Argument::Get().ReadFrom(&mmio_).reg_value(), 0x1234'abcd); |
| |
| dut_->DdkUnbind(ddk::UnbindTxn(dut_->zxdev())); |
| } |
| |
| TEST_F(SdhciTest, ZeroBlockSize) { |
| ASSERT_NO_FATAL_FAILURE(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()); |
| |
| for (int i = 0; i < 4; i++) { |
| zx::vmo vmo; |
| ASSERT_OK(zx::vmo::create(512 * 16, 0, &vmo)); |
| EXPECT_OK(dut_->SdmmcRegisterVmo(i, 3, std::move(vmo), 64 * i, 512 * 12, SDMMC_VMO_RIGHT_READ)); |
| } |
| |
| const sdmmc_buffer_region_t buffers[4] = { |
| { |
| .buffer = |
| { |
| .vmo_id = 1, |
| }, |
| .type = SDMMC_BUFFER_TYPE_VMO_ID, |
| .offset = 16, |
| .size = 512, |
| }, |
| { |
| .buffer = |
| { |
| .vmo_id = 0, |
| }, |
| .type = SDMMC_BUFFER_TYPE_VMO_ID, |
| .offset = 32, |
| .size = 512 * 3, |
| }, |
| { |
| .buffer = |
| { |
| .vmo_id = 3, |
| }, |
| .type = SDMMC_BUFFER_TYPE_VMO_ID, |
| .offset = 48, |
| .size = 512 * 10, |
| }, |
| { |
| .buffer = |
| { |
| .vmo_id = 2, |
| }, |
| .type = SDMMC_BUFFER_TYPE_VMO_ID, |
| .offset = 80, |
| .size = 512 * 7, |
| }, |
| }; |
| |
| const sdmmc_req_t request = { |
| .cmd_idx = SDMMC_WRITE_MULTIPLE_BLOCK, |
| .cmd_flags = SDMMC_WRITE_MULTIPLE_BLOCK_FLAGS, |
| .arg = 0x1234abcd, |
| .blocksize = 0, |
| .suppress_error_messages = false, |
| .client_id = 3, |
| .buffers_list = buffers, |
| .buffers_count = std::size(buffers), |
| }; |
| uint32_t response[4] = {}; |
| EXPECT_NOT_OK(dut_->SdmmcRequest(&request, response)); |
| |
| dut_->DdkUnbind(ddk::UnbindTxn(dut_->zxdev())); |
| } |
| |
| TEST_F(SdhciTest, NoBuffers) { |
| ASSERT_NO_FATAL_FAILURE(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(512 * 16, 0, &vmo)); |
| EXPECT_OK(dut_->SdmmcRegisterVmo(1, 3, std::move(vmo), 0, 1024, |
| SDMMC_VMO_RIGHT_READ | SDMMC_VMO_RIGHT_WRITE)); |
| |
| const sdmmc_buffer_region_t buffer = { |
| .buffer = |
| { |
| .vmo_id = 1, |
| }, |
| .type = SDMMC_BUFFER_TYPE_VMO_ID, |
| .offset = 0, |
| .size = 512, |
| }; |
| |
| const sdmmc_req_t request = { |
| .cmd_idx = SDMMC_WRITE_MULTIPLE_BLOCK, |
| .cmd_flags = SDMMC_WRITE_MULTIPLE_BLOCK_FLAGS, |
| .arg = 0x1234abcd, |
| .blocksize = 0, |
| .suppress_error_messages = false, |
| .client_id = 3, |
| .buffers_list = &buffer, |
| .buffers_count = 0, |
| }; |
| uint32_t response[4] = {}; |
| EXPECT_NOT_OK(dut_->SdmmcRequest(&request, response)); |
| |
| dut_->DdkUnbind(ddk::UnbindTxn(dut_->zxdev())); |
| } |
| |
| TEST_F(SdhciTest, OwnedAndUnownedBuffers) { |
| ASSERT_NO_FATAL_FAILURE(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 vmos[4]; |
| for (int i = 0; i < 4; i++) { |
| ASSERT_OK(zx::vmo::create(512 * 16, 0, &vmos[i])); |
| if (i % 2 == 0) { |
| EXPECT_OK( |
| dut_->SdmmcRegisterVmo(i, 3, std::move(vmos[i]), 64 * i, 512 * 12, SDMMC_VMO_RIGHT_READ)); |
| } |
| } |
| |
| const sdmmc_buffer_region_t buffers[4] = { |
| { |
| .buffer = |
| { |
| .vmo = vmos[1].get(), |
| }, |
| .type = SDMMC_BUFFER_TYPE_VMO_HANDLE, |
| .offset = 16, |
| .size = 512, |
| }, |
| { |
| .buffer = |
| { |
| .vmo_id = 0, |
| }, |
| .type = SDMMC_BUFFER_TYPE_VMO_ID, |
| .offset = 32, |
| .size = 512 * 3, |
| }, |
| { |
| .buffer = |
| { |
| .vmo = vmos[3].get(), |
| }, |
| .type = SDMMC_BUFFER_TYPE_VMO_HANDLE, |
| .offset = 48, |
| .size = 512 * 10, |
| }, |
| { |
| .buffer = |
| { |
| .vmo_id = 2, |
| }, |
| .type = SDMMC_BUFFER_TYPE_VMO_ID, |
| .offset = 80, |
| .size = 512 * 7, |
| }, |
| }; |
| |
| const sdmmc_req_t request = { |
| .cmd_idx = SDMMC_WRITE_MULTIPLE_BLOCK, |
| .cmd_flags = SDMMC_WRITE_MULTIPLE_BLOCK_FLAGS, |
| .arg = 0x1234abcd, |
| .blocksize = 512, |
| .suppress_error_messages = false, |
| .client_id = 3, |
| .buffers_list = buffers, |
| .buffers_count = std::size(buffers), |
| }; |
| |
| EXPECT_NO_FAILURES(ExpectPmoCount(3)); |
| |
| uint32_t response[4] = {}; |
| EXPECT_OK(dut_->SdmmcRequest(&request, response)); |
| |
| // Unowned buffers should have been unpinned. |
| EXPECT_NO_FAILURES(ExpectPmoCount(3)); |
| |
| EXPECT_EQ(AdmaSystemAddress::Get(0).ReadFrom(&mmio_).reg_value(), zx_system_get_page_size()); |
| EXPECT_EQ(AdmaSystemAddress::Get(1).ReadFrom(&mmio_).reg_value(), 0); |
| |
| const auto* 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, zx_system_get_page_size() + 16); |
| EXPECT_EQ(descriptors[0].length, 512); |
| |
| memcpy(&address, &descriptors[1].address, sizeof(address)); |
| EXPECT_EQ(descriptors[1].attr, 0b100'001); |
| EXPECT_EQ(address, zx_system_get_page_size() + 32); |
| EXPECT_EQ(descriptors[1].length, 512 * 3); |
| |
| // Buffer is greater than one page and gets split across two descriptors. |
| memcpy(&address, &descriptors[2].address, sizeof(address)); |
| EXPECT_EQ(descriptors[2].attr, 0b100'001); |
| EXPECT_EQ(address, zx_system_get_page_size() + 48); |
| EXPECT_EQ(descriptors[2].length, zx_system_get_page_size() - 48); |
| |
| memcpy(&address, &descriptors[3].address, sizeof(address)); |
| EXPECT_EQ(descriptors[3].attr, 0b100'001); |
| EXPECT_EQ(address, zx_system_get_page_size()); |
| EXPECT_EQ(descriptors[3].length, (512 * 10) - zx_system_get_page_size() + 48); |
| |
| memcpy(&address, &descriptors[4].address, sizeof(address)); |
| EXPECT_EQ(descriptors[4].attr, 0b100'011); |
| EXPECT_EQ(address, zx_system_get_page_size() + 208); |
| EXPECT_EQ(descriptors[4].length, 512 * 7); |
| |
| dut_->DdkUnbind(ddk::UnbindTxn(dut_->zxdev())); |
| } |
| |
| TEST_F(SdhciTest, CombineContiguousRegions) { |
| constexpr zx_paddr_t kDescriptorAddress = 0xc000'0000; |
| const zx_paddr_t kStartAddress = 0xa7ff'ffff & ~PageMask(); |
| |
| ASSERT_NO_FATAL_FAILURE(CreateDut({ |
| kDescriptorAddress, |
| kStartAddress, |
| kStartAddress + zx_system_get_page_size(), |
| kStartAddress + (zx_system_get_page_size() * 2), |
| kStartAddress + (zx_system_get_page_size() * 3), |
| 0xb000'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()); |
| |
| zx::vmo vmo; |
| ASSERT_OK(zx::vmo::create((zx_system_get_page_size() * 4) + 512, 0, &vmo)); |
| |
| const sdmmc_buffer_region_t buffer = { |
| .buffer = |
| { |
| .vmo = vmo.get(), |
| }, |
| .type = SDMMC_BUFFER_TYPE_VMO_HANDLE, |
| .offset = 512, |
| .size = zx_system_get_page_size() * 4, |
| }; |
| |
| const sdmmc_req_t request = { |
| .cmd_idx = SDMMC_WRITE_MULTIPLE_BLOCK, |
| .cmd_flags = SDMMC_WRITE_MULTIPLE_BLOCK_FLAGS, |
| .arg = 0x1234abcd, |
| .blocksize = 512, |
| .suppress_error_messages = false, |
| .client_id = 0, |
| .buffers_list = &buffer, |
| .buffers_count = 1, |
| }; |
| |
| EXPECT_NO_FAILURES(ExpectPmoCount(1)); |
| |
| uint32_t response[4] = {}; |
| EXPECT_OK(dut_->SdmmcRequest(&request, response)); |
| |
| EXPECT_NO_FAILURES(ExpectPmoCount(1)); |
| |
| EXPECT_EQ(AdmaSystemAddress::Get(0).ReadFrom(&mmio_).reg_value(), kDescriptorAddress); |
| 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, kStartAddress + 512); |
| EXPECT_EQ(descriptors[0].length, (zx_system_get_page_size() * 4) - 512); |
| |
| EXPECT_EQ(descriptors[1].attr, 0b100'011); |
| EXPECT_EQ(descriptors[1].address, 0xb000'0000); |
| EXPECT_EQ(descriptors[1].length, 512); |
| |
| dut_->DdkUnbind(ddk::UnbindTxn(dut_->zxdev())); |
| } |
| |
| TEST_F(SdhciTest, DiscontiguousRegions) { |
| constexpr zx_paddr_t kDescriptorAddress = 0xc000'0000; |
| constexpr zx_paddr_t kDiscontiguousPageOffset = 0x1'0000'0000; |
| const zx_paddr_t kStartAddress = 0xa7ff'ffff & ~PageMask(); |
| |
| ASSERT_NO_FATAL_FAILURE(CreateDut({ |
| kDescriptorAddress, |
| kStartAddress, |
| kDiscontiguousPageOffset + kStartAddress, |
| (2 * kDiscontiguousPageOffset) + kStartAddress, |
| (3 * kDiscontiguousPageOffset) + kStartAddress, |
| (4 * kDiscontiguousPageOffset) + kStartAddress, |
| (4 * kDiscontiguousPageOffset) + kStartAddress + zx_system_get_page_size(), |
| (4 * kDiscontiguousPageOffset) + kStartAddress + (2 * zx_system_get_page_size()), |
| (5 * kDiscontiguousPageOffset) + kStartAddress, |
| (6 * kDiscontiguousPageOffset) + kStartAddress, |
| (7 * kDiscontiguousPageOffset) + kStartAddress, |
| (7 * kDiscontiguousPageOffset) + kStartAddress + zx_system_get_page_size(), |
| (8 * kDiscontiguousPageOffset) + kStartAddress, |
| })); |
| |
| 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(zx_system_get_page_size() * 12, 0, &vmo)); |
| |
| const sdmmc_buffer_region_t buffer = { |
| .buffer = |
| { |
| .vmo = vmo.get(), |
| }, |
| .type = SDMMC_BUFFER_TYPE_VMO_HANDLE, |
| .offset = 512, |
| .size = (zx_system_get_page_size() * 12) - 512 - 1024, |
| }; |
| |
| const sdmmc_req_t request = { |
| .cmd_idx = SDMMC_WRITE_MULTIPLE_BLOCK, |
| .cmd_flags = SDMMC_WRITE_MULTIPLE_BLOCK_FLAGS, |
| .arg = 0x1234abcd, |
| .blocksize = 512, |
| .suppress_error_messages = false, |
| .client_id = 0, |
| .buffers_list = &buffer, |
| .buffers_count = 1, |
| }; |
| |
| EXPECT_NO_FAILURES(ExpectPmoCount(1)); |
| |
| uint32_t response[4] = {}; |
| EXPECT_OK(dut_->SdmmcRequest(&request, response)); |
| |
| EXPECT_NO_FAILURES(ExpectPmoCount(1)); |
| |
| EXPECT_EQ(AdmaSystemAddress::Get(0).ReadFrom(&mmio_).reg_value(), kDescriptorAddress); |
| EXPECT_EQ(AdmaSystemAddress::Get(1).ReadFrom(&mmio_).reg_value(), 0); |
| |
| const auto* const descriptors = reinterpret_cast<Sdhci::AdmaDescriptor96*>(dut_->iobuf_virt()); |
| |
| EXPECT_EQ(descriptors[0].attr, 0b100'001); |
| EXPECT_EQ(descriptors[0].get_address(), kStartAddress + 512); |
| EXPECT_EQ(descriptors[0].length, zx_system_get_page_size() - 512); |
| |
| EXPECT_EQ(descriptors[1].attr, 0b100'001); |
| EXPECT_EQ(descriptors[1].get_address(), kDiscontiguousPageOffset + kStartAddress); |
| EXPECT_EQ(descriptors[1].length, zx_system_get_page_size()); |
| |
| EXPECT_EQ(descriptors[2].attr, 0b100'001); |
| EXPECT_EQ(descriptors[2].get_address(), (2 * kDiscontiguousPageOffset) + kStartAddress); |
| EXPECT_EQ(descriptors[2].length, zx_system_get_page_size()); |
| |
| EXPECT_EQ(descriptors[3].attr, 0b100'001); |
| EXPECT_EQ(descriptors[3].get_address(), (3 * kDiscontiguousPageOffset) + kStartAddress); |
| EXPECT_EQ(descriptors[3].length, zx_system_get_page_size()); |
| |
| EXPECT_EQ(descriptors[4].attr, 0b100'001); |
| EXPECT_EQ(descriptors[4].get_address(), (4 * kDiscontiguousPageOffset) + kStartAddress); |
| EXPECT_EQ(descriptors[4].length, zx_system_get_page_size() * 3); |
| |
| EXPECT_EQ(descriptors[5].attr, 0b100'001); |
| EXPECT_EQ(descriptors[5].get_address(), (5 * kDiscontiguousPageOffset) + kStartAddress); |
| EXPECT_EQ(descriptors[5].length, zx_system_get_page_size()); |
| |
| EXPECT_EQ(descriptors[6].attr, 0b100'001); |
| EXPECT_EQ(descriptors[6].get_address(), (6 * kDiscontiguousPageOffset) + kStartAddress); |
| EXPECT_EQ(descriptors[6].length, zx_system_get_page_size()); |
| |
| EXPECT_EQ(descriptors[7].attr, 0b100'001); |
| EXPECT_EQ(descriptors[7].get_address(), (7 * kDiscontiguousPageOffset) + kStartAddress); |
| EXPECT_EQ(descriptors[7].length, zx_system_get_page_size() * 2); |
| |
| EXPECT_EQ(descriptors[8].attr, 0b100'011); |
| EXPECT_EQ(descriptors[8].get_address(), (8 * kDiscontiguousPageOffset) + kStartAddress); |
| EXPECT_EQ(descriptors[8].length, zx_system_get_page_size() - 1024); |
| |
| dut_->DdkUnbind(ddk::UnbindTxn(dut_->zxdev())); |
| } |
| |
| TEST_F(SdhciTest, RegionStartAndEndOffsets) { |
| constexpr zx_paddr_t kDescriptorAddress = 0xc000'0000; |
| const zx_paddr_t kStartAddress = 0xa7ff'ffff & ~PageMask(); |
| |
| ASSERT_NO_FATAL_FAILURE(CreateDut({ |
| kDescriptorAddress, |
| kStartAddress, |
| kStartAddress + zx_system_get_page_size(), |
| kStartAddress + (zx_system_get_page_size() * 2), |
| kStartAddress + (zx_system_get_page_size() * 3), |
| })); |
| |
| 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((zx_system_get_page_size() * 4), 0, &vmo)); |
| |
| sdmmc_buffer_region_t buffer = { |
| .buffer = |
| { |
| .vmo = vmo.get(), |
| }, |
| .type = SDMMC_BUFFER_TYPE_VMO_HANDLE, |
| .offset = 0, |
| .size = zx_system_get_page_size(), |
| }; |
| |
| const sdmmc_req_t request = { |
| .cmd_idx = SDMMC_WRITE_MULTIPLE_BLOCK, |
| .cmd_flags = SDMMC_WRITE_MULTIPLE_BLOCK_FLAGS, |
| .arg = 0x1234abcd, |
| .blocksize = 512, |
| .suppress_error_messages = false, |
| .client_id = 0, |
| .buffers_list = &buffer, |
| .buffers_count = 1, |
| }; |
| |
| uint32_t response[4] = {}; |
| EXPECT_OK(dut_->SdmmcRequest(&request, response)); |
| |
| const Sdhci::AdmaDescriptor64* const descriptors = |
| reinterpret_cast<Sdhci::AdmaDescriptor64*>(dut_->iobuf_virt()); |
| |
| EXPECT_EQ(descriptors[0].attr, 0b100'011); |
| EXPECT_EQ(descriptors[0].address, kStartAddress); |
| EXPECT_EQ(descriptors[0].length, zx_system_get_page_size()); |
| |
| buffer.offset = 512; |
| buffer.size = zx_system_get_page_size() - 512; |
| |
| EXPECT_OK(dut_->SdmmcRequest(&request, response)); |
| |
| EXPECT_EQ(descriptors[0].attr, 0b100'011); |
| EXPECT_EQ(descriptors[0].address, kStartAddress + zx_system_get_page_size() + 512); |
| EXPECT_EQ(descriptors[0].length, zx_system_get_page_size() - 512); |
| |
| buffer.offset = 0; |
| buffer.size = zx_system_get_page_size() - 512; |
| |
| EXPECT_OK(dut_->SdmmcRequest(&request, response)); |
| |
| EXPECT_EQ(descriptors[0].attr, 0b100'011); |
| EXPECT_EQ(descriptors[0].address, kStartAddress + (zx_system_get_page_size() * 2)); |
| EXPECT_EQ(descriptors[0].length, zx_system_get_page_size() - 512); |
| |
| buffer.offset = 512; |
| buffer.size = zx_system_get_page_size() - 1024; |
| |
| EXPECT_OK(dut_->SdmmcRequest(&request, response)); |
| |
| EXPECT_EQ(descriptors[0].attr, 0b100'011); |
| EXPECT_EQ(descriptors[0].address, kStartAddress + (zx_system_get_page_size() * 3) + 512); |
| EXPECT_EQ(descriptors[0].length, zx_system_get_page_size() - 1024); |
| |
| dut_->DdkUnbind(ddk::UnbindTxn(dut_->zxdev())); |
| } |
| |
| TEST_F(SdhciTest, BufferZeroSize) { |
| ASSERT_NO_FATAL_FAILURE(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(zx_system_get_page_size() * 4, 0, &vmo)); |
| EXPECT_OK(dut_->SdmmcRegisterVmo(1, 0, std::move(vmo), 0, zx_system_get_page_size() * 4, |
| SDMMC_VMO_RIGHT_READ)); |
| } |
| |
| zx::vmo vmo; |
| ASSERT_OK(zx::vmo::create(zx_system_get_page_size() * 4, 0, &vmo)); |
| |
| { |
| const sdmmc_buffer_region_t buffers[3] = { |
| { |
| .buffer = |
| { |
| .vmo_id = 1, |
| }, |
| .type = SDMMC_BUFFER_TYPE_VMO_ID, |
| .offset = 0, |
| .size = 512, |
| }, |
| { |
| .buffer = |
| { |
| .vmo = vmo.get(), |
| }, |
| .type = SDMMC_BUFFER_TYPE_VMO_HANDLE, |
| .offset = 0, |
| .size = 0, |
| }, |
| { |
| .buffer = |
| { |
| .vmo_id = 1, |
| }, |
| .type = SDMMC_BUFFER_TYPE_VMO_ID, |
| .offset = 512, |
| .size = 512, |
| }, |
| }; |
| |
| const sdmmc_req_t request = { |
| .cmd_idx = SDMMC_WRITE_MULTIPLE_BLOCK, |
| .cmd_flags = SDMMC_WRITE_MULTIPLE_BLOCK_FLAGS, |
| .arg = 0x1234abcd, |
| .blocksize = 512, |
| .suppress_error_messages = false, |
| .client_id = 0, |
| .buffers_list = buffers, |
| .buffers_count = std::size(buffers), |
| }; |
| |
| uint32_t response[4] = {}; |
| EXPECT_NOT_OK(dut_->SdmmcRequest(&request, response)); |
| } |
| |
| { |
| const sdmmc_buffer_region_t buffers[3] = { |
| { |
| .buffer = |
| { |
| .vmo = vmo.get(), |
| }, |
| .type = SDMMC_BUFFER_TYPE_VMO_HANDLE, |
| .offset = 0, |
| .size = 512, |
| }, |
| { |
| .buffer = |
| { |
| .vmo_id = 1, |
| }, |
| .type = SDMMC_BUFFER_TYPE_VMO_ID, |
| .offset = 0, |
| .size = 0, |
| }, |
| { |
| .buffer = |
| { |
| .vmo = vmo.get(), |
| }, |
| .type = SDMMC_BUFFER_TYPE_VMO_HANDLE, |
| .offset = 512, |
| .size = 512, |
| }, |
| }; |
| |
| const sdmmc_req_t request = { |
| .cmd_idx = SDMMC_WRITE_MULTIPLE_BLOCK, |
| .cmd_flags = SDMMC_WRITE_MULTIPLE_BLOCK_FLAGS, |
| .arg = 0x1234abcd, |
| .blocksize = 512, |
| .suppress_error_messages = false, |
| .client_id = 0, |
| .buffers_list = buffers, |
| .buffers_count = std::size(buffers), |
| }; |
| |
| uint32_t response[4] = {}; |
| EXPECT_NOT_OK(dut_->SdmmcRequest(&request, response)); |
| } |
| |
| dut_->DdkUnbind(ddk::UnbindTxn(dut_->zxdev())); |
| } |
| |
| TEST_F(SdhciTest, TransferError) { |
| ASSERT_NO_FATAL_FAILURE(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(512, 0, &vmo)); |
| |
| const sdmmc_buffer_region_t buffer = { |
| .buffer = |
| { |
| .vmo = vmo.get(), |
| }, |
| .type = SDMMC_BUFFER_TYPE_VMO_HANDLE, |
| .offset = 0, |
| .size = 512, |
| }; |
| const sdmmc_req_t request = { |
| .cmd_idx = SDMMC_WRITE_MULTIPLE_BLOCK, |
| .cmd_flags = SDMMC_WRITE_MULTIPLE_BLOCK_FLAGS, |
| .arg = 0x1234abcd, |
| .blocksize = 512, |
| .suppress_error_messages = false, |
| .client_id = 0, |
| .buffers_list = &buffer, |
| .buffers_count = 1, |
| }; |
| |
| dut_->InjectTransferError(); |
| uint32_t response[4] = {}; |
| EXPECT_NOT_OK(dut_->SdmmcRequest(&request, response)); |
| |
| dut_->DdkUnbind(ddk::UnbindTxn(dut_->zxdev())); |
| } |
| |
| TEST_F(SdhciTest, MaxTransferSize) { |
| std::vector<zx_paddr_t> bti_paddrs; |
| bti_paddrs.push_back(0x1000'0000'0000'0000); |
| |
| for (size_t i = 0; i < 512; i++) { |
| // 512 pages, fully discontiguous. |
| bti_paddrs.push_back(zx_system_get_page_size() * (i + 1) * 2); |
| } |
| |
| ASSERT_NO_FATAL_FAILURE(CreateDut(std::move(bti_paddrs))); |
| |
| 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(512, 0, &vmo)); |
| |
| const sdmmc_buffer_region_t buffer = { |
| .buffer = |
| { |
| .vmo = vmo.get(), |
| }, |
| .type = SDMMC_BUFFER_TYPE_VMO_HANDLE, |
| .offset = 0, |
| .size = 512 * zx_system_get_page_size(), |
| }; |
| const sdmmc_req_t request = { |
| .cmd_idx = SDMMC_WRITE_MULTIPLE_BLOCK, |
| .cmd_flags = SDMMC_WRITE_MULTIPLE_BLOCK_FLAGS, |
| .arg = 0x1234abcd, |
| .blocksize = 512, |
| .suppress_error_messages = false, |
| .client_id = 0, |
| .buffers_list = &buffer, |
| .buffers_count = 1, |
| }; |
| |
| uint32_t response[4] = {}; |
| EXPECT_OK(dut_->SdmmcRequest(&request, response)); |
| |
| const Sdhci::AdmaDescriptor96* const descriptors = |
| reinterpret_cast<Sdhci::AdmaDescriptor96*>(dut_->iobuf_virt()); |
| |
| EXPECT_EQ(descriptors[0].attr, 0b100'001); |
| EXPECT_EQ(descriptors[0].get_address(), zx_system_get_page_size() * 2); |
| EXPECT_EQ(descriptors[0].length, zx_system_get_page_size()); |
| |
| EXPECT_EQ(descriptors[511].attr, 0b100'011); |
| EXPECT_EQ(descriptors[511].get_address(), zx_system_get_page_size() * 2 * 512); |
| EXPECT_EQ(descriptors[511].length, zx_system_get_page_size()); |
| |
| dut_->DdkUnbind(ddk::UnbindTxn(dut_->zxdev())); |
| } |
| |
| TEST_F(SdhciTest, TransferSizeExceeded) { |
| std::vector<zx_paddr_t> bti_paddrs; |
| bti_paddrs.push_back(0x1000'0000'0000'0000); |
| |
| for (size_t i = 0; i < 513; i++) { |
| bti_paddrs.push_back(zx_system_get_page_size() * (i + 1) * 2); |
| } |
| |
| ASSERT_NO_FATAL_FAILURE(CreateDut(std::move(bti_paddrs))); |
| |
| 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(512, 0, &vmo)); |
| |
| const sdmmc_buffer_region_t buffer = { |
| .buffer = |
| { |
| .vmo = vmo.get(), |
| }, |
| .type = SDMMC_BUFFER_TYPE_VMO_HANDLE, |
| .offset = 0, |
| .size = 513 * zx_system_get_page_size(), |
| }; |
| const sdmmc_req_t request = { |
| .cmd_idx = SDMMC_WRITE_MULTIPLE_BLOCK, |
| .cmd_flags = SDMMC_WRITE_MULTIPLE_BLOCK_FLAGS, |
| .arg = 0x1234abcd, |
| .blocksize = 512, |
| .suppress_error_messages = false, |
| .client_id = 0, |
| .buffers_list = &buffer, |
| .buffers_count = 1, |
| }; |
| |
| uint32_t response[4] = {}; |
| EXPECT_NOT_OK(dut_->SdmmcRequest(&request, response)); |
| |
| dut_->DdkUnbind(ddk::UnbindTxn(dut_->zxdev())); |
| } |
| |
| TEST_F(SdhciTest, DmaSplitSizeAndAligntmentBoundaries) { |
| constexpr zx_paddr_t kDescriptorAddress = 0xc000'0000; |
| std::vector<zx_paddr_t> paddrs; |
| // Generate a single contiguous physical region. |
| paddrs.push_back(kDescriptorAddress); |
| for (zx_paddr_t p = 0x1'0001'8000; p < 0x1'0010'0000; p += zx_system_get_page_size()) { |
| paddrs.push_back(p); |
| } |
| |
| ASSERT_NO_FATAL_FAILURE( |
| CreateDut(std::move(paddrs), SDHCI_QUIRK_USE_DMA_BOUNDARY_ALIGNMENT, 0x2'0000)); |
| |
| 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(1024, 0, &vmo)); |
| |
| const sdmmc_buffer_region_t buffer = { |
| .buffer = |
| { |
| .vmo = vmo.get(), |
| }, |
| .type = SDMMC_BUFFER_TYPE_VMO_HANDLE, |
| .offset = 0x1'8000, |
| .size = 0x4'0000, |
| }; |
| |
| const sdmmc_req_t request = { |
| .cmd_idx = SDMMC_READ_MULTIPLE_BLOCK, |
| .cmd_flags = SDMMC_READ_MULTIPLE_BLOCK_FLAGS, |
| .arg = 0x1234abcd, |
| .blocksize = 512, |
| .suppress_error_messages = false, |
| .client_id = 0, |
| .buffers_list = &buffer, |
| .buffers_count = 1, |
| }; |
| uint32_t response[4] = {}; |
| EXPECT_OK(dut_->SdmmcRequest(&request, response)); |
| |
| EXPECT_EQ(AdmaSystemAddress::Get(0).ReadFrom(&mmio_).reg_value(), kDescriptorAddress); |
| EXPECT_EQ(AdmaSystemAddress::Get(1).ReadFrom(&mmio_).reg_value(), 0); |
| |
| const Sdhci::AdmaDescriptor96* const descriptors = |
| reinterpret_cast<Sdhci::AdmaDescriptor96*>(dut_->iobuf_virt()); |
| |
| // Region split due to alignment. |
| EXPECT_EQ(descriptors[0].attr, 0b100'001); |
| EXPECT_EQ(descriptors[0].get_address(), 0x1'0001'8000); |
| EXPECT_EQ(descriptors[0].length, 0x8000); |
| |
| // Region split due to both alignment and descriptor max size. |
| EXPECT_EQ(descriptors[1].attr, 0b100'001); |
| EXPECT_EQ(descriptors[1].get_address(), 0x1'0002'0000); |
| EXPECT_EQ(descriptors[1].length, 0); // Zero length -> 0x1'0000 bytes |
| EXPECT_EQ(descriptors[2].attr, 0b100'001); |
| |
| // Region split due to descriptor max size. |
| EXPECT_EQ(descriptors[2].get_address(), 0x1'0003'0000); |
| EXPECT_EQ(descriptors[2].length, 0); |
| |
| EXPECT_EQ(descriptors[3].attr, 0b100'001); |
| EXPECT_EQ(descriptors[3].get_address(), 0x1'0004'0000); |
| EXPECT_EQ(descriptors[3].length, 0); |
| |
| EXPECT_EQ(descriptors[4].attr, 0b100'011); |
| EXPECT_EQ(descriptors[4].get_address(), 0x1'0005'0000); |
| EXPECT_EQ(descriptors[4].length, 0x8000); |
| |
| dut_->DdkUnbind(ddk::UnbindTxn(dut_->zxdev())); |
| } |
| |
| } // namespace sdhci |