blob: f2957c4fb06cda3103db2cd4eaeaf486485addcf [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 "device_controller_connection.h"
#include <fuchsia/io/llcpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/zx/vmo.h>
#include <thread>
#include <fbl/auto_lock.h>
#include <zxtest/zxtest.h>
#include "connection_destroyer.h"
#include "driver_host_context.h"
#include "zx_device.h"
namespace {
TEST(DeviceControllerConnectionTestCase, Creation) {
DriverHostContext ctx(&kAsyncLoopConfigNoAttachToCurrentThread);
fbl::RefPtr<zx_driver> drv;
ASSERT_OK(zx_driver::Create("test", ctx.inspect().drivers(), &drv));
fbl::RefPtr<zx_device> dev;
ASSERT_OK(zx_device::Create(&ctx, "test", drv.get(), &dev));
zx::channel device_local, device_remote;
ASSERT_OK(zx::channel::create(0, &device_local, &device_remote));
zx::channel device_local2, device_remote2;
ASSERT_OK(zx::channel::create(0, &device_local2, &device_remote2));
std::unique_ptr<DeviceControllerConnection> conn;
fidl::Client<llcpp::fuchsia::device::manager::Coordinator> client;
client.Bind(std::move(device_remote2), ctx.loop().dispatcher());
ASSERT_NULL(dev->conn.load());
ASSERT_OK(DeviceControllerConnection::Create(&ctx, dev, std::move(device_remote),
std::move(client), &conn));
ASSERT_NOT_NULL(dev->conn.load());
ASSERT_OK(DeviceControllerConnection::BeginWait(std::move(conn), ctx.loop().dispatcher()));
ASSERT_OK(ctx.loop().RunUntilIdle());
// Clean up memory. Connection destroyer runs asynchronously.
{
fbl::AutoLock lock(&ctx.api_lock());
dev->removal_cb = [](zx_status_t) {};
ctx.DriverManagerRemove(std::move(dev));
}
ASSERT_OK(ctx.loop().RunUntilIdle());
}
TEST(DeviceControllerConnectionTestCase, PeerClosedDuringReply) {
DriverHostContext ctx(&kAsyncLoopConfigNoAttachToCurrentThread);
fbl::RefPtr<zx_driver> drv;
ASSERT_OK(zx_driver::Create("test", ctx.inspect().drivers(), &drv));
fbl::RefPtr<zx_device> dev;
ASSERT_OK(zx_device::Create(&ctx, "test", drv.get(), &dev));
zx::channel device_local, device_remote;
ASSERT_OK(zx::channel::create(0, &device_local, &device_remote));
zx::channel device_local2, device_remote2;
ASSERT_OK(zx::channel::create(0, &device_local2, &device_remote2));
class DeviceControllerConnectionTest : public DeviceControllerConnection {
public:
DeviceControllerConnectionTest(
DriverHostContext* ctx, fbl::RefPtr<zx_device> dev, zx::channel rpc,
fidl::Client<llcpp::fuchsia::device::manager::Coordinator> coordinator_client,
fidl::Client<::llcpp::fuchsia::device::manager::DeviceController>& local)
: DeviceControllerConnection(ctx, std::move(dev), std::move(rpc),
std::move(coordinator_client)),
local_(local) {}
void BindDriver(::fidl::StringView driver_path, ::zx::vmo driver,
BindDriverCompleter::Sync& completer) override {
// Pretend that a device closure happened right before we began
// processing BindDriver. Close the other half of the channel, so the reply below will fail
// from ZX_ERR_PEER_CLOSED.
completer_ = completer.ToAsync();
local_.Unbind();
}
void UnboundDone() {
completer_->Reply(ZX_OK, zx::channel());
fbl::AutoLock lock(&driver_host_context_->api_lock());
this->dev()->removal_cb = [](zx_status_t) {};
driver_host_context_->DriverManagerRemove(std::move(this->dev_));
}
private:
fidl::Client<::llcpp::fuchsia::device::manager::DeviceController>& local_;
std::optional<BindDriverCompleter::Async> completer_;
};
fidl::Client<::llcpp::fuchsia::device::manager::DeviceController> client;
fidl::Client<llcpp::fuchsia::device::manager::Coordinator> coordinator;
client.Bind(std::move(device_remote2), ctx.loop().dispatcher());
std::unique_ptr<DeviceControllerConnectionTest> conn;
conn = std::make_unique<DeviceControllerConnectionTest>(
&ctx, std::move(dev), std::move(device_remote), std::move(coordinator), client);
auto* conn_ref = conn.get();
ASSERT_OK(DeviceControllerConnectionTest::BeginWait(std::move(conn), ctx.loop().dispatcher()));
ASSERT_OK(ctx.loop().RunUntilIdle());
bool unbound = false;
ASSERT_OK(client.Bind(std::move(device_local), ctx.loop().dispatcher(), [&](fidl::UnbindInfo) {
unbound = true;
conn_ref->UnboundDone();
}));
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(0, 0, &vmo));
auto result = client->BindDriver(
::fidl::StringView("", 1), std::move(vmo),
[](::llcpp::fuchsia::device::manager::DeviceController::BindDriverResponse* response) {});
ASSERT_OK(result.status());
ASSERT_OK(ctx.loop().RunUntilIdle());
ASSERT_TRUE(unbound);
}
// Verify we do not abort when an expected PEER_CLOSED comes in.
TEST(DeviceControllerConnectionTestCase, PeerClosed) {
DriverHostContext ctx(&kAsyncLoopConfigNoAttachToCurrentThread);
fbl::RefPtr<zx_driver> drv;
ASSERT_OK(zx_driver::Create("test", ctx.inspect().drivers(), &drv));
fbl::RefPtr<zx_device> dev;
ASSERT_OK(zx_device::Create(&ctx, "test", drv.get(), &dev));
zx::channel device_local, device_remote;
ASSERT_OK(zx::channel::create(0, &device_local, &device_remote));
zx::channel device_local2, device_remote2;
ASSERT_OK(zx::channel::create(0, &device_local2, &device_remote2));
fidl::Client<llcpp::fuchsia::device::manager::Coordinator> client;
client.Bind(std::move(device_remote2), ctx.loop().dispatcher());
std::unique_ptr<DeviceControllerConnection> conn;
ASSERT_OK(DeviceControllerConnection::Create(&ctx, dev, std::move(device_remote),
std::move(client), &conn));
ASSERT_OK(DeviceControllerConnection::BeginWait(std::move(conn), ctx.loop().dispatcher()));
ASSERT_OK(ctx.loop().RunUntilIdle());
// Perform the device shutdown protocol, since otherwise the driver_host code
// will assert, since it is unable to handle unexpected connection closures.
{
fbl::AutoLock lock(&ctx.api_lock());
dev->removal_cb = [](zx_status_t) {};
ctx.DriverManagerRemove(std::move(dev));
device_local.reset();
}
ASSERT_OK(ctx.loop().RunUntilIdle());
}
TEST(DeviceControllerConnectionTestCase, UnbindHook) {
DriverHostContext ctx(&kAsyncLoopConfigNoAttachToCurrentThread);
fbl::RefPtr<zx_driver> drv;
ASSERT_OK(zx_driver::Create("test", ctx.inspect().drivers(), &drv));
fbl::RefPtr<zx_device> dev;
ASSERT_OK(zx_device::Create(&ctx, "test", drv.get(), &dev));
zx::channel device_local, device_remote;
ASSERT_OK(zx::channel::create(0, &device_local, &device_remote));
zx::channel device_local2, device_remote2;
ASSERT_OK(zx::channel::create(0, &device_local2, &device_remote2));
class DeviceControllerConnectionTest : public DeviceControllerConnection {
public:
DeviceControllerConnectionTest(
DriverHostContext* ctx, fbl::RefPtr<zx_device> dev, zx::channel rpc,
fidl::Client<llcpp::fuchsia::device::manager::Coordinator> coordinator_client)
: DeviceControllerConnection(ctx, std::move(dev), std::move(rpc),
std::move(coordinator_client)) {}
void Unbind(UnbindCompleter::Sync& completer) {
fbl::RefPtr<zx_device> dev = this->dev();
// Set dev->flags() so that we can check that the unbind hook is called in
// the test.
dev->set_flag(DEV_FLAG_DEAD);
completer.ReplySuccess();
}
};
fidl::Client<llcpp::fuchsia::device::manager::Coordinator> coordinator;
coordinator.Bind(std::move(device_remote2), ctx.loop().dispatcher());
std::unique_ptr<DeviceControllerConnectionTest> conn;
conn = std::make_unique<DeviceControllerConnectionTest>(
&ctx, std::move(dev), std::move(device_remote), std::move(coordinator));
fbl::RefPtr<zx_device> my_dev = conn->dev();
ASSERT_OK(DeviceControllerConnectionTest::BeginWait(std::move(conn), ctx.loop().dispatcher()));
ASSERT_OK(ctx.loop().RunUntilIdle());
fidl::Client<::llcpp::fuchsia::device::manager::DeviceController> client;
ASSERT_OK(client.Bind(std::move(device_local), ctx.loop().dispatcher()));
bool unbind_successful = false;
auto result = client->Unbind(
[&](::llcpp::fuchsia::device::manager::DeviceController::UnbindResponse* response) {
unbind_successful = response->result.is_response();
});
ASSERT_OK(result.status());
ASSERT_OK(ctx.loop().RunUntilIdle());
ASSERT_EQ(my_dev->flags(), DEV_FLAG_DEAD);
ASSERT_TRUE(unbind_successful);
client.Unbind();
{
fbl::AutoLock lock(&ctx.api_lock());
my_dev->removal_cb = [](zx_status_t) {};
ctx.DriverManagerRemove(std::move(my_dev));
}
ASSERT_OK(ctx.loop().RunUntilIdle());
}
} // namespace