blob: aef8b021ae69dd15de80b77bfd11d88c3dcb777d [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 "src/devices/usb/drivers/aml-usb-phy/aml-usb-phy.h"
#include <fidl/fuchsia.hardware.platform.device/cpp/wire_test_base.h>
#include <lib/async_patterns/testing/cpp/dispatcher_bound.h>
#include <lib/ddk/metadata.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/zx/clock.h>
#include <lib/zx/interrupt.h>
#include <fake-mmio-reg/fake-mmio-reg.h>
#include <gtest/gtest.h>
#include <soc/aml-common/aml-registers.h>
#include "src/devices/registers/testing/mock-registers/mock-registers.h"
#include "src/devices/usb/drivers/aml-usb-phy/usb-phy-regs.h"
namespace aml_usb_phy {
constexpr auto kRegisterBanks = 4;
constexpr auto kRegisterCount = 2048;
class FakeMmio {
public:
FakeMmio() : region_(sizeof(uint32_t), kRegisterCount) {
for (size_t c = 0; c < kRegisterCount; c++) {
region_[c * sizeof(uint32_t)].SetReadCallback([this, c]() { return reg_values_[c]; });
region_[c * sizeof(uint32_t)].SetWriteCallback(
[this, c](uint64_t value) { reg_values_[c] = value; });
}
}
fdf::MmioBuffer mmio() { return region_.GetMmioBuffer(); }
uint64_t reg_values_[kRegisterCount] = {0};
private:
ddk_fake::FakeMmioRegRegion region_;
};
class FakePDev : public fidl::testing::WireTestBase<fuchsia_hardware_platform_device::Device> {
public:
FakePDev() {
EXPECT_EQ(ZX_OK, zx::interrupt::create(zx::resource(), 0, ZX_INTERRUPT_VIRTUAL, &interrupt_));
}
fuchsia_hardware_platform_device::Service::InstanceHandler GetInstanceHandler(
async_dispatcher_t* dispatcher) {
return fuchsia_hardware_platform_device::Service::InstanceHandler({
.device = binding_group_.CreateHandler(this, dispatcher, fidl::kIgnoreBindingClosure),
});
}
zx::interrupt& irq() { return interrupt_; }
private:
void NotImplemented_(const std::string& name, ::fidl::CompleterBase& completer) override {}
void GetNodeDeviceInfo(GetNodeDeviceInfoCompleter::Sync& completer) override {
completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
}
void GetInterruptById(
fuchsia_hardware_platform_device::wire::DeviceGetInterruptByIdRequest* request,
GetInterruptByIdCompleter::Sync& completer) override {
if (request->index != 0) {
return completer.ReplyError(ZX_ERR_NOT_FOUND);
}
zx::interrupt out_interrupt;
zx_status_t status = interrupt_.duplicate(ZX_RIGHT_SAME_RIGHTS, &out_interrupt);
if (status == ZX_OK) {
completer.ReplySuccess(std::move(out_interrupt));
} else {
completer.ReplyError(status);
}
}
zx::interrupt interrupt_;
fidl::ServerBindingGroup<fuchsia_hardware_platform_device::Device> binding_group_;
};
class TestAmlUsbPhyDevice : public AmlUsbPhyDevice {
public:
TestAmlUsbPhyDevice(fdf::DriverStartArgs start_args,
fdf::UnownedSynchronizedDispatcher driver_dispatcher)
: AmlUsbPhyDevice(std::move(start_args), std::move(driver_dispatcher)) {}
static DriverRegistration GetDriverRegistration() {
return FUCHSIA_DRIVER_REGISTRATION_V1(
fdf_internal::DriverServer<TestAmlUsbPhyDevice>::initialize,
fdf_internal::DriverServer<TestAmlUsbPhyDevice>::destroy);
}
bool dwc2_connected() { return device()->dwc2_connected(); }
FakeMmio& usbctrl_mmio() { return mmio_[0]; }
private:
zx::result<fdf::MmioBuffer> MapMmio(
const fidl::WireSyncClient<fuchsia_hardware_platform_device::Device>& pdev,
uint32_t idx) override {
if (idx >= kRegisterBanks) {
return zx::error(ZX_ERR_OUT_OF_RANGE);
}
return zx::ok(mmio_[idx].mmio());
}
FakeMmio mmio_[kRegisterBanks];
};
struct IncomingNamespace {
fdf_testing::TestNode node_{std::string("root")};
fdf_testing::TestEnvironment env_{fdf::Dispatcher::GetCurrent()->get()};
compat::DeviceServer device_server_;
FakePDev pdev_server;
mock_registers::MockRegisters registers{fdf::Dispatcher::GetCurrent()->async_dispatcher()};
};
// Fixture that supports tests of AmlUsbPhy::Create.
class AmlUsbPhyTest : public testing::Test {
public:
void SetUp() override {
static constexpr uint32_t kMagicNumbers[8] = {};
static constexpr uint8_t kPhyType = kG12A;
static const std::vector<UsbPhyMode> kPhyModes = {
{UsbProtocol::Usb2_0, UsbMode::Host, false},
{UsbProtocol::Usb2_0, UsbMode::Otg, true},
{UsbProtocol::Usb3_0, UsbMode::Host, false},
};
fuchsia_driver_framework::DriverStartArgs start_args;
incoming_.SyncCall([&](IncomingNamespace* incoming) {
auto start_args_result = incoming->node_.CreateStartArgsAndServe();
ASSERT_TRUE(start_args_result.is_ok());
start_args = std::move(start_args_result->start_args);
outgoing_ = std::move(start_args_result->outgoing_directory_client);
auto init_result =
incoming->env_.Initialize(std::move(start_args_result->incoming_directory_server));
ASSERT_TRUE(init_result.is_ok());
incoming->device_server_.Init("pdev", "");
// Serve metadata.
auto status = incoming->device_server_.AddMetadata(DEVICE_METADATA_PRIVATE, &kMagicNumbers,
sizeof(kMagicNumbers));
EXPECT_EQ(ZX_OK, status);
status = incoming->device_server_.AddMetadata(
DEVICE_METADATA_PRIVATE_PHY_TYPE | DEVICE_METADATA_PRIVATE, &kPhyType, sizeof(kPhyType));
EXPECT_EQ(ZX_OK, status);
status = incoming->device_server_.AddMetadata(DEVICE_METADATA_USB_MODE, kPhyModes.data(),
kPhyModes.size() * sizeof(kPhyModes[0]));
EXPECT_EQ(ZX_OK, status);
status = incoming->device_server_.Serve(fdf::Dispatcher::GetCurrent()->async_dispatcher(),
&incoming->env_.incoming_directory());
EXPECT_EQ(ZX_OK, status);
// Serve pdev_server.
auto result =
incoming->env_.incoming_directory().AddService<fuchsia_hardware_platform_device::Service>(
std::move(incoming->pdev_server.GetInstanceHandler(
fdf::Dispatcher::GetCurrent()->async_dispatcher())),
"pdev");
ASSERT_TRUE(result.is_ok());
// Serve registers.
result = incoming->env_.incoming_directory().AddService<fuchsia_hardware_registers::Service>(
std::move(incoming->registers.GetInstanceHandler()), "register-reset");
ASSERT_TRUE(result.is_ok());
// Prepare for Start().
incoming->registers.ExpectWrite<uint32_t>(RESET1_LEVEL_OFFSET,
aml_registers::USB_RESET1_LEVEL_MASK,
aml_registers::USB_RESET1_LEVEL_MASK);
incoming->registers.ExpectWrite<uint32_t>(RESET1_REGISTER_OFFSET,
aml_registers::USB_RESET1_REGISTER_UNKNOWN_1_MASK,
aml_registers::USB_RESET1_REGISTER_UNKNOWN_1_MASK);
incoming->registers.ExpectWrite<uint32_t>(RESET1_LEVEL_OFFSET,
aml_registers::USB_RESET1_LEVEL_MASK,
~aml_registers::USB_RESET1_LEVEL_MASK);
incoming->registers.ExpectWrite<uint32_t>(RESET1_LEVEL_OFFSET,
aml_registers::USB_RESET1_LEVEL_MASK,
aml_registers::USB_RESET1_LEVEL_MASK);
});
ASSERT_NO_FATAL_FAILURE();
// Start dut_.
auto result = runtime_.RunToCompletion(dut_.Start(std::move(start_args)));
ASSERT_TRUE(result.is_ok());
runtime_.RunUntilIdle();
}
void TearDown() override {
incoming_.SyncCall([](IncomingNamespace* incoming) { incoming->registers.VerifyAll(); });
ASSERT_NO_FATAL_FAILURE();
}
// This method fires the irq and then waits for the side effects of SetMode to have taken place.
void TriggerInterruptAndCheckMode(UsbMode mode) {
// Switch to appropriate mode. This will be read by the irq thread.
dut_->usbctrl_mmio().reg_values_[USB_R5_OFFSET >> 2] = (mode == UsbMode::Peripheral) << 6;
// Wake up the irq thread.
incoming_.SyncCall([](IncomingNamespace* incoming) {
incoming->pdev_server.irq().trigger(0, zx::clock::get_monotonic());
});
runtime_.RunUntilIdle();
// Check that mode is as expected.
auto& phy = dut_->device();
EXPECT_EQ(phy->usbphy(UsbProtocol::Usb2_0, 0)->phy_mode(), UsbMode::Host);
EXPECT_EQ(phy->usbphy(UsbProtocol::Usb2_0, 1)->phy_mode(), mode);
EXPECT_EQ(phy->usbphy(UsbProtocol::Usb3_0, 0)->phy_mode(), UsbMode::Host);
}
void CheckDevices(fdf_testing::TestNode* test_node, std::vector<std::string> devices) {
// Wait for devices
runtime_.RunUntil(
[this, &test_node, count = devices.size()]() {
return incoming_.SyncCall([&test_node, &count](IncomingNamespace* incoming) {
return test_node->children().size() == count;
});
},
zx::usec(1000));
// Check devices
incoming_.SyncCall([&test_node, &devices](IncomingNamespace* incoming) {
for (auto& dev : devices) {
EXPECT_NE(test_node->children().find(dev), test_node->children().end());
}
});
}
protected:
fdf_testing::DriverRuntime runtime_;
fdf::UnownedSynchronizedDispatcher env_dispatcher_ = runtime_.StartBackgroundDispatcher();
async_patterns::TestDispatcherBound<IncomingNamespace> incoming_{
env_dispatcher_->async_dispatcher(), std::in_place};
fidl::ClientEnd<fuchsia_io::Directory> outgoing_;
fdf_testing::DriverUnderTest<TestAmlUsbPhyDevice> dut_{
TestAmlUsbPhyDevice::GetDriverRegistration()};
};
TEST_F(AmlUsbPhyTest, SetMode) {
fdf_testing::TestNode* phy;
runtime_.RunUntil(
[this]() {
return incoming_.SyncCall(
[&](IncomingNamespace* incoming) { return incoming->node_.children().size() == 1; });
},
zx::usec(1000));
incoming_.SyncCall([&](IncomingNamespace* incoming) {
// The aml_usb_phy device should be added.
ASSERT_EQ(incoming->node_.children().size(), 1);
ASSERT_NE(incoming->node_.children().find("aml_usb_phy"), incoming->node_.children().end());
phy = &incoming->node_.children().at("aml_usb_phy");
});
CheckDevices(phy, {"xhci"});
// Trigger interrupt configuring initial Host mode.
TriggerInterruptAndCheckMode(UsbMode::Host);
// Nothing should've changed.
CheckDevices(phy, {"xhci"});
// Trigger interrupt, and switch to Peripheral mode.
TriggerInterruptAndCheckMode(UsbMode::Peripheral);
CheckDevices(phy, {"xhci", "dwc2"});
// Trigger interrupt, and switch (back) to Host mode.
TriggerInterruptAndCheckMode(UsbMode::Host);
// The dwc2 device should be removed.
CheckDevices(phy, {"xhci"});
}
TEST_F(AmlUsbPhyTest, ConnectStatusChanged) {
fdf_testing::TestNode* phy;
runtime_.RunUntil(
[this]() {
return incoming_.SyncCall(
[&](IncomingNamespace* incoming) { return incoming->node_.children().size() == 1; });
},
zx::usec(1000));
incoming_.SyncCall([&](IncomingNamespace* incoming) {
// The aml_usb_phy device should be added.
ASSERT_EQ(incoming->node_.children().size(), 1);
ASSERT_NE(incoming->node_.children().find("aml_usb_phy"), incoming->node_.children().end());
phy = &incoming->node_.children().at("aml_usb_phy");
});
CheckDevices(phy, {"xhci"});
auto endpoints = fidl::CreateEndpoints<fuchsia_io::Directory>();
ASSERT_TRUE(endpoints.is_ok());
auto status = fdio_open_at(outgoing_.handle()->get(), "/svc",
static_cast<uint32_t>(fuchsia_io::OpenFlags::kDirectory),
endpoints->server.TakeChannel().release());
EXPECT_EQ(ZX_OK, status);
auto result = fdf::internal::DriverTransportConnect<fuchsia_hardware_usb_phy::Service::Device>(
endpoints->client, "xhci");
ASSERT_TRUE(result.is_ok());
runtime_.PerformBlockingWork([&result]() {
fdf::Arena arena('TEST');
fdf::WireUnownedResult wire_result =
fdf::WireCall(*result).buffer(arena)->ConnectStatusChanged(true);
EXPECT_EQ(ZX_OK, wire_result.status());
ASSERT_TRUE(wire_result.value().is_ok());
});
EXPECT_TRUE(dut_->dwc2_connected());
}
} // namespace aml_usb_phy