blob: 84bc4c03a2af1caf73e0c1e70d4aa3fd8e57be10 [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 <fidl/fuchsia.device/cpp/wire.h>
#include <lib/zx/channel.h>
#include <lib/zx/vmo.h>
#include <zircon/status.h>
#include <fbl/auto_lock.h>
#include <fbl/string_printf.h>
#include "driver_host.h"
#include "env.h"
#include "log.h"
#include "proxy_iostate.h"
#include "zx_device.h"
#include "zx_driver.h"
namespace {
// Handles outstanding calls to fuchsia.device.manager.DeviceController/BindDriver
// and fuchsia.device.Controller/Bind.
void BindReply(const fbl::RefPtr<zx_device_t>& dev,
DeviceControllerConnection::BindDriverCompleter::Sync& completer, zx_status_t status,
zx::channel test_output = zx::channel()) {
completer.Reply(status, std::move(test_output));
bool complete_bind = true;
for (auto& child : dev->children()) {
if ((child.flags() & DEV_FLAG_INVISIBLE) || child.ops()->init) {
// Driver has initialization to do.
complete_bind = false;
}
}
if (!complete_bind) {
dev->set_complete_bind_rebind_after_init(true);
return;
}
if (auto bind_conn = dev->take_bind_conn(); bind_conn) {
bind_conn(status);
}
if (auto rebind_conn = dev->take_rebind_conn(); rebind_conn) {
rebind_conn(status);
}
}
} // namespace
void DeviceControllerConnection::Init(InitRequestView request, InitCompleter::Sync& completer) {
ZX_ASSERT(this->dev()->init_cb == nullptr);
auto trace = this->dev()->BeginAsyncTrace("driver_host:lifecycle", "init");
this->dev()->init_cb = [completer = completer.ToAsync(), trace = std::move(trace)](
zx_status_t status) mutable { completer.Reply(status); };
fbl::AutoLock lock(&driver_host_context_->api_lock());
driver_host_context_->DeviceInit(this->dev());
}
void DeviceControllerConnection::Suspend(SuspendRequestView request,
SuspendCompleter::Sync& completer) {
ZX_ASSERT(this->dev()->suspend_cb == nullptr);
auto trace = this->dev()->BeginAsyncTrace("driver_host:lifecycle", "suspend");
this->dev()->suspend_cb = [completer = completer.ToAsync(), trace = std::move(trace)](
zx_status_t status, uint8_t out_state) mutable {
if (status == ZX_ERR_NOT_SUPPORTED) {
status = ZX_OK;
}
completer.Reply(status);
};
fbl::AutoLock lock(&driver_host_context_->api_lock());
driver_host_context_->DeviceSystemSuspend(this->dev(), request->flags);
}
void DeviceControllerConnection::Resume(ResumeRequestView request,
ResumeCompleter::Sync& completer) {
ZX_ASSERT(this->dev()->resume_cb == nullptr);
auto trace = this->dev()->BeginAsyncTrace("driver_host:lifecycle", "resume");
this->dev()->resume_cb = [completer = completer.ToAsync(), trace = std::move(trace)](
zx_status_t status, uint8_t out_power_state,
uint32_t out_perf_state) mutable {
if (status == ZX_ERR_NOT_SUPPORTED) {
status = ZX_OK;
}
if (status != ZX_OK &&
(out_power_state ==
static_cast<uint8_t>(fuchsia_device::wire::DevicePowerState::kDevicePowerStateD0))) {
// Do not fail system resume, when the device is unable to go into a particular performance
// state, but resumed to a working state.
status = ZX_OK;
}
completer.Reply(status);
};
fbl::AutoLock lock(&driver_host_context_->api_lock());
driver_host_context_->DeviceSystemResume(this->dev(), request->target_system_state);
}
void DeviceControllerConnection::ConnectProxy(ConnectProxyRequestView request,
ConnectProxyCompleter::Sync& _completer) {
VLOGD(1, *dev(), "Connected to proxy for device %p", dev().get());
dev()->ops()->rxrpc(dev()->ctx, ZX_HANDLE_INVALID);
// Ignore any errors in the creation for now?
// TODO(teisenbe): Investigate if this is the right thing
ProxyIostate::Create(dev(), std::move(request->shadow),
driver_host_context_->loop().dispatcher());
}
void DeviceControllerConnection::BindDriver(BindDriverRequestView request,
BindDriverCompleter::Sync& completer) {
const auto& dev = this->dev();
std::string_view driver_path(request->driver_path.data(), request->driver_path.size());
// TODO: api lock integration
if (driver_path != "/boot/driver/fragment.so") {
LOGD(INFO, *dev, "Binding driver '%.*s'", static_cast<int>(driver_path.size()),
driver_path.data());
} else {
LOGD(TRACE, *dev, "Binding driver '%.*s'", static_cast<int>(driver_path.size()),
driver_path.data());
}
if (dev->flags() & DEV_FLAG_DEAD) {
LOGD(ERROR, *dev, "Cannot bind to removed device");
BindReply(dev, completer, ZX_ERR_IO_NOT_PRESENT);
return;
}
fbl::RefPtr<zx_driver_t> drv;
fbl::RefPtr<Driver> driver;
zx_status_t r =
driver_host_context_->FindDriver(driver_path, std::move(request->driver), &drv, &driver);
if (r != ZX_OK) {
LOGD(ERROR, *dev, "Failed to load driver '%.*s': %s", static_cast<int>(driver_path.size()),
driver_path.data(), zx_status_get_string(r));
BindReply(dev, completer, r);
return;
}
// Check for driver test flags.
bool tests_default = getenv_bool("driver.tests.enable", false);
auto option = fbl::StringPrintf("driver.%s.tests.enable", drv->name());
zx::channel test_output;
if (getenv_bool(option.data(), tests_default) && drv->has_run_unit_tests_op()) {
zx::channel test_input;
zx::channel::create(0, &test_input, &test_output);
bool tests_passed = drv->RunUnitTestsOp(dev, driver, std::move(test_input));
if (!tests_passed) {
FX_LOGF(ERROR, "unit-tests", "[ FAILED ] %s", drv->name());
drv->set_status(ZX_ERR_BAD_STATE);
BindReply(dev, completer, ZX_ERR_BAD_STATE, std::move(test_output));
return;
}
FX_LOGF(INFO, "unit-tests", "[ PASSED ] %s", drv->name());
}
if (drv->has_bind_op()) {
internal::BindContext bind_ctx = {
.parent = dev,
.child = nullptr,
};
r = drv->BindOp(&bind_ctx, driver, dev);
if (r != ZX_OK) {
LOGD(ERROR, *dev, "Failed to bind driver '%.*s': %s", static_cast<int>(driver_path.size()),
driver_path.data(), zx_status_get_string(r));
} else if (bind_ctx.child == nullptr) {
LOGD(WARNING, *dev, "Driver '%.*s' did not add a child device in bind()",
static_cast<int>(driver_path.size()), driver_path.data());
}
BindReply(dev, completer, r, std::move(test_output));
return;
}
if (!drv->has_create_op()) {
LOGD(ERROR, *dev, "Neither create() nor bind() are implemented for driver '%.*s'",
static_cast<int>(driver_path.size()), driver_path.data());
}
BindReply(dev, completer, ZX_ERR_NOT_SUPPORTED, std::move(test_output));
}
void DeviceControllerConnection::Unbind(UnbindRequestView request,
UnbindCompleter::Sync& completer) {
ZX_ASSERT(this->dev()->unbind_cb == nullptr);
auto trace = this->dev()->BeginAsyncTrace("driver_host:lifecycle", "unbind");
this->dev()->unbind_cb = [dev = this->dev(), completer = completer.ToAsync(),
trace = std::move(trace)](zx_status_t status) mutable {
if (status != ZX_OK && dev->parent()) {
// If unbind returns an error, and if client is waiting for unbind to complete,
// inform the client.
if (auto unbind_children_conn = dev->parent()->take_unbind_children_conn();
unbind_children_conn) {
unbind_children_conn(status);
}
}
completer.ReplySuccess();
};
fbl::AutoLock lock(&driver_host_context_->api_lock());
driver_host_context_->DeviceUnbind(this->dev());
}
void DeviceControllerConnection::CompleteRemoval(CompleteRemovalRequestView request,
CompleteRemovalCompleter::Sync& completer) {
ZX_ASSERT(this->dev()->removal_cb == nullptr);
this->dev()->removal_cb = [completer = completer.ToAsync()](zx_status_t status) mutable {
completer.ReplySuccess();
};
fbl::AutoLock lock(&driver_host_context_->api_lock());
driver_host_context_->DeviceCompleteRemoval(this->dev());
}
DeviceControllerConnection::DeviceControllerConnection(
DriverHostContext* ctx, fbl::RefPtr<zx_device> dev,
fidl::WireSharedClient<fuchsia_device_manager::Coordinator> coordinator_client)
: driver_host_context_(ctx),
dev_(std::move(dev)),
coordinator_client_(std::move(coordinator_client)) {
dev_->coordinator_client = coordinator_client_.Clone();
}
std::unique_ptr<DeviceControllerConnection> DeviceControllerConnection::Create(
DriverHostContext* ctx, fbl::RefPtr<zx_device> dev,
fidl::WireSharedClient<fuchsia_device_manager::Coordinator> coordinator_client) {
return std::make_unique<DeviceControllerConnection>(ctx, std::move(dev),
std::move(coordinator_client));
}
void DeviceControllerConnection::Bind(
std::unique_ptr<DeviceControllerConnection> conn,
fidl::ServerEnd<fuchsia_device_manager::DeviceController> request,
async_dispatcher_t* dispatcher) {
auto dev = conn->dev_;
fbl::AutoLock al(&dev->controller_lock);
dev->controller_binding = fidl::BindServer(
dispatcher, std::move(request), std::move(conn),
[](DeviceControllerConnection* self, fidl::UnbindInfo info,
fidl::ServerEnd<fuchsia_device_manager::DeviceController> server_end) {
auto& dev = self->dev_;
if (info.is_user_initiated()) {
return;
}
if (info.is_peer_closed()) {
// Check if we were expecting this peer close. If not, this could be a
// serious bug.
{
fbl::AutoLock al(&dev->controller_lock);
if (!dev->controller_binding) {
// We're in the middle of shutting down, so just stop processing
// signals and wait for the queued shutdown packet. It has a
// reference to the connection, which it will use to recover
// ownership of it.
return;
}
}
// This is expected in test environments where driver_manager has
// terminated.
// TODO(fxbug.dev/52627): Support graceful termination.
LOGD(WARNING, *dev, "driver_manager disconnected from device %p", dev.get());
zx_process_exit(1);
return;
}
LOGD(FATAL, *dev, "FIDL error for device %p: %s", dev.get(),
info.FormatDescription().c_str());
});
}
// Handler for when a fuchsia.io open() is called on a device
void DeviceControllerConnection::Open(OpenRequestView request, OpenCompleter::Sync& completer) {
VLOGD(1, *dev(), "Opening device %p", dev().get());
if (request->path.size() > 1 && request->path.data()[0] != '.') {
LOGD(ERROR, *dev(), "Attempt to open path '%.*s'", static_cast<int>(request->path.size()),
request->path.data());
}
driver_host_context_->DeviceConnect(this->dev(), request->flags, request->object.TakeChannel());
}