blob: de4e8f3122399aebe46698b15ae64a5670ae3b4d [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 <fbl/auto_lock.h>
#include <fuchsia/device/c/fidl.h>
#include <fuchsia/io/llcpp/fidl.h>
#include <lib/zx/vmo.h>
#include <zircon/status.h>
#include "../shared/env.h"
#include "../shared/fidl_txn.h"
#include "../shared/log.h"
#include "connection-destroyer.h"
#include "devhost.h"
#include "proxy-iostate.h"
#include "zx-device.h"
namespace devmgr {
namespace {
namespace fuchsia = ::llcpp::fuchsia;
// Handles outstanding calls to fuchsia.device.manager.DeviceController/BindDriver
// and fuchsia.device.Controller/Bind.
zx_status_t BindReply(const fbl::RefPtr<zx_device_t>& dev,
DeviceControllerConnection::BindDriverCompleter::Sync completer,
zx_status_t status, zx::channel test_output = zx::channel()) {
zx_status_t bind_status = ZX_OK;
completer.Reply(status, std::move(test_output));
fs::FidlConnection conn(fidl_txn_t{}, ZX_HANDLE_INVALID, 0);
if (dev->PopBindConn(&conn)) {
bind_status = fuchsia_device_ControllerBind_reply(conn.Txn(), status);
}
return bind_status;
}
} // namespace
void DeviceControllerConnection::CompleteCompatibilityTests(
llcpp::fuchsia::device::manager::CompatibilityTestStatus status,
CompleteCompatibilityTestsCompleter::Sync completer) {
const auto& dev = this->dev();
fs::FidlConnection conn(fidl_txn_t{}, ZX_HANDLE_INVALID, 0);
if (dev->PopTestCompatibilityConn(&conn)) {
fuchsia_device_ControllerRunCompatibilityTests_reply(conn.Txn(), static_cast<uint32_t>(status));
}
}
void DeviceControllerConnection::Suspend(uint32_t flags, SuspendCompleter::Sync completer) {
zx_status_t r;
{
ApiAutoLock lock;
r = devhost_device_suspend(this->dev(), flags);
}
completer.Reply(r);
}
void DeviceControllerConnection::Resume(uint32_t target_system_state, ResumeCompleter::Sync completer) {
zx_status_t r;
{
ApiAutoLock lock;
r = devhost_device_resume(this->dev(), target_system_state);
}
completer.Reply(r);
}
void DeviceControllerConnection::ConnectProxy(::zx::channel shadow,
ConnectProxyCompleter::Sync _completer) {
log(RPC_SDW, "devhost connect proxy rpc\n");
this->dev()->ops->rxrpc(this->dev()->ctx, ZX_HANDLE_INVALID);
// Ignore any errors in the creation for now?
// TODO(teisenbe): Investigate if this is the right thing
ProxyIostate::Create(this->dev(), std::move(shadow), DevhostAsyncLoop()->dispatcher());
}
void DeviceControllerConnection::RemoveDevice(RemoveDeviceCompleter::Sync completer) {
device_remove(this->dev().get());
}
void DeviceControllerConnection::BindDriver(::fidl::StringView driver_path_view, zx::vmo driver,
BindDriverCompleter::Sync completer) {
const auto& dev = this->dev();
fbl::StringPiece driver_path(driver_path_view.data(), driver_path_view.size());
// get path
char buffer[512];
const char* path = mkdevpath(dev, buffer, sizeof(buffer));
// TODO: api lock integration
log(ERROR, "devhost[%s] bind driver '%.*s'\n", path, static_cast<int>(driver_path.size()),
driver_path.data());
fbl::RefPtr<zx_driver_t> drv;
if (dev->flags & DEV_FLAG_DEAD) {
log(ERROR, "devhost[%s] bind to removed device disallowed\n", path);
BindReply(dev, std::move(completer), ZX_ERR_IO_NOT_PRESENT);
return;
}
zx_status_t r;
if ((r = dh_find_driver(driver_path, std::move(driver), &drv)) < 0) {
log(ERROR, "devhost[%s] driver load failed: %d\n", path, r);
BindReply(dev, std::move(completer), r);
return;
}
// Check for driver test flags.
bool tests_default = getenv_bool("driver.tests.enable", false);
char tmp[128];
snprintf(tmp, sizeof(tmp), "driver.%s.tests.enable", drv->name());
zx::channel test_output;
if (getenv_bool(tmp, 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, std::move(test_input));
if (!tests_passed) {
log(ERROR, "devhost: driver '%s' unit tests failed\n", drv->name());
drv->set_status(ZX_ERR_BAD_STATE);
BindReply(dev, std::move(completer), ZX_ERR_BAD_STATE, std::move(test_output));
return;
}
log(INFO, "devhost: driver '%s' unit tests passed\n", drv->name());
}
if (drv->has_bind_op()) {
BindContext bind_ctx = {
.parent = dev,
.child = nullptr,
};
r = drv->BindOp(&bind_ctx, dev);
if ((r == ZX_OK) && (bind_ctx.child == nullptr)) {
printf("devhost: WARNING: driver '%.*s' did not add device in bind()\n",
static_cast<int>(driver_path.size()), driver_path.data());
}
if (r != ZX_OK) {
log(ERROR, "devhost[%s] bind driver '%.*s' failed: %d\n", path,
static_cast<int>(driver_path.size()), driver_path.data(), r);
}
BindReply(dev, std::move(completer), r, std::move(test_output));
return;
}
if (!drv->has_create_op()) {
log(ERROR, "devhost[%s] neither create nor bind are implemented: '%.*s'\n", path,
static_cast<int>(driver_path.size()), driver_path.data());
}
BindReply(dev, std::move(completer), ZX_ERR_NOT_SUPPORTED, std::move(test_output));
}
void DeviceControllerConnection::Unbind(UnbindCompleter::Sync completer) {
ApiAutoLock lock;
devhost_device_unbind(this->dev());
}
void DeviceControllerConnection::CompleteRemoval(CompleteRemovalCompleter::Sync completer) {
ApiAutoLock lock;
devhost_device_complete_removal(this->dev());
}
DeviceControllerConnection::DeviceControllerConnection(fbl::RefPtr<zx_device> dev, zx::channel rpc)
: dev_(std::move(dev)) {
dev_->rpc = zx::unowned_channel(rpc);
dev_->conn.store(this);
set_channel(std::move(rpc));
}
DeviceControllerConnection::~DeviceControllerConnection() {
// Ensure that the device has no dangling references to the resources we're
// destroying. This is safe because a device only ever has one associated
// DeviceControllerConnection.
dev_->conn.store(nullptr);
dev_->rpc = zx::unowned_channel();
}
zx_status_t DeviceControllerConnection::Create(fbl::RefPtr<zx_device> dev, zx::channel rpc,
std::unique_ptr<DeviceControllerConnection>* conn) {
*conn = std::make_unique<DeviceControllerConnection>(std::move(dev), std::move(rpc));
if (*conn == nullptr) {
return ZX_ERR_NO_MEMORY;
}
return ZX_OK;
}
// Handler for when a io.fidl open() is called on a device
void DeviceControllerConnection::Open(uint32_t flags, uint32_t mode, ::fidl::StringView path,
::zx::channel object, OpenCompleter::Sync completer) {
if (path.size() != 1 && path.data()[0] != '.') {
log(ERROR, "devhost: Tried to open path '%.*s'\n", static_cast<int>(path.size()), path.data());
}
devhost_device_connect(this->dev(), flags, std::move(object));
}
void DeviceControllerConnection::HandleRpc(std::unique_ptr<DeviceControllerConnection> conn,
async_dispatcher_t* dispatcher, async::WaitBase* wait,
zx_status_t status, const zx_packet_signal_t* signal) {
if (status != ZX_OK) {
log(ERROR, "devhost: devcoord conn wait error: %d\n", status);
return;
}
if (signal->observed & ZX_CHANNEL_READABLE) {
zx_status_t r = conn->HandleRead();
if (r != ZX_OK) {
if (conn->dev_->conn.load() == nullptr && (r == ZX_ERR_INTERNAL || r == ZX_ERR_PEER_CLOSED)) {
// Treat this as a PEER_CLOSED below. It can happen if the
// devcoordinator sent us a request while we asked the
// devcoordinator to remove us. The coordinator then closes the
// channel before we can reply, and the FIDL bindings convert
// the PEER_CLOSED on zx_channel_write() to a ZX_ERR_INTERNAL. See ZX-4114.
__UNUSED auto r = conn.release();
return;
}
log(ERROR, "devhost: devmgr rpc unhandleable ios=%p r=%d. fatal.\n", conn.get(), r);
abort();
}
BeginWait(std::move(conn), dispatcher);
return;
}
if (signal->observed & ZX_CHANNEL_PEER_CLOSED) {
// Check if we were expecting this peer close. If not, this could be a
// serious bug.
if (conn->dev_->conn.load() == nullptr) {
// 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.
__UNUSED auto r = conn.release();
return;
}
log(ERROR, "devhost: devmgr disconnected! fatal. (conn=%p)\n", conn.get());
abort();
}
log(ERROR, "devhost: no work? %08x\n", signal->observed);
BeginWait(std::move(conn), dispatcher);
}
zx_status_t DeviceControllerConnection::HandleRead() {
zx::unowned_channel conn = channel();
uint8_t msg[8192];
zx_handle_t hin[ZX_CHANNEL_MAX_MSG_HANDLES];
uint32_t msize = sizeof(msg);
uint32_t hcount = fbl::count_of(hin);
zx_status_t status = conn->read(0, msg, hin, msize, hcount, &msize, &hcount);
if (status != ZX_OK) {
return status;
}
fidl_msg_t fidl_msg = {
.bytes = msg,
.handles = hin,
.num_bytes = msize,
.num_handles = hcount,
};
if (fidl_msg.num_bytes < sizeof(fidl_message_header_t)) {
zx_handle_close_many(fidl_msg.handles, fidl_msg.num_handles);
return ZX_ERR_IO;
}
char buffer[512];
const char* path = mkdevpath(dev_, buffer, sizeof(buffer));
auto hdr = static_cast<fidl_message_header_t*>(fidl_msg.bytes);
// Depending on the state of the migration, GenOrdinal and Ordinal may be the
// same value. See FIDL-524.
uint64_t ordinal = hdr->ordinal;
if (ordinal == fuchsia_io_DirectoryOpenOrdinal || ordinal == fuchsia_io_DirectoryOpenGenOrdinal) {
log(RPC_RIO, "devhost[%s] FIDL OPEN\n", path);
zx::unowned_channel conn = channel();
DevmgrFidlTxn txn(std::move(conn), hdr->txid);
fuchsia::io::Directory::Dispatch(this, &fidl_msg, &txn);
if (status != ZX_OK) {
return txn.Status();
}
return txn.Status();
}
DevmgrFidlTxn txn(std::move(conn), hdr->txid);
fuchsia::device::manager::DeviceController::Dispatch(this, &fidl_msg, &txn);
return txn.Status();
}
} // namespace devmgr