blob: 9dd21e9111f89d735bd16df10807c8933b2659b6 [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 <lib/zx/vmo.h>
#include <zircon/status.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 {
struct DevhostRpcReadContext {
const char* path;
DeviceControllerConnection* conn;
};
// 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, fidl_txn_t* txn, zx_status_t status) {
zx_status_t bind_driver_status =
fuchsia_device_manager_DeviceControllerBindDriver_reply(txn, status);
zx_status_t bind_status = ZX_OK;
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_driver_status != ZX_OK ? bind_driver_status : bind_status;
}
zx_status_t fidl_BindDriver(void* raw_ctx, const char* driver_path_data,
size_t driver_path_size, zx_handle_t raw_driver_vmo,
fidl_txn_t* txn) {
auto ctx = static_cast<DevhostRpcReadContext*>(raw_ctx);
const auto& dev = ctx->conn->dev();
zx::vmo driver_vmo(raw_driver_vmo);
fbl::StringPiece driver_path(driver_path_data, driver_path_size);
// TODO: api lock integration
log(RPC_IN, "devhost[%s] bind driver '%.*s'\n", ctx->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", ctx->path);
return BindReply(dev, txn, ZX_ERR_IO_NOT_PRESENT);
}
zx_status_t r;
if ((r = dh_find_driver(driver_path, std::move(driver_vmo), &drv)) < 0) {
log(ERROR, "devhost[%s] driver load failed: %d\n", ctx->path, r);
return BindReply(dev, txn, r);
}
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", ctx->path,
static_cast<int>(driver_path_size), driver_path_data, r);
}
return BindReply(dev, txn, r);
}
if (!drv->has_create_op()) {
log(ERROR, "devhost[%s] neither create nor bind are implemented: '%.*s'\n", ctx->path,
static_cast<int>(driver_path_size), driver_path_data);
}
return BindReply(dev, txn, ZX_ERR_NOT_SUPPORTED);
}
zx_status_t fidl_ConnectProxy(void* raw_ctx, zx_handle_t raw_shadow) {
auto ctx = static_cast<DevhostRpcReadContext*>(raw_ctx);
zx::channel shadow(raw_shadow);
log(RPC_SDW, "devhost[%s] connect proxy rpc\n", ctx->path);
ctx->conn->dev()->ops->rxrpc(ctx->conn->dev()->ctx, ZX_HANDLE_INVALID);
// Ignore any errors in the creation for now?
// TODO(teisenbe/kulakowski): Investigate if this is the right thing
ProxyIostate::Create(ctx->conn->dev(), std::move(shadow), DevhostAsyncLoop()->dispatcher());
return ZX_OK;
}
zx_status_t fidl_Suspend(void* raw_ctx, uint32_t flags, fidl_txn_t* txn) {
auto ctx = static_cast<DevhostRpcReadContext*>(raw_ctx);
zx_status_t r;
{
ApiAutoLock lock;
r = devhost_device_suspend(ctx->conn->dev(), flags);
}
return fuchsia_device_manager_DeviceControllerSuspend_reply(txn, r);
}
zx_status_t fidl_RemoveDevice(void* raw_ctx) {
auto ctx = static_cast<DevhostRpcReadContext*>(raw_ctx);
device_remove(ctx->conn->dev().get());
return ZX_OK;
}
// Handler for when open() is called on a device
zx_status_t fidl_DirectoryOpen(void* ctx, uint32_t flags, uint32_t mode, const char* path_data,
size_t path_size, zx_handle_t object) {
zx::channel c(object);
if (path_size != 1 && path_data[0] != '.') {
log(ERROR, "devhost: Tried to open path '%.*s'\n", static_cast<int>(path_size), path_data);
return ZX_OK;
}
auto conn = static_cast<DeviceControllerConnection*>(ctx);
devhost_device_connect(conn->dev(), flags, std::move(c));
return ZX_OK;
}
const fuchsia_device_manager_DeviceController_ops_t kDefaultDeviceOps = {
.BindDriver = fidl_BindDriver,
.ConnectProxy = fidl_ConnectProxy,
.RemoveDevice = fidl_RemoveDevice,
.Suspend = fidl_Suspend,
};
const fuchsia_io_Directory_ops_t kDefaultDirectoryOps = []() {
fuchsia_io_Directory_ops_t ops;
ops.Open = fidl_DirectoryOpen;
return ops;
}();
zx_status_t dh_null_reply(fidl_txn_t* reply, const fidl_msg_t* msg) {
return ZX_OK;
}
} // namespace
DeviceControllerConnection::DeviceControllerConnection(
fbl::RefPtr<zx_device> dev, zx::channel rpc,
const fuchsia_device_manager_DeviceController_ops_t* device_fidl_ops,
const fuchsia_io_Directory_ops_t* directory_fidl_ops)
: dev_(std::move(dev)), device_fidl_ops_(device_fidl_ops),
directory_fidl_ops_(directory_fidl_ops) {
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) {
return Create(std::move(dev), std::move(rpc), &kDefaultDeviceOps, &kDefaultDirectoryOps, conn);
}
zx_status_t DeviceControllerConnection::Create(
fbl::RefPtr<zx_device> dev, zx::channel rpc,
const fuchsia_device_manager_DeviceController_ops_t* device_fidl_ops,
const fuchsia_io_Directory_ops_t* directory_fidl_ops,
std::unique_ptr<DeviceControllerConnection>* conn) {
*conn = std::make_unique<DeviceControllerConnection>(std::move(dev), std::move(rpc),
device_fidl_ops, directory_fidl_ops);
if (*conn == nullptr) {
return ZX_ERR_NO_MEMORY;
}
return ZX_OK;
}
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) {
// 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));
// Double-check that Open (the only message we forward) cannot be mistaken for an
// internal dev coordinator RPC message.
static_assert(
fuchsia_device_manager_DevhostControllerCreateDeviceStubOrdinal !=
fuchsia_io_DirectoryOpenOrdinal &&
fuchsia_device_manager_DevhostControllerCreateDeviceOrdinal !=
fuchsia_io_DirectoryOpenOrdinal &&
fuchsia_device_manager_DeviceControllerBindDriverOrdinal !=
fuchsia_io_DirectoryOpenOrdinal &&
fuchsia_device_manager_DeviceControllerConnectProxyOrdinal !=
fuchsia_io_DirectoryOpenOrdinal &&
fuchsia_device_manager_DeviceControllerSuspendOrdinal !=
fuchsia_io_DirectoryOpenOrdinal &&
fuchsia_device_manager_DeviceControllerRemoveDeviceOrdinal !=
fuchsia_io_DirectoryOpenOrdinal);
auto hdr = static_cast<fidl_message_header_t*>(fidl_msg.bytes);
if (hdr->ordinal == fuchsia_io_DirectoryOpenOrdinal) {
log(RPC_RIO, "devhost[%s] FIDL OPEN\n", path);
fidl_txn_t dh_null_txn = {
.reply = dh_null_reply,
};
status = fuchsia_io_Directory_dispatch(this, &dh_null_txn, &fidl_msg, directory_fidl_ops_);
if (status != ZX_OK) {
log(ERROR, "devhost: OPEN failed: %s\n", zx_status_get_string(status));
return status;
}
return ZX_OK;
}
FidlTxn txn(std::move(conn), hdr->txid);
DevhostRpcReadContext read_ctx = {path, this};
return fuchsia_device_manager_DeviceController_dispatch(&read_ctx, txn.fidl_txn(), &fidl_msg,
device_fidl_ops_);
}
} // namespace devmgr