blob: 58e55ac0d8426aba420efda9d407562e2c967e1c [file] [log] [blame]
// 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()));
}