| // Copyright 2019 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <fcntl.h> |
| #include <fidl/fuchsia.device.lifecycle.test/cpp/wire.h> |
| #include <fidl/fuchsia.device/cpp/wire.h> |
| #include <fidl/fuchsia.hardware.serial/cpp/wire.h> |
| #include <fidl/fuchsia.io/cpp/wire.h> |
| #include <lib/ddk/platform-defs.h> |
| #include <lib/driver-integration-test/fixture.h> |
| #include <lib/fdio/cpp/caller.h> |
| #include <lib/fdio/directory.h> |
| #include <lib/fidl/cpp/wire/channel.h> |
| #include <lib/fidl/cpp/wire/connect_service.h> |
| #include <lib/fit/defer.h> |
| #include <zircon/processargs.h> |
| #include <zircon/syscalls.h> |
| |
| #include <vector> |
| |
| #include <zxtest/zxtest.h> |
| |
| using driver_integration_test::IsolatedDevmgr; |
| using fuchsia_device_lifecycle_test::Lifecycle; |
| using fuchsia_device_lifecycle_test::TestDevice; |
| |
| namespace { |
| constexpr char kLifecycleTopoPath[] = "sys/platform/11:10:0/ddk-lifecycle-test"; |
| constexpr char kLifecycleControllerTopoPath[] = |
| "sys/platform/11:10:0/ddk-lifecycle-test/device_controller"; |
| constexpr char kChildTopoPath[] = |
| "sys/platform/11:10:0/ddk-lifecycle-test/ddk-lifecycle-test-child-0"; |
| } // namespace |
| |
| class LifecycleTest : public zxtest::Test { |
| public: |
| ~LifecycleTest() override = default; |
| void SetUp() override { |
| IsolatedDevmgr::Args args; |
| |
| board_test::DeviceEntry dev = {}; |
| dev.vid = PDEV_VID_TEST; |
| dev.pid = PDEV_PID_LIFECYCLE_TEST; |
| dev.did = 0; |
| args.device_list.push_back(dev); |
| |
| zx_status_t status = IsolatedDevmgr::Create(&args, &devmgr_); |
| ASSERT_OK(status); |
| zx::result channel = |
| device_watcher::RecursiveWaitForFile(devmgr_.devfs_root().get(), kLifecycleTopoPath); |
| ASSERT_OK(channel.status_value()); |
| chan_ = fidl::ClientEnd<TestDevice>(std::move(channel.value())); |
| |
| // Subscribe to the device lifecycle events. |
| auto endpoints = fidl::CreateEndpoints<Lifecycle>(); |
| ASSERT_OK(endpoints.status_value()); |
| auto [local, remote] = *std::move(endpoints); |
| |
| auto result = fidl::WireCall(chan_)->SubscribeToLifecycle(std::move(remote)); |
| ASSERT_OK(result.status()); |
| ASSERT_FALSE(result->is_error()); |
| lifecycle_chan_ = std::move(local); |
| } |
| |
| protected: |
| void WaitPreRelease(uint64_t child_id); |
| |
| fidl::ClientEnd<TestDevice> chan_; |
| IsolatedDevmgr devmgr_; |
| |
| fidl::ClientEnd<Lifecycle> lifecycle_chan_; |
| }; |
| |
| void LifecycleTest::WaitPreRelease(uint64_t child_id) { |
| class EventHandler : public fidl::WireSyncEventHandler<Lifecycle> { |
| public: |
| EventHandler() = default; |
| |
| bool removed() const { return removed_; } |
| uint64_t device_id() const { return device_id_; } |
| |
| void OnChildPreRelease(fidl::WireEvent<Lifecycle::OnChildPreRelease>* event) override { |
| device_id_ = event->child_id; |
| removed_ = true; |
| } |
| |
| private: |
| bool removed_ = false; |
| uint64_t device_id_ = 0; |
| }; |
| |
| EventHandler event_handler; |
| while (!event_handler.removed()) { |
| ASSERT_OK(event_handler.HandleOneEvent(lifecycle_chan_).status()); |
| } |
| ASSERT_EQ(event_handler.device_id(), child_id); |
| } |
| |
| TEST_F(LifecycleTest, ChildPreRelease) { |
| // Add some child devices and store the returned ids. |
| std::vector<uint64_t> child_ids; |
| const uint32_t num_children = 10; |
| for (unsigned int i = 0; i < num_children; i++) { |
| auto result = |
| fidl::WireCall(chan_)->AddChild(true /* complete_init */, ZX_OK /* init_status */); |
| ASSERT_OK(result.status()); |
| ASSERT_FALSE(result->is_error()); |
| child_ids.push_back(result->value()->child_id); |
| } |
| |
| // Remove the child devices and check the test device received the pre-release notifications. |
| for (auto child_id : child_ids) { |
| auto result = fidl::WireCall(chan_)->RemoveChild(child_id); |
| ASSERT_OK(result.status()); |
| ASSERT_FALSE(result->is_error()); |
| |
| // Wait for the child pre-release notification. |
| ASSERT_NO_FATAL_FAILURE(WaitPreRelease(child_id)); |
| } |
| } |
| |
| TEST_F(LifecycleTest, Init) { |
| // Add a child device that does not complete its init hook yet. |
| uint64_t child_id; |
| auto result = fidl::WireCall(chan_)->AddChild(false /* complete_init */, ZX_OK /* init_status */); |
| ASSERT_OK(result.status()); |
| ASSERT_FALSE(result->is_error()); |
| child_id = result->value()->child_id; |
| |
| auto remove_result = fidl::WireCall(chan_)->RemoveChild(child_id); |
| ASSERT_OK(remove_result.status()); |
| ASSERT_FALSE(remove_result->is_error()); |
| |
| auto init_result = fidl::WireCall(chan_)->CompleteChildInit(child_id); |
| ASSERT_OK(init_result.status()); |
| ASSERT_FALSE(init_result->is_error()); |
| |
| // Wait for the child pre-release notification. |
| ASSERT_NO_FATAL_FAILURE(WaitPreRelease(child_id)); |
| } |
| |
| TEST_F(LifecycleTest, CloseAllConnectionsOnChildUnbind) { |
| auto result = fidl::WireCall(chan_)->AddChild(true /* complete_init */, ZX_OK /* init_status */); |
| ASSERT_OK(result.status()); |
| ASSERT_FALSE(result->is_error()); |
| auto child_id = result->value()->child_id; |
| |
| zx::result channel = |
| device_watcher::RecursiveWaitForFile(devmgr_.devfs_root().get(), kChildTopoPath); |
| ASSERT_OK(channel.status_value()); |
| |
| zx::channel chan = std::move(channel.value()); |
| ASSERT_TRUE(fidl::WireCall(chan_)->RemoveChild(child_id).ok()); |
| zx_signals_t closed; |
| ASSERT_OK(chan.wait_one(ZX_CHANNEL_PEER_CLOSED, zx::time::infinite(), &closed)); |
| ASSERT_TRUE(closed & ZX_CHANNEL_PEER_CLOSED); |
| // Wait for the child pre-release notification. |
| ASSERT_NO_FATAL_FAILURE(WaitPreRelease(child_id)); |
| } |
| |
| template <typename Protocol> |
| zx::result<fidl::WireClient<Protocol>> MakeClient(const fbl::unique_fd& fd, const char* path, |
| async_dispatcher_t* dispatcher) { |
| zx::result channel = device_watcher::RecursiveWaitForFile(fd.get(), path); |
| if (channel.is_error()) { |
| return channel.take_error(); |
| } |
| return zx::ok(fidl::WireClient<Protocol>(fidl::ClientEnd<Protocol>(std::move(channel.value())), |
| dispatcher)); |
| } |
| |
| TEST_F(LifecycleTest, CallsFailDuringUnbind) { |
| auto result = fidl::WireCall(chan_)->AddChild(true /* complete_init */, ZX_OK /* init_status */); |
| ASSERT_OK(result.status()); |
| ASSERT_FALSE(result->is_error()); |
| auto child_id = result->value()->child_id; |
| |
| async::Loop loop{&kAsyncLoopConfigNeverAttachToThread}; |
| |
| // Connect before unbinding. |
| |
| const fbl::unique_fd& fd = devmgr_.devfs_root(); |
| async_dispatcher_t* const dispatcher = loop.dispatcher(); |
| |
| zx::result queryable = MakeClient<fuchsia_unknown::Queryable>(fd, kChildTopoPath, dispatcher); |
| ASSERT_OK(queryable); |
| |
| zx::result controller = MakeClient<fuchsia_device::Controller>(fd, kChildTopoPath, dispatcher); |
| ASSERT_OK(controller); |
| |
| zx::result device = MakeClient<fuchsia_hardware_serial::Device>(fd, kChildTopoPath, dispatcher); |
| ASSERT_OK(device); |
| |
| ASSERT_OK(loop.RunUntilIdle()); |
| |
| ASSERT_OK(fidl::WireCall(chan_)->AsyncRemoveChild(child_id).status()); |
| |
| auto quitter = |
| std::make_shared<fit::deferred_action<fit::callback<void()>>>([&loop]() { loop.Quit(); }); |
| |
| queryable.value()->Query().ThenExactlyOnce( |
| [&, quitter](fidl::WireUnownedResult<fuchsia_unknown::Queryable::Query>& result) { |
| ASSERT_STATUS(result.status(), ZX_ERR_IO_NOT_PRESENT); |
| }); |
| controller.value()->GetTopologicalPath().ThenExactlyOnce( |
| [&, |
| quitter](fidl::WireUnownedResult<fuchsia_device::Controller::GetTopologicalPath>& result) { |
| ASSERT_STATUS(result.status(), ZX_ERR_IO_NOT_PRESENT); |
| }); |
| device.value()->GetClass().ThenExactlyOnce( |
| [&, quitter](fidl::WireUnownedResult<fuchsia_hardware_serial::Device::GetClass>& result) { |
| ASSERT_STATUS(result.status(), ZX_ERR_IO_NOT_PRESENT); |
| }); |
| |
| quitter.reset(); |
| |
| ASSERT_STATUS(loop.Run(), ZX_ERR_CANCELED); |
| |
| ASSERT_EQ(open(kChildTopoPath, O_RDWR), -1); |
| ASSERT_EQ(errno, ENOENT); |
| |
| ASSERT_OK(fidl::WireCall(chan_)->CompleteUnbind(child_id).status()); |
| } |
| |
| TEST_F(LifecycleTest, CloseAllConnectionsOnUnbind) { |
| zx::result channel = device_watcher::RecursiveWaitForFile(devmgr_.devfs_root().get(), |
| kLifecycleControllerTopoPath); |
| ASSERT_OK(channel); |
| fidl::WireSyncClient controller{ |
| fidl::ClientEnd<fuchsia_device::Controller>(std::move(channel.value()))}; |
| const fidl::WireResult result = controller->ScheduleUnbind(); |
| ASSERT_OK(result.status()); |
| const fit::result response = result.value(); |
| ASSERT_TRUE(response.is_ok(), "%s", zx_status_get_string(response.error_value())); |
| zx_signals_t closed; |
| ASSERT_OK(chan_.channel().wait_one(ZX_CHANNEL_PEER_CLOSED, zx::time::infinite(), &closed)); |
| ASSERT_TRUE(closed & ZX_CHANNEL_PEER_CLOSED); |
| } |
| |
| // Tests that the child device is removed if init fails. |
| TEST_F(LifecycleTest, FailedInit) { |
| uint64_t child_id; |
| auto result = |
| fidl::WireCall(chan_)->AddChild(true /* complete_init */, ZX_ERR_BAD_STATE /* init_status */); |
| ASSERT_OK(result.status()); |
| ASSERT_FALSE(result->is_error()); |
| child_id = result->value()->child_id; |
| |
| // Wait for the child pre-release notification. |
| ASSERT_NO_FATAL_FAILURE(WaitPreRelease(child_id)); |
| } |
| |
| TEST_F(LifecycleTest, RebindNoChildren) { |
| zx::result channel = device_watcher::RecursiveWaitForFile(devmgr_.devfs_root().get(), |
| kLifecycleControllerTopoPath); |
| ASSERT_OK(channel); |
| fidl::WireSyncClient controller{ |
| fidl::ClientEnd<fuchsia_device::Controller>(std::move(channel.value()))}; |
| fidl::WireResult result = controller->Rebind({}); |
| ASSERT_OK(result.status()); |
| ASSERT_TRUE(result.value().is_ok(), "%s", zx_status_get_string(result.value().error_value())); |
| } |
| |
| TEST_F(LifecycleTest, RebindChildren) { |
| { |
| fidl::WireResult result = |
| fidl::WireCall(chan_)->AddChild(true /* complete_init */, ZX_OK /* init_status */); |
| ASSERT_OK(result.status()); |
| ASSERT_TRUE(result.value().is_ok(), "%s", zx_status_get_string(result.value().error_value())); |
| } |
| |
| zx::result channel = device_watcher::RecursiveWaitForFile(devmgr_.devfs_root().get(), |
| kLifecycleControllerTopoPath); |
| ASSERT_OK(channel); |
| fidl::WireSyncClient controller{ |
| fidl::ClientEnd<fuchsia_device::Controller>(std::move(channel.value()))}; |
| fidl::WireResult result = controller->Rebind({}); |
| ASSERT_OK(result.status()); |
| ASSERT_TRUE(result.value().is_ok(), "%s", zx_status_get_string(result.value().error_value())); |
| } |