| // 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::Endpoints<fuchsia_io::Directory>::Create(); |
| 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 |