blob: b66b4c676a1e93f3223984ef3a3c58b93517f5b5 [file] [log] [blame]
// Copyright 2023 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 <fidl/fuchsia.hardware.power/cpp/fidl.h>
#include <fidl/fuchsia.power.broker/cpp/fidl.h>
#include <fidl/fuchsia.power.system/cpp/fidl.h>
#include <lib/async_patterns/testing/cpp/dispatcher_bound.h>
#include <lib/driver/component/cpp/driver_export.h>
#include <lib/driver/testing/cpp/driver_lifecycle.h>
#include <lib/driver/testing/cpp/driver_runtime.h>
#include <lib/driver/testing/cpp/test_environment.h>
#include <lib/driver/testing/cpp/test_node.h>
#include <lib/fake-bti/bti.h>
#include <lib/fzl/vmo-mapper.h>
#include <lib/inspect/testing/cpp/zxtest/inspect.h>
#include <lib/mmio-ptr/fake.h>
#include <lib/mmio/mmio-buffer.h>
#include <lib/sdio/hw.h>
#include <lib/sdmmc/hw.h>
#include <threads.h>
#include <zircon/types.h>
#include <memory>
#include <vector>
#include <soc/aml-s912/s912-hw.h>
#include <zxtest/zxtest.h>
#include "aml-sdmmc-regs.h"
#include "src/devices/bus/testing/fake-pdev/fake-pdev.h"
#include "src/devices/lib/mmio/test-helper.h"
namespace aml_sdmmc {
class TestAmlSdmmc : public AmlSdmmc {
public:
TestAmlSdmmc(fdf::DriverStartArgs start_args, fdf::UnownedSynchronizedDispatcher dispatcher)
: AmlSdmmc(std::move(start_args), std::move(dispatcher)) {}
void* SetTestHooks() {
view_.emplace(mmio().View(0));
return descs_buffer();
}
const inspect::Hierarchy* GetInspectRoot(const std::string& suffix) {
inspector_.ReadInspect(inspector().inspector());
return inspector_.hierarchy().GetByPath({"aml-sdmmc-port" + suffix});
}
void ExpectInspectBoolPropertyValue(const std::string& name, bool value) {
const auto* root = GetInspectRoot("-unknown");
ASSERT_NOT_NULL(root);
const auto* property = root->node().get_property<inspect::BoolPropertyValue>(name);
ASSERT_NOT_NULL(property);
EXPECT_EQ(property->value(), value);
}
void ExpectInspectPropertyValue(const std::string& name, uint64_t value) {
const auto* root = GetInspectRoot("-unknown");
ASSERT_NOT_NULL(root);
const auto* property = root->node().get_property<inspect::UintPropertyValue>(name);
ASSERT_NOT_NULL(property);
EXPECT_EQ(property->value(), value);
}
void ExpectInspectPropertyValue(const std::string& path, const std::string& name,
std::string_view value) {
const auto* root = GetInspectRoot("-unknown");
ASSERT_NOT_NULL(root);
const auto* property =
root->GetByPath({path})->node().get_property<inspect::StringPropertyValue>(name);
ASSERT_NOT_NULL(property);
EXPECT_STREQ(property->value(), value);
}
zx_status_t WaitForInterruptImpl() override {
fake_bti_pinned_vmo_info_t pinned_vmos[2];
size_t actual = 0;
zx_status_t status =
fake_bti_get_pinned_vmos(bti().get(), pinned_vmos, std::size(pinned_vmos), &actual);
// In the tuning case there are exactly two VMOs pinned: one to hold the DMA descriptors, and
// one to hold the received tuning block. Write the expected tuning data to the second pinned
// VMO so that the tuning check always passes.
if (status == ZX_OK && actual == std::size(pinned_vmos) &&
pinned_vmos[0].size >= sizeof(aml_sdmmc_tuning_blk_pattern_4bit)) {
zx_vmo_write(pinned_vmos[1].vmo, aml_sdmmc_tuning_blk_pattern_4bit, pinned_vmos[1].offset,
sizeof(aml_sdmmc_tuning_blk_pattern_4bit));
}
for (size_t i = 0; i < std::min(actual, std::size(pinned_vmos)); i++) {
zx_handle_close(pinned_vmos[i].vmo);
}
if (request_index_ < request_results_.size() && request_results_[request_index_] == 0) {
// Indicate a receive CRC error.
view_->Write32(1, kAmlSdmmcStatusOffset);
successful_transfers_ = 0;
request_index_++;
} else if (interrupt_status_.has_value()) {
view_->Write32(interrupt_status_.value(), kAmlSdmmcStatusOffset);
} else {
// Indicate that the request completed successfully.
view_->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; }
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_;
std::optional<fdf::MmioView> view_;
};
class FakeClock : public fidl::WireServer<fuchsia_hardware_clock::Clock> {
public:
fuchsia_hardware_clock::Service::InstanceHandler GetInstanceHandler() {
return fuchsia_hardware_clock::Service::InstanceHandler({
.clock = bindings_.CreateHandler(this, fdf::Dispatcher::GetCurrent()->async_dispatcher(),
fidl::kIgnoreBindingClosure),
});
}
bool enabled() const { return enabled_; }
private:
void Enable(EnableCompleter::Sync& completer) override {
enabled_ = true;
completer.ReplySuccess();
}
void Disable(DisableCompleter::Sync& completer) override {
enabled_ = false;
completer.ReplySuccess();
}
void IsEnabled(IsEnabledCompleter::Sync& completer) override {
completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
}
void SetRate(SetRateRequestView request, SetRateCompleter::Sync& completer) override {
completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
}
void QuerySupportedRate(QuerySupportedRateRequestView request,
QuerySupportedRateCompleter::Sync& completer) override {
completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
}
void GetRate(GetRateCompleter::Sync& completer) override {
completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
}
void SetInput(SetInputRequestView request, SetInputCompleter::Sync& completer) override {
completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
}
void GetNumInputs(GetNumInputsCompleter::Sync& completer) override {
completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
}
void GetInput(GetInputCompleter::Sync& completer) override {
completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
}
fidl::ServerBindingGroup<fuchsia_hardware_clock::Clock> bindings_;
bool enabled_ = false;
};
class FakeSystemActivityGovernor : public fidl::Server<fuchsia_power_system::ActivityGovernor> {
public:
FakeSystemActivityGovernor(zx::event exec_state_passive, zx::event wake_handling_active)
: exec_state_passive_(std::move(exec_state_passive)),
wake_handling_active_(std::move(wake_handling_active)) {}
fidl::ProtocolHandler<fuchsia_power_system::ActivityGovernor> CreateHandler() {
return bindings_.CreateHandler(this, fdf::Dispatcher::GetCurrent()->async_dispatcher(),
fidl::kIgnoreBindingClosure);
}
void GetPowerElements(GetPowerElementsCompleter::Sync& completer) override {
fuchsia_power_system::PowerElements elements;
zx::event execution_element, wake_handling_element;
exec_state_passive_.duplicate(ZX_RIGHT_SAME_RIGHTS, &execution_element);
wake_handling_active_.duplicate(ZX_RIGHT_SAME_RIGHTS, &wake_handling_element);
fuchsia_power_system::ExecutionState exec_state = {
{.passive_dependency_token = std::move(execution_element)}};
fuchsia_power_system::WakeHandling wake_handling = {
{.active_dependency_token = std::move(wake_handling_element)}};
elements = {
{.execution_state = std::move(exec_state), .wake_handling = std::move(wake_handling)}};
completer.Reply({{std::move(elements)}});
}
void RegisterListener(RegisterListenerRequest& req,
RegisterListenerCompleter::Sync& completer) override {}
void handle_unknown_method(fidl::UnknownMethodMetadata<fuchsia_power_system::ActivityGovernor> md,
fidl::UnknownMethodCompleter::Sync& completer) override {}
private:
fidl::ServerBindingGroup<fuchsia_power_system::ActivityGovernor> bindings_;
zx::event exec_state_passive_;
zx::event wake_handling_active_;
};
class FakeLeaseControl : public fidl::Server<fuchsia_power_broker::LeaseControl> {
public:
void WatchStatus(fuchsia_power_broker::LeaseControlWatchStatusRequest& req,
WatchStatusCompleter::Sync& completer) override {
completer.Reply(lease_status_);
}
void handle_unknown_method(fidl::UnknownMethodMetadata<fuchsia_power_broker::LeaseControl> md,
fidl::UnknownMethodCompleter::Sync& completer) override {}
static fuchsia_power_broker::LeaseStatus lease_status_;
};
fuchsia_power_broker::LeaseStatus FakeLeaseControl::lease_status_ =
fuchsia_power_broker::LeaseStatus::kPending;
class FakeLessor : public fidl::Server<fuchsia_power_broker::Lessor> {
public:
void Lease(fuchsia_power_broker::LessorLeaseRequest& req,
LeaseCompleter::Sync& completer) override {
auto [lease_control_client_end, lease_control_server_end] =
fidl::Endpoints<fuchsia_power_broker::LeaseControl>::Create();
// Instantiate (fake) lease control implementation.
auto lease_control_impl = std::make_unique<FakeLeaseControl>();
lease_control_ = lease_control_impl.get();
lease_control_binding_ = fidl::BindServer<fuchsia_power_broker::LeaseControl>(
fdf::Dispatcher::GetCurrent()->async_dispatcher(), std::move(lease_control_server_end),
std::move(lease_control_impl),
[](FakeLeaseControl* impl, fidl::UnbindInfo info,
fidl::ServerEnd<fuchsia_power_broker::LeaseControl> server_end) mutable {});
completer.Reply(fit::success(std::move(lease_control_client_end)));
}
void handle_unknown_method(fidl::UnknownMethodMetadata<fuchsia_power_broker::Lessor> md,
fidl::UnknownMethodCompleter::Sync& completer) override {}
FakeLeaseControl* lease_control_;
private:
std::optional<fidl::ServerBindingRef<fuchsia_power_broker::LeaseControl>> lease_control_binding_;
};
class FakeCurrentLevel : public fidl::Server<fuchsia_power_broker::CurrentLevel> {
public:
void AddSideEffect(
fit::function<void(fuchsia_power_broker::PowerLevel current_level)> side_effect) {
side_effect_ = std::move(side_effect);
}
void Update(fuchsia_power_broker::CurrentLevelUpdateRequest& req,
UpdateCompleter::Sync& completer) override {
if (side_effect_) {
side_effect_(req.current_level());
}
completer.Reply(fit::success());
}
void handle_unknown_method(fidl::UnknownMethodMetadata<fuchsia_power_broker::CurrentLevel> md,
fidl::UnknownMethodCompleter::Sync& completer) override {}
fit::function<void(fuchsia_power_broker::PowerLevel)> side_effect_;
};
class FakeRequiredLevel : public fidl::Server<fuchsia_power_broker::RequiredLevel> {
public:
void Watch(WatchCompleter::Sync& completer) override {
completer.Reply(fit::success(required_level_));
}
void handle_unknown_method(fidl::UnknownMethodMetadata<fuchsia_power_broker::RequiredLevel> md,
fidl::UnknownMethodCompleter::Sync& completer) override {}
fuchsia_power_broker::PowerLevel required_level_ = AmlSdmmc::kPowerLevelOn;
};
class PowerElement {
public:
explicit PowerElement(fidl::ServerEnd<fuchsia_power_broker::ElementControl> element_control,
fidl::ServerBindingRef<fuchsia_power_broker::Lessor> lessor,
fidl::ServerBindingRef<fuchsia_power_broker::CurrentLevel> current_level,
fidl::ServerBindingRef<fuchsia_power_broker::RequiredLevel> required_level)
: element_control_(std::move(element_control)),
lessor_(std::move(lessor)),
current_level_(std::move(current_level)),
required_level_(std::move(required_level)) {}
fidl::ServerEnd<fuchsia_power_broker::ElementControl> element_control_;
fidl::ServerBindingRef<fuchsia_power_broker::Lessor> lessor_;
fidl::ServerBindingRef<fuchsia_power_broker::CurrentLevel> current_level_;
fidl::ServerBindingRef<fuchsia_power_broker::RequiredLevel> required_level_;
};
class FakePowerBroker : public fidl::Server<fuchsia_power_broker::Topology> {
public:
fidl::ProtocolHandler<fuchsia_power_broker::Topology> CreateHandler() {
return bindings_.CreateHandler(this, fdf::Dispatcher::GetCurrent()->async_dispatcher(),
fidl::kIgnoreBindingClosure);
}
void AddElement(fuchsia_power_broker::ElementSchema& req,
AddElementCompleter::Sync& completer) override {
// Get channels from request.
ASSERT_TRUE(req.level_control_channels().has_value());
fidl::ServerEnd<fuchsia_power_broker::CurrentLevel>& current_level_server_end =
req.level_control_channels().value().current();
fidl::ServerEnd<fuchsia_power_broker::RequiredLevel>& required_level_server_end =
req.level_control_channels().value().required();
fidl::ServerEnd<fuchsia_power_broker::Lessor>& lessor_server_end = req.lessor_channel().value();
// Make channels to return to client
auto [element_control_client_end, element_control_server_end] =
fidl::Endpoints<fuchsia_power_broker::ElementControl>::Create();
// Instantiate (fake) lessor implementation.
auto lessor_impl = std::make_unique<FakeLessor>();
if (req.element_name() == AmlSdmmc::kHardwarePowerElementName) {
hardware_power_lessor_ = lessor_impl.get();
} else if (req.element_name() == AmlSdmmc::kSystemWakeOnRequestPowerElementName) {
wake_on_request_lessor_ = lessor_impl.get();
} else {
ZX_ASSERT_MSG(0, "Unexpected power element.");
}
fidl::ServerBindingRef<fuchsia_power_broker::Lessor> lessor_binding =
fidl::BindServer<fuchsia_power_broker::Lessor>(
fdf::Dispatcher::GetCurrent()->async_dispatcher(), std::move(lessor_server_end),
std::move(lessor_impl),
[](FakeLessor* impl, fidl::UnbindInfo info,
fidl::ServerEnd<fuchsia_power_broker::Lessor> server_end) mutable {});
// Instantiate (fake) current and required level implementations.
auto current_level_impl = std::make_unique<FakeCurrentLevel>();
auto required_level_impl = std::make_unique<FakeRequiredLevel>();
if (req.element_name() == AmlSdmmc::kHardwarePowerElementName) {
hardware_power_current_level_ = current_level_impl.get();
hardware_power_required_level_ = required_level_impl.get();
} else if (req.element_name() == AmlSdmmc::kSystemWakeOnRequestPowerElementName) {
wake_on_request_current_level_ = current_level_impl.get();
wake_on_request_required_level_ = required_level_impl.get();
} else {
ZX_ASSERT_MSG(0, "Unexpected power element.");
}
fidl::ServerBindingRef<fuchsia_power_broker::CurrentLevel> current_level_binding =
fidl::BindServer<fuchsia_power_broker::CurrentLevel>(
fdf::Dispatcher::GetCurrent()->async_dispatcher(), std::move(current_level_server_end),
std::move(current_level_impl),
[](FakeCurrentLevel* impl, fidl::UnbindInfo info,
fidl::ServerEnd<fuchsia_power_broker::CurrentLevel> server_end) mutable {});
fidl::ServerBindingRef<fuchsia_power_broker::RequiredLevel> required_level_binding =
fidl::BindServer<fuchsia_power_broker::RequiredLevel>(
fdf::Dispatcher::GetCurrent()->async_dispatcher(), std::move(required_level_server_end),
std::move(required_level_impl),
[](FakeRequiredLevel* impl, fidl::UnbindInfo info,
fidl::ServerEnd<fuchsia_power_broker::RequiredLevel> server_end) mutable {});
if (wake_on_request_current_level_ && hardware_power_lessor_) {
wake_on_request_current_level_->AddSideEffect(
[&](fuchsia_power_broker::PowerLevel current_level) {
if (current_level == AmlSdmmc::kPowerLevelOn) {
hardware_power_lessor_->lease_control_->lease_status_ =
fuchsia_power_broker::LeaseStatus::kSatisfied;
} else if (current_level == AmlSdmmc::kPowerLevelOff) {
hardware_power_lessor_->lease_control_->lease_status_ =
fuchsia_power_broker::LeaseStatus::kPending;
}
});
}
servers_.emplace_back(std::move(element_control_server_end), std::move(lessor_binding),
std::move(current_level_binding), std::move(required_level_binding));
fuchsia_power_broker::TopologyAddElementResponse result{
{.element_control_channel = std::move(element_control_client_end)},
};
completer.Reply(fit::success(std::move(result)));
}
void handle_unknown_method(fidl::UnknownMethodMetadata<fuchsia_power_broker::Topology> md,
fidl::UnknownMethodCompleter::Sync& completer) override {}
FakeLessor* hardware_power_lessor_ = nullptr;
FakeCurrentLevel* hardware_power_current_level_ = nullptr;
FakeRequiredLevel* hardware_power_required_level_ = nullptr;
FakeLessor* wake_on_request_lessor_ = nullptr;
FakeCurrentLevel* wake_on_request_current_level_ = nullptr;
FakeRequiredLevel* wake_on_request_required_level_ = nullptr;
private:
fidl::ServerBindingGroup<fuchsia_power_broker::Topology> bindings_;
std::vector<PowerElement> servers_;
};
struct IncomingNamespace {
IncomingNamespace() {
zx::event::create(0, &exec_passive);
zx::event::create(0, &wake_active);
zx::event exec_passive_dupe, wake_active_dupe;
ASSERT_OK(exec_passive.duplicate(ZX_RIGHT_SAME_RIGHTS, &exec_passive_dupe));
ASSERT_OK(wake_active.duplicate(ZX_RIGHT_SAME_RIGHTS, &wake_active_dupe));
system_activity_governor.emplace(std::move(exec_passive_dupe), std::move(wake_active_dupe));
}
fdf_testing::TestNode node{"root"};
fdf_testing::TestEnvironment env{fdf::Dispatcher::GetCurrent()->get()};
fake_pdev::FakePDevFidl pdev_server;
FakeClock clock_server;
zx::event exec_passive, wake_active;
std::optional<FakeSystemActivityGovernor> system_activity_governor;
FakePowerBroker power_broker;
};
class AmlSdmmcTest : public zxtest::Test {
public:
AmlSdmmcTest()
: env_dispatcher_(runtime_.StartBackgroundDispatcher()),
incoming_(env_dispatcher_->async_dispatcher(), std::in_place),
mmio_buffer_(
fdf_testing::CreateMmioBuffer(S912_SD_EMMC_B_LENGTH, ZX_CACHE_POLICY_UNCACHED_DEVICE)) {
mmio_.emplace(mmio_buffer_.View(0));
}
void StartDriver(bool create_fake_bti_with_paddrs = false, bool supply_power_framework = false) {
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;
if (create_fake_bti_with_paddrs) {
ASSERT_OK(fake_bti_create_with_paddrs(bti_paddrs_, std::size(bti_paddrs_),
bti.reset_and_get_address()));
} else {
ASSERT_OK(fake_bti_create(bti.reset_and_get_address()));
}
// Initialize driver test environment.
fuchsia_driver_framework::DriverStartArgs start_args;
incoming_.SyncCall([&, bti = std::move(bti)](IncomingNamespace* incoming) mutable {
auto start_args_result = incoming->node.CreateStartArgsAndServe();
ASSERT_TRUE(start_args_result.is_ok());
start_args = std::move(start_args_result->start_args);
outgoing_directory_client_ = std::move(start_args_result->outgoing_directory_client);
ASSERT_OK(incoming->env.Initialize(std::move(start_args_result->incoming_directory_server)));
// Serve (fake) pdev_server.
fake_pdev::FakePDevFidl::Config config;
config.use_fake_irq = true;
zx::vmo dup;
mmio_buffer_.get_vmo()->duplicate(ZX_RIGHT_SAME_RIGHTS, &dup);
config.mmios[0] =
fake_pdev::MmioInfo{std::move(dup), mmio_buffer_.get_offset(), mmio_buffer_.get_size()};
config.btis[0] = std::move(bti);
config.device_info = pdev_device_info_t{};
if (supply_power_framework) {
config.power_elements = GetAllPowerConfigs();
}
incoming->pdev_server.SetConfig(std::move(config));
{
auto result = incoming->env.incoming_directory()
.AddService<fuchsia_hardware_platform_device::Service>(
std::move(incoming->pdev_server.GetInstanceHandler(
fdf::Dispatcher::GetCurrent()->async_dispatcher())),
"default");
ASSERT_TRUE(result.is_ok());
}
// Serve (fake) clock_server.
{
auto result =
incoming->env.incoming_directory().AddService<fuchsia_hardware_clock::Service>(
std::move(incoming->clock_server.GetInstanceHandler()), "clock-gate");
ASSERT_TRUE(result.is_ok());
}
if (supply_power_framework) {
// Serve (fake) system_activity_governor.
{
auto result = incoming->env.incoming_directory()
.component()
.AddUnmanagedProtocol<fuchsia_power_system::ActivityGovernor>(
incoming->system_activity_governor->CreateHandler());
ASSERT_TRUE(result.is_ok());
}
// Serve (fake) power_broker.
{
auto result = incoming->env.incoming_directory()
.component()
.AddUnmanagedProtocol<fuchsia_power_broker::Topology>(
incoming->power_broker.CreateHandler());
ASSERT_TRUE(result.is_ok());
}
}
});
// Start dut_.
ASSERT_OK(runtime_.RunToCompletion(dut_.Start(std::move(start_args))));
descs_ = dut_->SetTestHooks();
mmio_->Write32(0xff, kAmlSdmmcDelay1Offset);
mmio_->Write32(0xff, kAmlSdmmcDelay2Offset);
mmio_->Write32(0xff, kAmlSdmmcAdjustOffset);
ASSERT_OK(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.
}
void TearDown() override {
zx::result prepare_stop_result = runtime_.RunToCompletion(dut_.PrepareStop());
EXPECT_OK(prepare_stop_result.status_value());
incoming_.reset();
runtime_.ShutdownAllDispatchers(fdf::Dispatcher::GetCurrent()->get());
EXPECT_OK(dut_.Stop());
}
fidl::ClientEnd<fuchsia_io::Directory> CreateDriverSvcClient() {
fidl::ClientEnd<fuchsia_io::Directory> client_end;
[&]() {
// Open the svc directory in the driver's outgoing, and store a client to it.
auto [svc_client_end, svc_server_end] = fidl::Endpoints<fuchsia_io::Directory>::Create();
zx_status_t status = fdio_open_at(outgoing_directory_client_.handle()->get(), "/svc",
static_cast<uint32_t>(fuchsia_io::OpenFlags::kDirectory),
svc_server_end.TakeChannel().release());
ASSERT_EQ(ZX_OK, status);
client_end = std::move(svc_client_end);
}();
return client_end;
}
fdf::WireSyncClient<fuchsia_hardware_sdmmc::Sdmmc> GetClient() {
fdf::WireSyncClient<fuchsia_hardware_sdmmc::Sdmmc> client;
[&]() {
zx::result sdmmc_client_end =
fdf::internal::DriverTransportConnect<fuchsia_hardware_sdmmc::SdmmcService::Sdmmc>(
CreateDriverSvcClient(), component::kDefaultInstance);
ASSERT_TRUE(sdmmc_client_end.is_ok());
client.Bind(std::move(*sdmmc_client_end));
}();
return client;
}
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 (see comment above).
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) {
// Start at 1 (see comment above).
for (size_t i = 0; i < vmos; i++) {
bti_paddrs_[i + 1] = zx_system_get_page_size() * (i + 1) * 2;
}
}
fuchsia_hardware_power::PowerElementConfiguration GetHardwarePowerConfig() {
auto transitions_from_off =
std::vector<fuchsia_hardware_power::Transition>{fuchsia_hardware_power::Transition{{
.target_level = AmlSdmmc::kPowerLevelOn,
.latency_us = 100,
}}};
auto transitions_from_on =
std::vector<fuchsia_hardware_power::Transition>{fuchsia_hardware_power::Transition{{
.target_level = AmlSdmmc::kPowerLevelOff,
.latency_us = 200,
}}};
fuchsia_hardware_power::PowerLevel off = {
{.level = AmlSdmmc::kPowerLevelOff, .name = "off", .transitions = transitions_from_off}};
fuchsia_hardware_power::PowerLevel on = {
{.level = AmlSdmmc::kPowerLevelOn, .name = "on", .transitions = transitions_from_on}};
fuchsia_hardware_power::PowerElement hardware_power = {{
.name = AmlSdmmc::kHardwarePowerElementName,
.levels = {{off, on}},
}};
fuchsia_hardware_power::LevelTuple on_to_wake_handling = {{
.child_level = AmlSdmmc::kPowerLevelOn,
.parent_level =
static_cast<uint8_t>(fuchsia_power_system::ExecutionStateLevel::kWakeHandling),
}};
fuchsia_hardware_power::PowerDependency passive_on_exec_state_wake_handling = {{
.child = AmlSdmmc::kHardwarePowerElementName,
.parent = fuchsia_hardware_power::ParentElement::WithSag(
fuchsia_hardware_power::SagElement::kExecutionState),
.level_deps = {{on_to_wake_handling}},
.strength = fuchsia_hardware_power::RequirementType::kPassive,
}};
fuchsia_hardware_power::PowerElementConfiguration hardware_power_config = {
{.element = hardware_power, .dependencies = {{passive_on_exec_state_wake_handling}}}};
return hardware_power_config;
}
fuchsia_hardware_power::PowerElementConfiguration GetSystemWakeOnRequestPowerConfig() {
auto transitions_from_off =
std::vector<fuchsia_hardware_power::Transition>{fuchsia_hardware_power::Transition{{
.target_level = AmlSdmmc::kPowerLevelOn,
.latency_us = 0,
}}};
auto transitions_from_on =
std::vector<fuchsia_hardware_power::Transition>{fuchsia_hardware_power::Transition{{
.target_level = AmlSdmmc::kPowerLevelOff,
.latency_us = 0,
}}};
fuchsia_hardware_power::PowerLevel off = {
{.level = AmlSdmmc::kPowerLevelOff, .name = "off", .transitions = transitions_from_off}};
fuchsia_hardware_power::PowerLevel on = {
{.level = AmlSdmmc::kPowerLevelOn, .name = "on", .transitions = transitions_from_on}};
fuchsia_hardware_power::PowerElement wake_on_request = {{
.name = AmlSdmmc::kSystemWakeOnRequestPowerElementName,
.levels = {{off, on}},
}};
fuchsia_hardware_power::LevelTuple on_to_active = {{
.child_level = AmlSdmmc::kPowerLevelOn,
.parent_level = static_cast<uint8_t>(fuchsia_power_system::WakeHandlingLevel::kActive),
}};
fuchsia_hardware_power::PowerDependency active_on_wake_handling_active = {{
.child = AmlSdmmc::kSystemWakeOnRequestPowerElementName,
.parent = fuchsia_hardware_power::ParentElement::WithSag(
fuchsia_hardware_power::SagElement::kWakeHandling),
.level_deps = {{on_to_active}},
.strength = fuchsia_hardware_power::RequirementType::kActive,
}};
fuchsia_hardware_power::PowerElementConfiguration wake_on_request_config = {
{.element = wake_on_request, .dependencies = {{active_on_wake_handling_active}}}};
return wake_on_request_config;
}
std::vector<fuchsia_hardware_power::PowerElementConfiguration> GetAllPowerConfigs() {
return std::vector<fuchsia_hardware_power::PowerElementConfiguration>{
GetHardwarePowerConfig(), GetSystemWakeOnRequestPowerConfig()};
}
aml_sdmmc_desc_t* descriptors() const { return reinterpret_cast<aml_sdmmc_desc_t*>(descs_); }
zx_paddr_t bti_paddrs_[64] = {};
std::optional<fdf::MmioView> mmio_;
fdf_testing::DriverRuntime runtime_;
fdf::UnownedSynchronizedDispatcher env_dispatcher_;
async_patterns::TestDispatcherBound<IncomingNamespace> incoming_;
fidl::ClientEnd<fuchsia_io::Directory> outgoing_directory_client_;
fdf_testing::DriverUnderTest<TestAmlSdmmc> dut_;
private:
fdf::MmioBuffer mmio_buffer_;
void* descs_ = nullptr;
};
TEST_F(AmlSdmmcTest, Init) {
StartDriver();
AmlSdmmcClock::Get().FromValue(0).WriteTo(&*mmio_);
ASSERT_OK(dut_->Init({}));
EXPECT_EQ(AmlSdmmcClock::Get().ReadFrom(&*mmio_).reg_value(), AmlSdmmcClock::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, Tuning) {
StartDriver();
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);
adjust.set_adj_fixed(0).set_adj_delay(0x3f).WriteTo(&*mmio_);
EXPECT_OK(dut_->SdmmcPerformTuning(SD_SEND_TUNING_BLOCK));
adjust.ReadFrom(&*mmio_);
EXPECT_EQ(adjust.adj_fixed(), 1);
EXPECT_EQ(adjust.adj_delay(), 0);
}
TEST_F(AmlSdmmcTest, DelayLineTuningAllPass) {
StartDriver();
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_);
// No failing window was found, so the default settings should be used.
EXPECT_EQ(adjust.adj_delay(), 0);
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);
dut_->ExpectInspectPropertyValue("adj_delay", 0);
dut_->ExpectInspectPropertyValue("delay_lines", 0);
dut_->ExpectInspectPropertyValue(
"tuning_results_adj_delay_0", "tuning_results",
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||");
dut_->ExpectInspectPropertyValue("distance_to_failing_point", 63);
}
TEST_F(AmlSdmmcTest, DelayLineTuningFailingPoint) {
StartDriver();
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_);
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(), 30);
EXPECT_EQ(delay1.dly_1(), 30);
EXPECT_EQ(delay1.dly_2(), 30);
EXPECT_EQ(delay1.dly_3(), 30);
EXPECT_EQ(delay1.dly_4(), 30);
EXPECT_EQ(delay2.dly_5(), 30);
EXPECT_EQ(delay2.dly_6(), 30);
EXPECT_EQ(delay2.dly_7(), 30);
EXPECT_EQ(delay2.dly_8(), 30);
EXPECT_EQ(delay2.dly_9(), 30);
dut_->ExpectInspectPropertyValue("adj_delay", 7);
dut_->ExpectInspectPropertyValue("delay_lines", 30);
dut_->ExpectInspectPropertyValue(
"tuning_results_adj_delay_7", "tuning_results",
"-------------------------------|||||||||||||||||||||||||||||||||");
dut_->ExpectInspectPropertyValue("distance_to_failing_point", 0);
}
TEST_F(AmlSdmmcTest, DelayLineTuningEvenDivider) {
StartDriver();
dut_->SetRequestResults(
// Largest failing window: adj_delay 8, middle delay 25
"||||||||||||||||||||||||||||||||||||||||||||||||||--------------"
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
"-|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
"---------------------|||||||||||||||||||||||||||||||||||||||||||"
"||||||||||-------------------------------|||||||||||||||||||||||"
"||||||||||||||||||||||||||||||-------------------------------|||");
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);
dut_->ExpectInspectPropertyValue("adj_delay", 3);
dut_->ExpectInspectPropertyValue("delay_lines", 25);
dut_->ExpectInspectPropertyValue(
"tuning_results_adj_delay_4", "tuning_results",
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||");
dut_->ExpectInspectPropertyValue("distance_to_failing_point", 63);
}
TEST_F(AmlSdmmcTest, DelayLineTuningOddDivider) {
StartDriver();
dut_->SetRequestResults(
// Largest failing window: adj_delay 3, first delay 0
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
"-----------|||||||||||||||||||||||||||||||||||||||||||||||||||||"
"-------------------------------|||||||||||||||||||||||||||||||||"
"||||||||||||||||||||-------------------------------|||||||||||||"
"||||||||||||||||||||||||||||||||||||||||------------------------"
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||----"
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||");
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);
dut_->ExpectInspectPropertyValue("adj_delay", 7);
dut_->ExpectInspectPropertyValue("delay_lines", 0);
dut_->ExpectInspectPropertyValue(
"tuning_results_adj_delay_7", "tuning_results",
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||");
dut_->ExpectInspectPropertyValue("distance_to_failing_point", 63);
}
TEST_F(AmlSdmmcTest, DelayLineTuningCorrectFailingWindowIfLastOne) {
StartDriver();
dut_->SetRequestResults(
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||----"
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||");
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_);
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);
dut_->ExpectInspectPropertyValue("adj_delay", 2);
dut_->ExpectInspectPropertyValue("delay_lines", 60);
}
TEST_F(AmlSdmmcTest, SetBusFreq) {
StartDriver();
ASSERT_OK(dut_->Init({}));
dut_->ExpectInspectPropertyValue("bus_clock_frequency", 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);
dut_->ExpectInspectPropertyValue("bus_clock_frequency", 100'000'000);
EXPECT_OK(dut_->SdmmcSetBusFreq(0));
EXPECT_EQ(clock.ReadFrom(&*mmio_).cfg_div(), 0);
dut_->ExpectInspectPropertyValue("bus_clock_frequency", 0);
EXPECT_OK(dut_->SdmmcSetBusFreq(54'000'000));
EXPECT_EQ(clock.ReadFrom(&*mmio_).cfg_div(), 19);
EXPECT_EQ(clock.cfg_src(), 1);
dut_->ExpectInspectPropertyValue("bus_clock_frequency", 52'631'578);
EXPECT_OK(dut_->SdmmcSetBusFreq(400'000));
EXPECT_EQ(clock.ReadFrom(&*mmio_).cfg_div(), 60);
EXPECT_EQ(clock.cfg_src(), 0);
dut_->ExpectInspectPropertyValue("bus_clock_frequency", 400'000);
}
TEST_F(AmlSdmmcTest, ClearStatus) {
StartDriver();
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));
uint32_t unused_response[4];
EXPECT_OK(dut_->SdmmcRequest(&request, unused_response));
auto status = AmlSdmmcStatus::Get().FromValue(0);
EXPECT_EQ(AmlSdmmcStatus::kClearStatus, status.ReadFrom(&*mmio_).reg_value());
}
TEST_F(AmlSdmmcTest, TxCrcError) {
StartDriver();
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));
uint32_t unused_response[4];
EXPECT_EQ(ZX_ERR_IO_DATA_INTEGRITY, dut_->SdmmcRequest(&request, unused_response));
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, UnownedVmosBlockMode) {
StartDriver(/*create_fake_bti_with_paddrs=*/true);
InitializeContiguousPaddrs(10);
ASSERT_OK(dut_->Init({}));
zx::vmo vmos[10] = {};
sdmmc_buffer_region_t buffers[10];
for (uint32_t i = 0; i < std::size(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_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 = std::size(buffers),
};
uint32_t response[4] = {};
AmlSdmmcCmdResp::Get().FromValue(0xfedc9876).WriteTo(&*mmio_);
EXPECT_OK(dut_->SdmmcRequest(&request, response));
EXPECT_EQ(response[0], 0xfedc9876);
const aml_sdmmc_desc_t* descs = descriptors();
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 < std::size(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 == std::size(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) {
StartDriver(/*create_fake_bti_with_paddrs=*/true);
InitializeContiguousPaddrs(10);
ASSERT_OK(dut_->Init({}));
zx::vmo vmos[10] = {};
sdmmc_buffer_region_t buffers[10];
for (uint32_t i = 0; i < std::size(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_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 = std::size(buffers),
};
uint32_t response[4] = {};
EXPECT_NOT_OK(dut_->SdmmcRequest(&request, response));
}
TEST_F(AmlSdmmcTest, UnownedVmosByteMode) {
StartDriver(/*create_fake_bti_with_paddrs=*/true);
InitializeContiguousPaddrs(10);
ASSERT_OK(dut_->Init({}));
zx::vmo vmos[10] = {};
sdmmc_buffer_region_t buffers[10];
for (uint32_t i = 0; i < std::size(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_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 = std::size(buffers),
};
uint32_t response[4] = {};
AmlSdmmcCmdResp::Get().FromValue(0xfedc9876).WriteTo(&*mmio_);
EXPECT_OK(dut_->SdmmcRequest(&request, response));
EXPECT_EQ(response[0], 0xfedc9876);
const aml_sdmmc_desc_t* descs = descriptors();
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 < std::size(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 == std::size(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) {
StartDriver(/*create_fake_bti_with_paddrs=*/true);
InitializeContiguousPaddrs(1);
ASSERT_OK(dut_->Init({}));
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &vmo));
sdmmc_buffer_region_t buffer = {
.buffer =
{
.vmo = vmo.get(),
},
.type = SDMMC_BUFFER_TYPE_VMO_HANDLE,
.offset = 0,
.size = 400,
};
sdmmc_req_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_->SdmmcRequest(&request, response));
EXPECT_EQ(response[0], 0xfedc9876);
const aml_sdmmc_desc_t* descs = descriptors();
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) {
StartDriver(/*create_fake_bti_with_paddrs=*/true);
InitializeContiguousPaddrs(1);
ASSERT_OK(dut_->Init({}));
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &vmo));
sdmmc_buffer_region_t buffer = {
.buffer =
{
.vmo = vmo.get(),
},
.type = SDMMC_BUFFER_TYPE_VMO_HANDLE,
.offset = 3,
.size = 64,
};
sdmmc_req_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_->SdmmcRequest(&request, response));
}
TEST_F(AmlSdmmcTest, UnownedVmoSingleBufferMultipleDescriptors) {
StartDriver(/*create_fake_bti_with_paddrs=*/true);
const size_t pages = ((32 * 514) / zx_system_get_page_size()) + 1;
InitializeSingleVmoPaddrs(pages);
ASSERT_OK(dut_->Init({}));
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(pages * zx_system_get_page_size(), 0, &vmo));
sdmmc_buffer_region_t buffer = {
.buffer =
{
.vmo = vmo.get(),
},
.type = SDMMC_BUFFER_TYPE_VMO_HANDLE,
.offset = 16,
.size = 32 * 513,
};
sdmmc_req_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_->SdmmcRequest(&request, response));
EXPECT_EQ(response[0], 0xfedc9876);
const aml_sdmmc_desc_t* descs = descriptors();
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) {
StartDriver(/*create_fake_bti_with_paddrs=*/true);
const size_t pages = ((32 * 514) / zx_system_get_page_size()) + 1;
InitializeNonContiguousPaddrs(pages);
ASSERT_OK(dut_->Init({}));
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(pages * zx_system_get_page_size(), 0, &vmo));
sdmmc_buffer_region_t buffer = {
.buffer =
{
.vmo = vmo.get(),
},
.type = SDMMC_BUFFER_TYPE_VMO_HANDLE,
.offset = 16,
.size = 32 * 513,
};
sdmmc_req_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_->SdmmcRequest(&request, response));
}
TEST_F(AmlSdmmcTest, UnownedVmoSingleBufferPageAligned) {
StartDriver(/*create_fake_bti_with_paddrs=*/true);
const size_t pages = ((32 * 514) / zx_system_get_page_size()) + 1;
InitializeNonContiguousPaddrs(pages);
ASSERT_OK(dut_->Init({}));
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(pages * zx_system_get_page_size(), 0, &vmo));
sdmmc_buffer_region_t buffer = {
.buffer =
{
.vmo = vmo.get(),
},
.type = SDMMC_BUFFER_TYPE_VMO_HANDLE,
.offset = 32,
.size = 32 * 513,
};
sdmmc_req_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_->SdmmcRequest(&request, response));
EXPECT_EQ(response[0], 0xfedc9876);
const aml_sdmmc_desc_t* descs = descriptors();
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) {
StartDriver(/*create_fake_bti_with_paddrs=*/true);
InitializeContiguousPaddrs(10);
ASSERT_OK(dut_->Init({}));
sdmmc_buffer_region_t buffers[10];
for (uint32_t i = 0; i < std::size(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_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 = std::size(buffers),
};
uint32_t response[4] = {};
AmlSdmmcCmdResp::Get().FromValue(0xfedc9876).WriteTo(&*mmio_);
EXPECT_OK(dut_->SdmmcRequest(&request, response));
EXPECT_EQ(response[0], 0xfedc9876);
const aml_sdmmc_desc_t* descs = descriptors();
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 < std::size(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 == std::size(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_->SdmmcRequest(&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_->SdmmcRequest(&request, response));
}
TEST_F(AmlSdmmcTest, OwnedVmosNotBlockSizeMultiple) {
StartDriver(/*create_fake_bti_with_paddrs=*/true);
InitializeContiguousPaddrs(10);
ASSERT_OK(dut_->Init({}));
sdmmc_buffer_region_t buffers[10];
for (uint32_t i = 0; i < std::size(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_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 = std::size(buffers),
};
uint32_t response[4] = {};
EXPECT_NOT_OK(dut_->SdmmcRequest(&request, response));
}
TEST_F(AmlSdmmcTest, OwnedVmosByteMode) {
StartDriver(/*create_fake_bti_with_paddrs=*/true);
InitializeContiguousPaddrs(10);
ASSERT_OK(dut_->Init({}));
sdmmc_buffer_region_t buffers[10];
for (uint32_t i = 0; i < std::size(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_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 = std::size(buffers),
};
uint32_t response[4] = {};
AmlSdmmcCmdResp::Get().FromValue(0xfedc9876).WriteTo(&*mmio_);
EXPECT_OK(dut_->SdmmcRequest(&request, response));
EXPECT_EQ(response[0], 0xfedc9876);
const aml_sdmmc_desc_t* descs = descriptors();
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 < std::size(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 == std::size(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) {
StartDriver(/*create_fake_bti_with_paddrs=*/true);
InitializeContiguousPaddrs(1);
ASSERT_OK(dut_->Init({}));
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &vmo));
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_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_->SdmmcRequest(&request, response));
EXPECT_EQ(response[0], 0xfedc9876);
const aml_sdmmc_desc_t* descs = descriptors();
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) {
StartDriver(/*create_fake_bti_with_paddrs=*/true);
InitializeContiguousPaddrs(1);
ASSERT_OK(dut_->Init({}));
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &vmo));
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_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_->SdmmcRequest(&request, response));
}
TEST_F(AmlSdmmcTest, OwnedVmoSingleBufferMultipleDescriptors) {
StartDriver(/*create_fake_bti_with_paddrs=*/true);
const size_t pages = ((32 * 514) / zx_system_get_page_size()) + 1;
InitializeSingleVmoPaddrs(pages);
ASSERT_OK(dut_->Init({}));
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(pages * zx_system_get_page_size(), 0, &vmo));
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_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_->SdmmcRequest(&request, response));
EXPECT_EQ(response[0], 0xfedc9876);
const aml_sdmmc_desc_t* descs = descriptors();
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) {
StartDriver(/*create_fake_bti_with_paddrs=*/true);
const size_t pages = ((32 * 514) / zx_system_get_page_size()) + 1;
InitializeNonContiguousPaddrs(pages);
ASSERT_OK(dut_->Init({}));
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(pages * zx_system_get_page_size(), 0, &vmo));
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_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_->SdmmcRequest(&request, response));
}
TEST_F(AmlSdmmcTest, OwnedVmoSingleBufferPageAligned) {
StartDriver(/*create_fake_bti_with_paddrs=*/true);
const size_t pages = ((32 * 514) / zx_system_get_page_size()) + 1;
InitializeNonContiguousPaddrs(pages);
ASSERT_OK(dut_->Init({}));
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(pages * zx_system_get_page_size(), 0, &vmo));
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_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_->SdmmcRequest(&request, response));
EXPECT_EQ(response[0], 0xfedc9876);
const aml_sdmmc_desc_t* descs = descriptors();
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) {
StartDriver(/*create_fake_bti_with_paddrs=*/true);
const size_t pages = ((32 * 514) / zx_system_get_page_size()) + 1;
InitializeNonContiguousPaddrs(pages);
ASSERT_OK(dut_->Init({}));
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(pages * zx_system_get_page_size(), 0, &vmo));
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_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_->SdmmcRequest(&request, response));
EXPECT_EQ(response[0], 0xfedc9876);
const aml_sdmmc_desc_t* descs = descriptors();
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_->SdmmcRequest(&request, response));
}
TEST_F(AmlSdmmcTest, SeparateClientVmoSpaces) {
StartDriver();
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) {
StartDriver(/*create_fake_bti_with_paddrs=*/true);
InitializeContiguousPaddrs(10);
ASSERT_OK(dut_->Init({}));
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_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 = std::size(buffers),
};
uint32_t response[4] = {};
AmlSdmmcCmdResp::Get().FromValue(0xfedc9876).WriteTo(&*mmio_);
EXPECT_OK(dut_->SdmmcRequest(&request, response));
EXPECT_EQ(response[0], 0xfedc9876);
const aml_sdmmc_desc_t* descs = descriptors();
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) {
StartDriver(/*create_fake_bti_with_paddrs=*/true);
ASSERT_OK(dut_->Init({}));
// Start at 1 because one paddr has already been read to create the DMA descriptor buffer.
bti_paddrs_[1] = 0x1897'7000;
bti_paddrs_[2] = 0x1997'8000;
bti_paddrs_[3] = 0x1997'e000;
// Make sure the appropriate cmd_info bits get cleared.
descriptors()[0].cmd_info = 0xffff'ffff;
descriptors()[1].cmd_info = 0xffff'ffff;
descriptors()[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_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_->SdmmcRequest(&request, response));
EXPECT_EQ(AmlSdmmcCfg::Get().ReadFrom(&*mmio_).blk_len(), 9);
const aml_sdmmc_desc_t* descs = descriptors();
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) {
StartDriver(/*create_fake_bti_with_paddrs=*/true);
InitializeContiguousPaddrs(10);
ASSERT_OK(dut_->Init({}));
sdmmc_buffer_region_t buffers[10];
for (uint32_t i = 0; i < std::size(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_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 = std::size(buffers),
};
uint32_t response[4] = {};
EXPECT_NOT_OK(dut_->SdmmcRequest(&request, response));
}
TEST_F(AmlSdmmcTest, ReadFromWriteOnlyVmo) {
StartDriver(/*create_fake_bti_with_paddrs=*/true);
InitializeContiguousPaddrs(10);
ASSERT_OK(dut_->Init({}));
sdmmc_buffer_region_t buffers[10];
for (uint32_t i = 0; i < std::size(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_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 = std::size(buffers),
};
uint32_t response[4] = {};
EXPECT_NOT_OK(dut_->SdmmcRequest(&request, response));
}
TEST_F(AmlSdmmcTest, ConsecutiveErrorLogging) {
StartDriver();
ASSERT_OK(dut_->Init({}));
// First data error.
dut_->SetRequestInterruptStatus(1 << 8);
sdmmc_req_t request;
memset(&request, 0, sizeof(request));
uint32_t unused_response[4];
EXPECT_EQ(ZX_ERR_IO_DATA_INTEGRITY, dut_->SdmmcRequest(&request, unused_response));
// First cmd error.
dut_->SetRequestInterruptStatus(1 << 11);
memset(&request, 0, sizeof(request));
EXPECT_EQ(ZX_ERR_TIMED_OUT, dut_->SdmmcRequest(&request, unused_response));
// Second data error.
dut_->SetRequestInterruptStatus(1 << 7);
memset(&request, 0, sizeof(request));
EXPECT_EQ(ZX_ERR_IO_DATA_INTEGRITY, dut_->SdmmcRequest(&request, unused_response));
// Second cmd error.
dut_->SetRequestInterruptStatus(1 << 11);
memset(&request, 0, sizeof(request));
EXPECT_EQ(ZX_ERR_TIMED_OUT, dut_->SdmmcRequest(&request, unused_response));
zx::vmo vmo;
EXPECT_OK(zx::vmo::create(32, 0, &vmo));
// cmd/data goes through.
const sdmmc_buffer_region_t region{
.buffer = {.vmo = vmo.get()},
.type = SDMMC_BUFFER_TYPE_VMO_HANDLE,
.offset = 0,
.size = 32,
};
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.buffers_list = &region;
request.buffers_count = 1;
EXPECT_OK(dut_->SdmmcRequest(&request, unused_response));
// Third data error.
dut_->SetRequestInterruptStatus(1 << 7);
memset(&request, 0, sizeof(request));
EXPECT_EQ(ZX_ERR_IO_DATA_INTEGRITY, dut_->SdmmcRequest(&request, unused_response));
// Third cmd error.
dut_->SetRequestInterruptStatus(1 << 11);
memset(&request, 0, sizeof(request));
EXPECT_EQ(ZX_ERR_TIMED_OUT, dut_->SdmmcRequest(&request, unused_response));
}
TEST_F(AmlSdmmcTest, PowerSuspendResume) {
StartDriver(/*create_fake_bti_with_paddrs=*/false, /*supply_power_framework=*/true);
auto clock = AmlSdmmcClock::Get().FromValue(0).WriteTo(&*mmio_);
ASSERT_OK(dut_->Init({}));
// Initial power level is kPowerLevelOff.
runtime_.PerformBlockingWork([&] {
bool clock_enabled;
do {
clock_enabled = incoming_.SyncCall(
[](IncomingNamespace* incoming) { return incoming->clock_server.enabled(); });
} while (clock_enabled);
});
dut_->ExpectInspectBoolPropertyValue("power_suspended", true);
dut_->ExpectInspectPropertyValue("wake_on_request_count", 0);
EXPECT_EQ(clock.ReadFrom(&*mmio_).cfg_div(), 0);
EXPECT_FALSE(incoming_.SyncCall(
[](IncomingNamespace* incoming) { return incoming->clock_server.enabled(); }));
// Trigger power level change to kPowerLevelOn.
incoming_.SyncCall([](IncomingNamespace* incoming) {
incoming->power_broker.hardware_power_lessor_->lease_control_->lease_status_ =
fuchsia_power_broker::LeaseStatus::kSatisfied;
});
runtime_.PerformBlockingWork([&] {
bool clock_enabled;
do {
clock_enabled = incoming_.SyncCall(
[](IncomingNamespace* incoming) { return incoming->clock_server.enabled(); });
} while (!clock_enabled);
});
dut_->ExpectInspectBoolPropertyValue("power_suspended", false);
dut_->ExpectInspectPropertyValue("wake_on_request_count", 0);
EXPECT_NE(clock.ReadFrom(&*mmio_).cfg_div(), 0);
EXPECT_TRUE(incoming_.SyncCall(
[](IncomingNamespace* incoming) { return incoming->clock_server.enabled(); }));
// Trigger power level change to kPowerLevelOff.
incoming_.SyncCall([](IncomingNamespace* incoming) {
incoming->power_broker.hardware_power_lessor_->lease_control_->lease_status_ =
fuchsia_power_broker::LeaseStatus::kPending;
});
runtime_.PerformBlockingWork([&] {
bool clock_enabled;
do {
clock_enabled = incoming_.SyncCall(
[](IncomingNamespace* incoming) { return incoming->clock_server.enabled(); });
} while (clock_enabled);
});
dut_->ExpectInspectBoolPropertyValue("power_suspended", true);
dut_->ExpectInspectPropertyValue("wake_on_request_count", 0);
EXPECT_EQ(clock.ReadFrom(&*mmio_).cfg_div(), 0);
EXPECT_FALSE(incoming_.SyncCall(
[](IncomingNamespace* incoming) { return incoming->clock_server.enabled(); }));
}
TEST_F(AmlSdmmcTest, WakeOnRequest) {
StartDriver(/*create_fake_bti_with_paddrs=*/false, /*supply_power_framework=*/true);
auto clock = AmlSdmmcClock::Get().FromValue(0).WriteTo(&*mmio_);
ASSERT_OK(dut_->Init({}));
// Initial power level is kPowerLevelOff.
runtime_.PerformBlockingWork([&] {
bool clock_enabled;
do {
clock_enabled = incoming_.SyncCall(
[](IncomingNamespace* incoming) { return incoming->clock_server.enabled(); });
} while (clock_enabled);
});
auto check_power_suspended = [&](int wake_on_request_count) {
dut_->ExpectInspectBoolPropertyValue("power_suspended", true);
dut_->ExpectInspectPropertyValue("wake_on_request_count", wake_on_request_count);
EXPECT_EQ(clock.ReadFrom(&*mmio_).cfg_div(), 0);
EXPECT_FALSE(incoming_.SyncCall(
[](IncomingNamespace* incoming) { return incoming->clock_server.enabled(); }));
};
check_power_suspended(0);
fdf::WireSyncClient<fuchsia_hardware_sdmmc::Sdmmc> client = GetClient();
// Issue request while power is suspended.
zx::vmo vmo, dup;
ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &vmo));
ASSERT_OK(vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &dup));
fuchsia_hardware_sdmmc::wire::SdmmcBufferRegion buffer_region{
.buffer = fuchsia_hardware_sdmmc::wire::SdmmcBuffer::WithVmo(std::move(dup)),
.offset = 0,
.size = zx_system_get_page_size(),
};
fuchsia_hardware_sdmmc::wire::SdmmcReq 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 = fidl::VectorView<fuchsia_hardware_sdmmc::wire::SdmmcBufferRegion>::FromExternal(
&buffer_region, 1),
};
auto requests =
fidl::VectorView<fuchsia_hardware_sdmmc::wire::SdmmcReq>::FromExternal(&request, 1);
runtime_.PerformBlockingWork([&] {
fdf::Arena arena('SDMM');
const auto result = client.buffer(arena)->Request(requests);
EXPECT_OK(result);
bool clock_enabled;
do {
clock_enabled = incoming_.SyncCall(
[](IncomingNamespace* incoming) { return incoming->clock_server.enabled(); });
} while (clock_enabled);
});
check_power_suspended(1);
// Issue SetBusWidth while power is suspended.
runtime_.PerformBlockingWork([&] {
fdf::Arena arena('SDMM');
const auto result =
client.buffer(arena)->SetBusWidth(fuchsia_hardware_sdmmc::wire::SdmmcBusWidth::kFour);
EXPECT_OK(result);
bool clock_enabled;
do {
clock_enabled = incoming_.SyncCall(
[](IncomingNamespace* incoming) { return incoming->clock_server.enabled(); });
} while (clock_enabled);
});
check_power_suspended(2);
// Issue SetBusFreq while power is suspended.
runtime_.PerformBlockingWork([&] {
fdf::Arena arena('SDMM');
const auto result = client.buffer(arena)->SetBusFreq(100'000'000);
EXPECT_OK(result);
bool clock_enabled;
do {
clock_enabled = incoming_.SyncCall(
[](IncomingNamespace* incoming) { return incoming->clock_server.enabled(); });
} while (clock_enabled);
});
check_power_suspended(3);
// Issue SetTiming while power is suspended.
runtime_.PerformBlockingWork([&] {
fdf::Arena arena('SDMM');
const auto result =
client.buffer(arena)->SetTiming(fuchsia_hardware_sdmmc::wire::SdmmcTiming::kHs400);
EXPECT_OK(result);
bool clock_enabled;
do {
clock_enabled = incoming_.SyncCall(
[](IncomingNamespace* incoming) { return incoming->clock_server.enabled(); });
} while (clock_enabled);
});
check_power_suspended(4);
// Issue HwReset while power is suspended.
runtime_.PerformBlockingWork([&] {
fdf::Arena arena('SDMM');
const auto result = client.buffer(arena)->HwReset();
EXPECT_OK(result);
bool clock_enabled;
do {
clock_enabled = incoming_.SyncCall(
[](IncomingNamespace* incoming) { return incoming->clock_server.enabled(); });
} while (clock_enabled);
});
check_power_suspended(5);
// Issue PerformTuning while power is suspended.
runtime_.PerformBlockingWork([&] {
fdf::Arena arena('SDMM');
const auto result = client.buffer(arena)->PerformTuning(SD_SEND_TUNING_BLOCK);
EXPECT_OK(result);
bool clock_enabled;
do {
clock_enabled = incoming_.SyncCall(
[](IncomingNamespace* incoming) { return incoming->clock_server.enabled(); });
} while (clock_enabled);
});
check_power_suspended(6);
}
} // namespace aml_sdmmc
FUCHSIA_DRIVER_EXPORT(aml_sdmmc::TestAmlSdmmc);