blob: be4aa0cb70158eec60d99afa7b570a14100926da [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_device> dev;
ASSERT_OK(zx_device::Create(&ctx, &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;
ASSERT_NULL(dev->conn.load());
ASSERT_OK(DeviceControllerConnection::Create(&ctx, dev, std::move(device_remote),
std::move(device_remote2), &conn));
ASSERT_NOT_NULL(dev->conn.load());
ASSERT_OK(DeviceControllerConnection::BeginWait(std::move(conn), ctx.loop().dispatcher()));
ASSERT_OK(ctx.loop().RunUntilIdle());
}
TEST(DeviceControllerConnectionTestCase, PeerClosedDuringReply) {
DriverHostContext ctx(&kAsyncLoopConfigNoAttachToCurrentThread);
fbl::RefPtr<zx_device> dev;
ASSERT_OK(zx_device::Create(&ctx, &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_local, &device_remote));
class DeviceControllerConnectionTest : public DeviceControllerConnection {
public:
DeviceControllerConnectionTest(DriverHostContext* ctx, fbl::RefPtr<zx_device> dev,
zx::channel rpc, zx::channel coordinator_rpc,
async_dispatcher_t* dispatcher, zx::channel local)
: DeviceControllerConnection(ctx, std::move(dev), std::move(rpc),
std::move(coordinator_rpc)) {
dispatcher_ = dispatcher;
local_ = std::move(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.
auto conn = this->dev()->conn.exchange(nullptr);
ConnectionDestroyer::Get()->QueueDeviceControllerConnection(dispatcher_, conn);
local_.reset();
completer.Reply(ZX_OK, zx::channel());
}
private:
async_dispatcher_t* dispatcher_;
zx::channel local_;
};
std::unique_ptr<DeviceControllerConnectionTest> conn;
auto device_local_handle = device_local.get();
conn = std::make_unique<DeviceControllerConnectionTest>(
&ctx, std::move(dev), std::move(device_remote), std::move(device_remote2),
ctx.loop().dispatcher(), std::move(device_local));
ASSERT_OK(DeviceControllerConnectionTest::BeginWait(std::move(conn), ctx.loop().dispatcher()));
ASSERT_OK(ctx.loop().RunUntilIdle());
// Create a thread to send a BindDriver message. The thread isn't strictly
// necessary, but is done out of convenience since the FIDL LLCPP bindings currently don't
// expose non-zx_channel_call client bindings.
enum {
INITIAL,
VMO_CREATE_FAILED,
WRONG_CALL_STATUS,
SUCCESS,
} thread_status = INITIAL;
std::thread synchronous_call_thread([owned_channel = device_local_handle, &thread_status]() {
auto unowned_channel = zx::unowned_channel(owned_channel);
zx::vmo vmo;
zx_status_t status = zx::vmo::create(0, 0, &vmo);
if (status != ZX_OK) {
thread_status = VMO_CREATE_FAILED;
return;
}
auto result = ::llcpp::fuchsia::device::manager::DeviceController::Call::BindDriver(
std::move(unowned_channel), ::fidl::StringView("", 1), std::move(vmo));
if (result.status() != ZX_ERR_CANCELED) {
thread_status = WRONG_CALL_STATUS;
return;
}
thread_status = SUCCESS;
return;
});
ASSERT_OK(ctx.loop().Run(zx::time::infinite(), true /* run_once */));
synchronous_call_thread.join();
ASSERT_EQ(SUCCESS, thread_status);
ASSERT_FALSE(device_local.is_valid());
}
// Verify we do not abort when an expected PEER_CLOSED comes in.
TEST(DeviceControllerConnectionTestCase, PeerClosed) {
DriverHostContext ctx(&kAsyncLoopConfigNoAttachToCurrentThread);
fbl::RefPtr<zx_device> dev;
ASSERT_OK(zx_device::Create(&ctx, &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;
ASSERT_OK(DeviceControllerConnection::Create(&ctx, dev, std::move(device_remote),
std::move(device_remote2), &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.
auto dev_conn = dev->conn.exchange(nullptr);
ConnectionDestroyer::Get()->QueueDeviceControllerConnection(ctx.loop().dispatcher(), dev_conn);
device_local.reset();
ASSERT_OK(ctx.loop().RunUntilIdle());
}
TEST(DeviceControllerConnectionTestCase, UnbindHook) {
DriverHostContext ctx(&kAsyncLoopConfigNoAttachToCurrentThread);
fbl::RefPtr<zx_device> dev;
ASSERT_OK(zx_device::Create(&ctx, &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, zx::channel coordinator_rpc)
: DeviceControllerConnection(ctx, std::move(dev), std::move(rpc),
std::move(coordinator_rpc)) {}
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->flags = DEV_FLAG_DEAD;
llcpp::fuchsia::device::manager::DeviceController_Unbind_Result result;
fidl::aligned<llcpp::fuchsia::device::manager::DeviceController_Unbind_Response> response;
result.set_response(fidl::unowned_ptr(&response));
completer.Reply(std::move(result));
}
};
std::unique_ptr<DeviceControllerConnectionTest> conn;
conn = std::make_unique<DeviceControllerConnectionTest>(
&ctx, std::move(dev), std::move(device_remote), std::move(device_remote2));
fbl::RefPtr<zx_device> my_dev = conn->dev();
ASSERT_OK(DeviceControllerConnectionTest::BeginWait(std::move(conn), ctx.loop().dispatcher()));
ASSERT_OK(ctx.loop().RunUntilIdle());
// Create a thread to send the Unbind message. The thread isn't strictly
// necessary, but is done out of convenience since the FIDL C bindings don't
// expose non-zx_channel_call client bindings.
enum {
INITIAL,
WRITE_FAILED,
SUCCESS,
} thread_status = INITIAL;
std::thread synchronous_call_thread([channel = device_local.get(), &thread_status]() {
auto unowned_channel = zx::unowned_channel(channel);
auto result = ::llcpp::fuchsia::device::manager::DeviceController::Call::Unbind(
std::move(unowned_channel));
if (!result.ok()) {
thread_status = WRITE_FAILED;
return;
}
thread_status = SUCCESS;
});
ASSERT_OK(ctx.loop().Run(zx::time::infinite(), true /* run_once */));
synchronous_call_thread.join();
ASSERT_EQ(SUCCESS, thread_status);
ASSERT_EQ(my_dev->flags, DEV_FLAG_DEAD);
}
} // namespace