blob: 9ec6c02565be6cd66105acdf99327ef10355ea1f [file] [log] [blame]
// Copyright 2022 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 "src/devices/usb/drivers/dwc3/dwc3.h"
#include <lib/async-loop/default.h>
#include <lib/async_patterns/testing/cpp/dispatcher_bound.h>
#include <lib/component/outgoing/cpp/outgoing_directory.h>
#include <fake-mmio-reg/fake-mmio-reg.h>
#include <zxtest/zxtest.h>
#include "src/devices/bus/testing/fake-pdev/fake-pdev.h"
#include "src/devices/testing/mock-ddk/mock-device.h"
#include "src/devices/usb/drivers/dwc3/dwc3-regs.h"
namespace dwc3 {
struct IncomingNamespace {
fake_pdev::FakePDevFidl pdev_server;
component::OutgoingDirectory outgoing{async_get_default_dispatcher()};
};
class TestFixture : public zxtest::Test {
public:
TestFixture();
void SetUp() override;
fdf::MmioBuffer mmio() { return reg_region_.GetMmioBuffer(); }
protected:
static constexpr size_t kRegSize = sizeof(uint32_t);
static constexpr size_t kMmioRegionSize = 64 << 10;
static constexpr size_t kRegCount = kMmioRegionSize / kRegSize;
// Section 1.3.22 of the DWC3 Programmer's guide
//
// DWC_USB31_CACHE_TOTAL_XFER_RESOURCES : 32
// DWC_USB31_NUM_IN_EPS : 16
// DWC_USB31_NUM_EPS : 32
// DWC_USB31_VENDOR_CTL_INTERFACE : 0
// DWC_USB31_HSPHY_DWIDTH : 2
// DWC_USB31_HSPHY_INTERFACE : 1
// DWC_USB31_SSPHY_INTERFACE : 2
//
uint64_t Read_GHWPARAMS3() { return 0x10420086; }
// Section 1.3.45 of the DWC3 Programmer's guide
uint64_t Read_USB31_VER_NUMBER() { return 0x31363061; } // 1.60a
// Section 1.4.2 of the DWC3 Programmer's guide
uint64_t Read_DCTL() { return dctl_val_; }
void Write_DCTL(uint64_t val) {
constexpr uint32_t kUnwriteableMask =
(1 << 29) | (1 << 17) | (1 << 16) | (1 << 15) | (1 << 14) | (1 << 13) | (1 << 0);
ZX_DEBUG_ASSERT(val <= std::numeric_limits<uint32_t>::max());
dctl_val_ = static_cast<uint32_t>(val & ~kUnwriteableMask);
// Immediately clear the soft reset bit if we are not testing the soft reset
// timeout behavior.
if (!stuck_reset_test_) {
dctl_val_ = DCTL::Get().FromValue(dctl_val_).set_CSFTRST(0).reg_value();
}
}
uint32_t dctl_val_ = DCTL::Get().FromValue(0).set_LPM_NYET_thres(0xF).reg_value();
bool stuck_reset_test_{false};
std::shared_ptr<MockDevice> mock_parent_{MockDevice::FakeRootParent()};
async::Loop incoming_loop_{&kAsyncLoopConfigNoAttachToCurrentThread};
async_patterns::TestDispatcherBound<IncomingNamespace> incoming_{incoming_loop_.dispatcher(),
std::in_place};
ddk_fake::FakeMmioRegRegion reg_region_{kRegSize, kRegCount};
};
TestFixture::TestFixture() {
auto& hwparams3 = reg_region_[GHWPARAMS3::Get().addr()];
auto& ver_reg = reg_region_[USB31_VER_NUMBER::Get().addr()];
auto& dctl_reg = reg_region_[DCTL::Get().addr()];
hwparams3.SetReadCallback([this]() -> uint64_t { return Read_GHWPARAMS3(); });
ver_reg.SetReadCallback([this]() -> uint64_t { return Read_USB31_VER_NUMBER(); });
dctl_reg.SetReadCallback([this]() -> uint64_t { return Read_DCTL(); });
dctl_reg.SetWriteCallback([this](uint64_t val) { return Write_DCTL(val); });
fake_pdev::FakePDevFidl::Config config;
config.mmios[0] = mmio();
config.use_fake_bti = true;
config.irqs[0] = {};
ASSERT_OK(zx::interrupt::create(zx::resource(), 0, ZX_INTERRUPT_VIRTUAL, &config.irqs[0]));
auto outgoing_endpoints = fidl::Endpoints<fuchsia_io::Directory>::Create();
ASSERT_OK(incoming_loop_.StartThread("incoming-ns-thread"));
incoming_.SyncCall([config = std::move(config), server = std::move(outgoing_endpoints.server)](
IncomingNamespace* infra) mutable {
infra->pdev_server.SetConfig(std::move(config));
ASSERT_OK(infra->outgoing.AddService<fuchsia_hardware_platform_device::Service>(
infra->pdev_server.GetInstanceHandler()));
ASSERT_OK(infra->outgoing.Serve(std::move(server)));
});
ASSERT_NO_FATAL_FAILURE();
mock_parent_->AddFidlService(fuchsia_hardware_platform_device::Service::Name,
std::move(outgoing_endpoints.client));
}
void TestFixture::SetUp() { stuck_reset_test_ = false; }
TEST_F(TestFixture, DdkLifecycle) {
ASSERT_OK(Dwc3::Create(nullptr, mock_parent_.get()));
// make sure the child device is there
ASSERT_EQ(1, mock_parent_->child_count());
auto* child = mock_parent_->GetLatestChild();
child->InitOp();
EXPECT_TRUE(child->InitReplyCalled());
EXPECT_OK(child->InitReplyCallStatus());
child->UnbindOp();
EXPECT_TRUE(child->UnbindReplyCalled());
child->ReleaseOp();
}
TEST_F(TestFixture, DdkHwResetTimeout) {
stuck_reset_test_ = true;
ASSERT_OK(Dwc3::Create(nullptr, mock_parent_.get()));
// make sure the child device is there
ASSERT_EQ(1, mock_parent_->child_count());
auto* child = mock_parent_->GetLatestChild();
child->InitOp();
EXPECT_TRUE(child->InitReplyCalled());
EXPECT_STATUS(ZX_ERR_TIMED_OUT, child->InitReplyCallStatus());
child->UnbindOp();
EXPECT_TRUE(child->UnbindReplyCalled());
child->ReleaseOp();
}
} // namespace dwc3