blob: f7e44db9a1cffc6ca23aa998036f17bb49c18eea [file] [log] [blame]
// Copyright 2017 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 "devhost.h"
#include <dlfcn.h>
#include <inttypes.h>
#include <new>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <utility>
#include <ddk/debug.h>
#include <ddk/device.h>
#include <ddk/driver.h>
#include <ddk/binding.h>
#include <zircon/dlfcn.h>
#include <zircon/process.h>
#include <zircon/processargs.h>
#include <zircon/syscalls.h>
#include <zircon/syscalls/log.h>
#include <fbl/auto_lock.h>
#include <fbl/function.h>
#include <fs/handler.h>
#include <fuchsia/device/manager/c/fidl.h>
#include <fuchsia/io/c/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async/cpp/receiver.h>
#include <lib/async/cpp/wait.h>
#include <lib/fdio/util.h>
#include <lib/fidl/coding.h>
#include <lib/zx/debuglog.h>
#include <lib/zx/resource.h>
#include <lib/zx/vmo.h>
#include <lib/zxio/null.h>
#include "../shared/async-loop-owned-rpc-handler.h"
#include "main.h"
#if ENABLE_DRIVER_TRACING
#include "tracing.h"
#endif
#include "../shared/env.h"
#include "../shared/fidl_txn.h"
#include "../shared/log.h"
zx_status_t zx_driver::Create(fbl::RefPtr<zx_driver>* out_driver) {
*out_driver = fbl::AdoptRef(new zx_driver());
return ZX_OK;
}
namespace devmgr {
uint32_t log_flags = LOG_ERROR | LOG_INFO;
struct ProxyIostate : AsyncLoopOwnedRpcHandler<ProxyIostate> {
ProxyIostate() = default;
~ProxyIostate();
// Creates a ProxyIostate and points |dev| at it. The ProxyIostate is owned
// by the async loop, and its destruction may be requested by calling
// Cancel().
static zx_status_t Create(const fbl::RefPtr<zx_device_t>& dev, zx::channel rpc);
// Request the destruction of the proxy connection
void Cancel();
static void HandleRpc(fbl::unique_ptr<ProxyIostate> conn,
async_dispatcher_t* dispatcher, async::WaitBase* wait, zx_status_t status,
const zx_packet_signal_t* signal);
fbl::RefPtr<zx_device_t> dev;
};
static void proxy_ios_destroy(const fbl::RefPtr<zx_device_t>& dev);
static fbl::DoublyLinkedList<fbl::RefPtr<zx_driver>> dh_drivers;
// Access the devhost's async event loop
async::Loop* DevhostAsyncLoop() {
static async::Loop loop(&kAsyncLoopConfigAttachToThread);
return &loop;
}
static zx_status_t SetupRootDevcoordinatorConnection(zx::channel ch) {
auto conn = fbl::make_unique<DevcoordinatorConnection>();
if (conn == nullptr) {
return ZX_ERR_NO_MEMORY;
}
conn->set_channel(std::move(ch));
return DevcoordinatorConnection::BeginWait(std::move(conn), DevhostAsyncLoop()->dispatcher());
}
// Handles destroying Connection objects in the single-threaded DevhostAsyncLoop().
// This allows us to prevent races between canceling waiting on the connection
// channel and executing the connection's handler.
class ConnectionDestroyer {
public:
static ConnectionDestroyer* Get() {
static ConnectionDestroyer destroyer;
return &destroyer;
}
zx_status_t QueueDevcoordinatorConnection(DevcoordinatorConnection* conn);
zx_status_t QueueProxyConnection(ProxyIostate* conn);
private:
ConnectionDestroyer() = default;
ConnectionDestroyer(const ConnectionDestroyer&) = delete;
ConnectionDestroyer& operator=(const ConnectionDestroyer&) = delete;
ConnectionDestroyer(ConnectionDestroyer&&) = delete;
ConnectionDestroyer& operator=(ConnectionDestroyer&&) = delete;
static void Handler(async_dispatcher_t* dispatcher, async::Receiver* receiver,
zx_status_t status, const zx_packet_user_t* data);
enum class Type {
Devcoordinator,
Proxy,
};
async::Receiver receiver_{ConnectionDestroyer::Handler};
};
zx_status_t ConnectionDestroyer::QueueProxyConnection(ProxyIostate* conn) {
zx_packet_user_t pkt = {};
pkt.u64[0] = static_cast<uint64_t>(Type::Proxy);
pkt.u64[1] = reinterpret_cast<uintptr_t>(conn);
return receiver_.QueuePacket(DevhostAsyncLoop()->dispatcher(), &pkt);
}
zx_status_t ConnectionDestroyer::QueueDevcoordinatorConnection(DevcoordinatorConnection* conn) {
zx_packet_user_t pkt = {};
pkt.u64[0] = static_cast<uint64_t>(Type::Devcoordinator);
pkt.u64[1] = reinterpret_cast<uintptr_t>(conn);
return receiver_.QueuePacket(DevhostAsyncLoop()->dispatcher(), &pkt);
}
void ConnectionDestroyer::Handler(async_dispatcher_t* dispatcher, async::Receiver* receiver,
zx_status_t status, const zx_packet_user_t* data) {
Type type = static_cast<Type>(data->u64[0]);
uintptr_t ptr = data->u64[1];
switch (type) {
case Type::Devcoordinator: {
auto conn = reinterpret_cast<DevcoordinatorConnection*>(ptr);
log(TRACE, "devhost: destroying devcoord conn '%p'\n", conn);
delete conn;
break;
}
case Type::Proxy: {
auto conn = reinterpret_cast<ProxyIostate*>(ptr);
log(TRACE, "devhost: destroying proxy conn '%p'\n", conn);
delete conn;
break;
}
default:
ZX_ASSERT_MSG(false, "Unknown IosDestructionType %" PRIu64 "\n", data->u64[0]);
}
}
static const char* mkdevpath(const fbl::RefPtr<zx_device_t>& dev, char* path, size_t max) {
if (dev == nullptr) {
return "";
}
if (max < 1) {
return "<invalid>";
}
char* end = path + max;
char sep = 0;
fbl::RefPtr<zx_device> itr_dev(dev);
while (itr_dev) {
*(--end) = sep;
size_t len = strlen(itr_dev->name);
if (len > (size_t)(end - path)) {
break;
}
end -= len;
memcpy(end, itr_dev->name, len);
sep = '/';
itr_dev = itr_dev->parent;
}
return end;
}
static uint32_t logflagval(char* flag) {
if (!strcmp(flag, "error")) {
return DDK_LOG_ERROR;
}
if (!strcmp(flag, "warn")) {
return DDK_LOG_WARN;
}
if (!strcmp(flag, "info")) {
return DDK_LOG_INFO;
}
if (!strcmp(flag, "trace")) {
return DDK_LOG_TRACE;
}
if (!strcmp(flag, "spew")) {
return DDK_LOG_SPEW;
}
if (!strcmp(flag, "debug1")) {
return DDK_LOG_DEBUG1;
}
if (!strcmp(flag, "debug2")) {
return DDK_LOG_DEBUG2;
}
if (!strcmp(flag, "debug3")) {
return DDK_LOG_DEBUG3;
}
if (!strcmp(flag, "debug4")) {
return DDK_LOG_DEBUG4;
}
return static_cast<uint32_t>(strtoul(flag, nullptr, 0));
}
static void logflag(char* flag, uint32_t* flags) {
if (*flag == '+') {
*flags |= logflagval(flag + 1);
} else if (*flag == '-') {
*flags &= ~logflagval(flag + 1);
}
}
static zx_status_t dh_find_driver(fbl::StringPiece libname, zx::vmo vmo,
fbl::RefPtr<zx_driver_t>* out) {
// check for already-loaded driver first
for (auto& drv : dh_drivers) {
if (!libname.compare(drv.libname())) {
*out = fbl::RefPtr(&drv);
return drv.status();
}
}
fbl::RefPtr<zx_driver> new_driver;
zx_status_t status = zx_driver::Create(&new_driver);
if (status != ZX_OK) {
return status;
}
new_driver->set_libname(libname);
// Let the |dh_drivers| list and our out parameter each have a refcount.
dh_drivers.push_back(new_driver);
*out = new_driver;
const char* c_libname = new_driver->libname().c_str();
void* dl = dlopen_vmo(vmo.get(), RTLD_NOW);
if (dl == nullptr) {
log(ERROR, "devhost: cannot load '%s': %s\n", c_libname, dlerror());
new_driver->set_status(ZX_ERR_IO);
return new_driver->status();
}
const zircon_driver_note_t* dn;
dn = static_cast<const zircon_driver_note_t*>(dlsym(dl, "__zircon_driver_note__"));
if (dn == nullptr) {
log(ERROR, "devhost: driver '%s' missing __zircon_driver_note__ symbol\n", c_libname);
new_driver->set_status(ZX_ERR_IO);
return new_driver->status();
}
zx_driver_rec_t* dr;
dr = static_cast<zx_driver_rec_t*>(dlsym(dl, "__zircon_driver_rec__"));
if (dr == nullptr) {
log(ERROR, "devhost: driver '%s' missing __zircon_driver_rec__ symbol\n", c_libname);
new_driver->set_status(ZX_ERR_IO);
return new_driver->status();
}
if (!dr->ops) {
log(ERROR, "devhost: driver '%s' has nullptr ops\n", c_libname);
new_driver->set_status(ZX_ERR_INVALID_ARGS);
return new_driver->status();
}
if (dr->ops->version != DRIVER_OPS_VERSION) {
log(ERROR, "devhost: driver '%s' has bad driver ops version %" PRIx64
", expecting %" PRIx64 "\n", c_libname, dr->ops->version, DRIVER_OPS_VERSION);
new_driver->set_status(ZX_ERR_INVALID_ARGS);
return new_driver->status();
}
new_driver->set_driver_rec(dr);
new_driver->set_name(dn->payload.name);
new_driver->set_ops(dr->ops);
dr->driver = new_driver.get();
// check for dprintf log level flags
char tmp[128];
snprintf(tmp, sizeof(tmp), "driver.%s.log", new_driver->name());
char* log = getenv(tmp);
if (log) {
while (log) {
char* sep = strchr(log, ',');
if (sep) {
*sep = 0;
logflag(log, &dr->log_flags);
*sep = ',';
log = sep + 1;
} else {
logflag(log, &dr->log_flags);
break;
}
}
log(INFO, "devhost: driver '%s': log flags set to: 0x%x\n", new_driver->name(), dr->log_flags);
}
if (new_driver->has_init_op()) {
new_driver->set_status(new_driver->InitOp());
if (new_driver->status() != ZX_OK) {
log(ERROR, "devhost: driver '%s' failed in init: %d\n",
c_libname, new_driver->status());
}
} else {
new_driver->set_status(ZX_OK);
}
return new_driver->status();
}
static zx_status_t dh_null_reply(fidl_txn_t* reply, const fidl_msg_t* msg) {
return ZX_OK;
}
static fidl_txn_t dh_null_txn = {
.reply = dh_null_reply,
};
struct DevhostRpcReadContext {
const char* path;
DevcoordinatorConnection* conn;
};
// Handler for when open() is called on a device
static zx_status_t fidl_devcoord_connection_directory_open(void* ctx, uint32_t flags, uint32_t mode,
const char* path_data, size_t path_size,
zx_handle_t object) {
auto conn = static_cast<DevcoordinatorConnection*>(ctx);
zx::channel c(object);
return devhost_device_connect(conn->dev, flags, path_data, path_size, std::move(c));
}
static const fuchsia_io_Directory_ops_t kDevcoordinatorConnectionDirectoryOps = []() {
fuchsia_io_Directory_ops_t ops;
ops.Open = fidl_devcoord_connection_directory_open;
return ops;
}();
static zx_status_t fidl_CreateDeviceStub(void* raw_ctx, zx_handle_t raw_rpc, uint32_t protocol_id) {
auto ctx = static_cast<DevhostRpcReadContext*>(raw_ctx);
zx::channel rpc(raw_rpc);
log(RPC_IN, "devhost[%s] create device stub\n", ctx->path);
auto newconn = fbl::make_unique<DevcoordinatorConnection>();
if (!newconn) {
return ZX_ERR_NO_MEMORY;
}
fbl::RefPtr<zx_device_t> dev;
zx_status_t r = zx_device::Create(&dev);
//TODO: dev->ops and other lifecycle bits
// no name means a dummy proxy device
if (r != ZX_OK) {
return r;
}
strcpy(dev->name, "proxy");
dev->protocol_id = protocol_id;
dev->ops = &device_default_ops;
dev->rpc = zx::unowned_channel(rpc);
newconn->dev = dev;
newconn->set_channel(std::move(rpc));
log(RPC_IN, "devhost[%s] creating new stub conn=%p\n", ctx->path, newconn.get());
if ((r = DevcoordinatorConnection::BeginWait(std::move(newconn),
DevhostAsyncLoop()->dispatcher())) != ZX_OK) {
return r;
}
return ZX_OK;
}
static zx_status_t fidl_CreateDevice(void* raw_ctx, zx_handle_t raw_rpc,
const char* driver_path_data, size_t driver_path_size,
zx_handle_t raw_driver_vmo, zx_handle_t raw_parent_proxy,
const char* proxy_args_data, size_t proxy_args_size) {
auto ctx = static_cast<DevhostRpcReadContext*>(raw_ctx);
zx::channel rpc(raw_rpc);
zx::vmo driver_vmo(raw_driver_vmo);
zx::handle parent_proxy(raw_parent_proxy);
fbl::StringPiece driver_path(driver_path_data, driver_path_size);
// This does not operate under the devhost api lock,
// since the newly created device is not visible to
// any API surface until a driver is bound to it.
// (which can only happen via another message on this thread)
log(RPC_IN, "devhost[%s] create device drv='%.*s' args='%.*s'\n", ctx->path,
static_cast<int>(driver_path_size), driver_path_data,
static_cast<int>(proxy_args_size), proxy_args_data);
auto newconn = fbl::make_unique<DevcoordinatorConnection>();
if (!newconn) {
return ZX_ERR_NO_MEMORY;
}
// named driver -- ask it to create the device
fbl::RefPtr<zx_driver_t> drv;
zx_status_t r = dh_find_driver(driver_path, std::move(driver_vmo), &drv);
if (r != ZX_OK) {
log(ERROR, "devhost[%s] driver load failed: %d\n", ctx->path, r);
return r;
}
if (drv->has_create_op()) {
// Create a dummy parent device for use in this call to Create
fbl::RefPtr<zx_device> parent;
if ((r = zx_device::Create(&parent)) != ZX_OK) {
return r;
}
// magic cookie for device create handshake
char dummy_name[sizeof(parent->name)] = "device_create dummy";
memcpy(&parent->name, &dummy_name, sizeof(parent->name));
CreationContext creation_context = {
.parent = std::move(parent),
.child = nullptr,
.rpc = zx::unowned_channel(rpc),
};
char proxy_args[fuchsia_device_manager_DEVICE_ARGS_MAX + 1];
memcpy(proxy_args, proxy_args_data, proxy_args_size);
proxy_args[proxy_args_size] = 0;
r = drv->CreateOp(&creation_context, creation_context.parent, "proxy", proxy_args, parent_proxy.release());
// Suppress a warning about dummy device being in a bad state. The
// message is spurious in this case, since the dummy parent never
// actually begins its device lifecycle. This flag is ordinarily
// set by device_remove().
creation_context.parent->flags |= DEV_FLAG_VERY_DEAD;
if (r != ZX_OK) {
log(ERROR, "devhost[%s] driver create() failed: %d\n", ctx->path, r);
return r;
}
newconn->dev = std::move(creation_context.child);
if (newconn->dev == nullptr) {
log(ERROR, "devhost[%s] driver create() failed to create a device!", ctx->path);
return ZX_ERR_BAD_STATE;
}
} else {
log(ERROR, "devhost[%s] driver create() not supported\n", ctx->path);
return ZX_ERR_NOT_SUPPORTED;
}
//TODO: inform devcoord
newconn->set_channel(std::move(rpc));
log(RPC_IN, "devhost[%s] creating '%.*s' conn=%p\n", ctx->path,
static_cast<int>(driver_path_size), driver_path_data, newconn.get());
if ((r = DevcoordinatorConnection::BeginWait(std::move(newconn),
DevhostAsyncLoop()->dispatcher())) != ZX_OK) {
return r;
}
return ZX_OK;
}
static 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);
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 (ctx->conn->dev->flags & DEV_FLAG_DEAD) {
log(ERROR, "devhost[%s] bind to removed device disallowed\n", ctx->path);
return fuchsia_device_manager_ControllerBindDriver_reply(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 fuchsia_device_manager_ControllerBindDriver_reply(txn, r);
}
if (drv->has_bind_op()) {
CreationContext creation_ctx = {
.parent = ctx->conn->dev,
.child = nullptr,
.rpc = zx::unowned_channel(),
};
r = drv->BindOp(&creation_ctx, ctx->conn->dev);
if ((r == ZX_OK) && (creation_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 fuchsia_device_manager_ControllerBindDriver_reply(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 fuchsia_device_manager_ControllerBindDriver_reply(txn, ZX_ERR_NOT_SUPPORTED);
}
static 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));
return ZX_OK;
}
static zx_status_t fidl_Suspend(void* raw_ctx, uint32_t flags, fidl_txn_t* txn) {
auto ctx = static_cast<DevhostRpcReadContext*>(raw_ctx);
// call suspend on the device this devhost is rooted on
fbl::RefPtr<zx_device_t> device = ctx->conn->dev;
while (device->parent != nullptr) {
device = device->parent;
}
zx_status_t r;
{
ApiAutoLock lock;
r = devhost_device_suspend(device, flags);
}
// TODO(teisenbe): We should probably check this return...
fuchsia_device_manager_ControllerSuspend_reply(txn, r);
return ZX_OK;
}
static zx_status_t fidl_RemoveDevice(void* raw_ctx) {
auto ctx = static_cast<DevhostRpcReadContext*>(raw_ctx);
device_remove(ctx->conn->dev.get());
return ZX_OK;
}
static fuchsia_device_manager_Controller_ops_t fidl_ops = {
.CreateDeviceStub = fidl_CreateDeviceStub,
.CreateDevice = fidl_CreateDevice,
.BindDriver = fidl_BindDriver,
.ConnectProxy = fidl_ConnectProxy,
.Suspend = fidl_Suspend,
.RemoveDevice = fidl_RemoveDevice,
};
static zx_status_t dh_handle_rpc_read(zx_handle_t h, DevcoordinatorConnection* conn) {
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 r;
if ((r = zx_channel_read(h, 0, &msg, hin, msize, hcount, &msize, &hcount)) != ZX_OK) {
return r;
}
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(conn->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_ControllerCreateDeviceStubOrdinal !=
fuchsia_io_DirectoryOpenOrdinal &&
fuchsia_device_manager_ControllerCreateDeviceOrdinal != fuchsia_io_DirectoryOpenOrdinal &&
fuchsia_device_manager_ControllerBindDriverOrdinal != fuchsia_io_DirectoryOpenOrdinal &&
fuchsia_device_manager_ControllerConnectProxyOrdinal != fuchsia_io_DirectoryOpenOrdinal &&
fuchsia_device_manager_ControllerSuspendOrdinal != fuchsia_io_DirectoryOpenOrdinal &&
fuchsia_device_manager_ControllerRemoveDeviceOrdinal != 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);
r = fuchsia_io_Directory_dispatch(conn, &dh_null_txn, &fidl_msg,
&kDevcoordinatorConnectionDirectoryOps);
if (r != ZX_OK) {
log(ERROR, "devhost: OPEN failed: %d\n", r);
return r;
}
return ZX_OK;
}
FidlTxn txn(zx::unowned_channel(h), hdr->txid);
DevhostRpcReadContext read_ctx = { path, conn };
return fuchsia_device_manager_Controller_dispatch(&read_ctx, txn.fidl_txn(), &fidl_msg,
&fidl_ops);
}
// handles devcoordinator rpc
void DevcoordinatorConnection::HandleRpc(fbl::unique_ptr<DevcoordinatorConnection> 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 = dh_handle_rpc_read(wait->object(), conn.get());
if (r != ZX_OK) {
log(ERROR, "devhost: devmgr rpc unhandleable ios=%p r=%d. fatal.\n", conn.get(), r);
exit(0);
}
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());
exit(0);
}
log(ERROR, "devhost: no work? %08x\n", signal->observed);
BeginWait(std::move(conn), dispatcher);
}
// handles remoteio rpc
void DevfsConnection::HandleRpc(fbl::unique_ptr<DevfsConnection> 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: devfs conn wait error: %d\n", status);
return;
}
if (signal->observed & ZX_CHANNEL_READABLE) {
status = fs::ReadMessage(wait->object(), [&conn](fidl_msg_t* msg, fs::FidlConnection* txn) {
return devhost_fidl_handler(msg, txn->Txn(), conn.get());
});
if (status == ZX_OK) {
BeginWait(std::move(conn), dispatcher);
return;
}
} else if (signal->observed & ZX_CHANNEL_PEER_CLOSED) {
fs::CloseMessage([&conn](fidl_msg_t* msg, fs::FidlConnection* txn) {
return devhost_fidl_handler(msg, txn->Txn(), conn.get());
});
} else {
printf("dh_handle_fidl_rpc: invalid signals %x\n", signal->observed);
exit(0);
}
// We arrive here if devhost_fidl_handler was a clean close (ERR_DISPATCHER_DONE),
// or close-due-to-error (non-ZX_OK), or if the channel was closed
// out from under us. In all cases, we are done with this connection, so we
// will destroy it by letting it leave scope.
log(TRACE, "devhost: destroying devfs conn %p\n", conn.get());
}
ProxyIostate::~ProxyIostate() {
fbl::AutoLock guard(&dev->proxy_ios_lock);
if (dev->proxy_ios == this) {
dev->proxy_ios = nullptr;
}
}
// Handling RPC From Proxy Devices to BusDevs
void ProxyIostate::HandleRpc(fbl::unique_ptr<ProxyIostate> conn, async_dispatcher_t* dispatcher,
async::WaitBase* wait, zx_status_t status,
const zx_packet_signal_t* signal) {
if (status != ZX_OK) {
return;
}
if (conn->dev == nullptr) {
log(RPC_SDW, "proxy-rpc: stale rpc? (ios=%p)\n", conn.get());
// Do not re-issue the wait here
return;
}
if (signal->observed & ZX_CHANNEL_READABLE) {
log(RPC_SDW, "proxy-rpc: rpc readable (ios=%p,dev=%p)\n", conn.get(), conn->dev.get());
zx_status_t r = conn->dev->ops->rxrpc(conn->dev->ctx, wait->object());
if (r != ZX_OK) {
log(RPC_SDW, "proxy-rpc: rpc cb error %d (ios=%p,dev=%p)\n", r, conn.get(),
conn->dev.get());
// Let |conn| be destroyed
return;
}
BeginWait(std::move(conn), dispatcher);
return;
}
if (signal->observed & ZX_CHANNEL_PEER_CLOSED) {
log(RPC_SDW, "proxy-rpc: peer closed (ios=%p,dev=%p)\n", conn.get(), conn->dev.get());
// Let |conn| be destroyed
return;
}
log(ERROR, "devhost: no work? %08x\n", signal->observed);
BeginWait(std::move(conn), dispatcher);
}
zx_status_t ProxyIostate::Create(const fbl::RefPtr<zx_device_t>& dev, zx::channel rpc) {
// This must be held for the adding of the channel to the port, since the
// async loop may run immediately after that point.
fbl::AutoLock guard(&dev->proxy_ios_lock);
if (dev->proxy_ios) {
dev->proxy_ios->Cancel();
dev->proxy_ios = nullptr;
}
auto ios = fbl::make_unique<ProxyIostate>();
if (ios == nullptr) {
return ZX_ERR_NO_MEMORY;
}
ios->dev = dev;
ios->set_channel(std::move(rpc));
// |ios| is will be owned by the async loop. |dev| holds a reference that will be
// cleared prior to destruction.
dev->proxy_ios = ios.get();
zx_status_t status = BeginWait(std::move(ios), DevhostAsyncLoop()->dispatcher());
if (status != ZX_OK) {
dev->proxy_ios = nullptr;
return status;
}
return ZX_OK;
}
// The device for which ProxyIostate is currently attached to should have
// its proxy_ios_lock held across Cancel().
void ProxyIostate::Cancel() {
// TODO(teisenbe): We should probably check the return code in case the
// queue was full
ConnectionDestroyer::Get()->QueueProxyConnection(this);
}
static void proxy_ios_destroy(const fbl::RefPtr<zx_device_t>& dev) {
fbl::AutoLock guard(&dev->proxy_ios_lock);
if (dev->proxy_ios) {
dev->proxy_ios->Cancel();
}
dev->proxy_ios = nullptr;
}
#define LOGBUF_MAX (ZX_LOG_RECORD_MAX - sizeof(zx_log_record_t))
static zx::debuglog devhost_log_handle;
static ssize_t devhost_log_write_internal(uint32_t flags, const void* void_data, size_t len) {
struct Context {
Context() = default;
uint32_t next = 0;
zx::unowned_debuglog handle;
char data[LOGBUF_MAX] = {};
};
static thread_local fbl::unique_ptr<Context> ctx;
if (ctx == nullptr) {
ctx = fbl::make_unique<Context>();
if (ctx == nullptr) {
return len;
}
ctx->handle = zx::unowned_debuglog(devhost_log_handle);
}
const char* data = static_cast<const char*>(void_data);
size_t r = len;
auto flush_context = [&]() {
ctx->handle->write(flags, ctx->data, ctx->next);
ctx->next = 0;
};
while (len-- > 0) {
char c = *data++;
if (c == '\n') {
if (ctx->next) {
flush_context();
}
continue;
}
if (c < ' ') {
continue;
}
ctx->data[ctx->next++] = c;
if (ctx->next == LOGBUF_MAX) {
flush_context();
}
}
return r;
}
} // namespace devmgr
__EXPORT void driver_printf(uint32_t flags, const char* fmt, ...) {
char buffer[512];
va_list ap;
va_start(ap, fmt);
int r = vsnprintf(buffer, sizeof(buffer), fmt, ap);
va_end(ap);
if (r > (int)sizeof(buffer)) {
r = sizeof(buffer);
}
devmgr::devhost_log_write_internal(flags, buffer, r);
}
namespace devmgr {
static zx_status_t devhost_log_write(zxio_t* io, const void* buffer,
size_t capacity, size_t* out_actual) {
devhost_log_write_internal(0, buffer, capacity);
*out_actual = capacity;
return ZX_OK;
}
static constexpr zxio_ops_t devhost_log_ops = []() {
zxio_ops_t ops = zxio_default_ops;
ops.write = devhost_log_write;
return ops;
}();
static void devhost_io_init() {
if (zx::debuglog::create(zx::resource(), 0, &devhost_log_handle) < 0) {
return;
}
fdio_t* io = nullptr;
zxio_storage_t* storage = nullptr;
if ((io = fdio_zxio_create(&storage)) == nullptr) {
return;
}
zxio_init(&storage->io, &devhost_log_ops);
close(1);
fdio_bind_to_fd(io, 1, 0);
dup2(1, 2);
}
// Send message to devcoordinator asking to add child device to
// parent device. Called under devhost api lock.
zx_status_t devhost_add(const fbl::RefPtr<zx_device_t>& parent,
const fbl::RefPtr<zx_device_t>& child, const char* proxy_args,
const zx_device_prop_t* props, uint32_t prop_count,
zx::channel client_remote) {
char buffer[512];
const char* path = mkdevpath(parent, buffer, sizeof(buffer));
log(RPC_OUT, "devhost[%s] add '%s'\n", path, child->name);
bool add_invisible = child->flags & DEV_FLAG_INVISIBLE;
auto conn = fbl::make_unique<DevcoordinatorConnection>();
if (!conn) {
return ZX_ERR_NO_MEMORY;
}
zx_status_t status;
zx::channel hrpc, hsend;
if ((status = zx::channel::create(0, &hrpc, &hsend)) != ZX_OK) {
return status;
}
const zx::channel& rpc = *parent->rpc;
if (!rpc.is_valid()) {
return ZX_ERR_IO_REFUSED;
}
size_t proxy_args_len = proxy_args ? strlen(proxy_args) : 0;
zx_status_t call_status;
static_assert(sizeof(zx_device_prop_t) == sizeof(uint64_t));
if (add_invisible) {
status = fuchsia_device_manager_CoordinatorAddDeviceInvisible(
rpc.get(), hsend.release(), reinterpret_cast<const uint64_t*>(props), prop_count,
child->name, strlen(child->name), child->protocol_id,
child->driver->libname().data(), child->driver->libname().size(), proxy_args,
proxy_args_len, client_remote.release(), &call_status);
} else {
status = fuchsia_device_manager_CoordinatorAddDevice(
rpc.get(), hsend.release(), reinterpret_cast<const uint64_t*>(props), prop_count,
child->name, strlen(child->name), child->protocol_id,
child->driver->libname().data(), child->driver->libname().size(), proxy_args,
proxy_args_len, client_remote.release(), &call_status);
}
if (status != ZX_OK) {
log(ERROR, "devhost[%s] add '%s': rpc sending failed: %d\n", path, child->name, status);
return status;
} else if (call_status != ZX_OK) {
log(ERROR, "devhost[%s] add '%s': rpc failed: %d\n", path, child->name, call_status);
return call_status;
}
child->rpc = zx::unowned_channel(hrpc);
child->conn.store(conn.get());
conn->dev = child;
conn->set_channel(std::move(hrpc));
status = DevcoordinatorConnection::BeginWait(std::move(conn), DevhostAsyncLoop()->dispatcher());
if (status != ZX_OK) {
child->conn.store(nullptr);
child->rpc = zx::unowned_channel();
return status;
}
return ZX_OK;
}
static void log_rpc(const fbl::RefPtr<zx_device_t>& dev, const char* opname) {
char buffer[512];
const char* path = mkdevpath(dev, buffer, sizeof(buffer));
log(RPC_OUT, "devhost[%s] %s'\n", path, opname);
}
static void log_rpc_result(const char* opname, zx_status_t status, zx_status_t call_status) {
if (status != ZX_OK) {
log(ERROR, "devhost: rpc:%s sending failed: %d\n", opname, status);
} else if (call_status != ZX_OK) {
log(ERROR, "devhost: rpc:%s failed: %d\n", opname, call_status);
}
}
void devhost_make_visible(const fbl::RefPtr<zx_device_t>& dev) {
const zx::channel& rpc = *dev->rpc;
if (!rpc.is_valid()) {
return;
}
// TODO(teisenbe): Handle failures here...
log_rpc(dev, "make-visible");
zx_status_t call_status;
zx_status_t status = fuchsia_device_manager_CoordinatorMakeVisible(
rpc.get(), &call_status);
log_rpc_result("make-visible", status, call_status);
}
// Send message to devcoordinator informing it that this device
// is being removed. Called under devhost api lock.
zx_status_t devhost_remove(const fbl::RefPtr<zx_device_t>& dev) {
DevcoordinatorConnection* conn = dev->conn.load();
if (conn == nullptr) {
log(ERROR, "removing device %p, conn is nullptr\n", dev.get());
return ZX_ERR_INTERNAL;
}
// This must be done before the RemoveDevice message is sent to
// devcoordinator, since devcoordinator will close the channel in response.
// The async loop may see the channel close before it sees the queued
// shutdown packet, so it needs to check if dev->conn has been nulled to
// handle that gracefully.
dev->conn.store(nullptr);
log(DEVLC, "removing device %p, conn %p\n", dev.get(), conn);
const zx::channel& rpc = *dev->rpc;
ZX_ASSERT(rpc.is_valid());
// TODO(teisenbe): Handle failures here...
log_rpc(dev, "remove-device");
zx_status_t call_status;
zx_status_t status = fuchsia_device_manager_CoordinatorRemoveDevice(rpc.get(), &call_status);
log_rpc_result("remove-device", status, call_status);
// Forget about our rpc channel since after the port_queue below it may be
// closed.
dev->rpc = zx::unowned_channel();
// queue an event to destroy the connection
ConnectionDestroyer::Get()->QueueDevcoordinatorConnection(conn);
// shut down our proxy rpc channel if it exists
proxy_ios_destroy(dev);
return ZX_OK;
}
zx_status_t devhost_get_topo_path(const fbl::RefPtr<zx_device_t>& dev, char* path, size_t max,
size_t* actual) {
fbl::RefPtr<zx_device_t> remote_dev = dev;
if (dev->flags & DEV_FLAG_INSTANCE) {
// Instances cannot be opened a second time. If dev represents an instance, return the path
// to its parent, prefixed with an '@'.
if (max < 1) {
return ZX_ERR_BUFFER_TOO_SMALL;
}
path[0] = '@';
path++;
max--;
remote_dev = dev->parent;
}
const zx::channel& rpc = *remote_dev->rpc;
if (!rpc.is_valid()) {
return ZX_ERR_IO_REFUSED;
}
log_rpc(remote_dev, "get-topo-path");
zx_status_t call_status;
zx_status_t status = fuchsia_device_manager_CoordinatorGetTopologicalPath(
rpc.get(), &call_status, path, max - 1, actual);
log_rpc_result("get-topo-path", status, call_status);
if (status != ZX_OK) {
return status;
}
if (call_status != ZX_OK) {
return status;
}
path[*actual] = 0;
*actual += 1;
// Account for the prefixed '@' we may have added above.
if (dev->flags & DEV_FLAG_INSTANCE) {
*actual += 1;
}
return ZX_OK;
}
zx_status_t devhost_device_bind(const fbl::RefPtr<zx_device_t>& dev, const char* drv_libname) {
const zx::channel& rpc = *dev->rpc;
if (!rpc.is_valid()) {
return ZX_ERR_IO_REFUSED;
}
log_rpc(dev, "bind-device");
zx_status_t call_status;
zx_status_t status = fuchsia_device_manager_CoordinatorBindDevice(
rpc.get(), drv_libname, strlen(drv_libname), &call_status);
log_rpc_result("bind-device", status, call_status);
if (status != ZX_OK) {
return status;
}
return call_status;
}
zx_status_t devhost_load_firmware(const fbl::RefPtr<zx_device_t>& dev, const char* path,
zx_handle_t* vmo, size_t* size) {
if ((vmo == nullptr) || (size == nullptr)) {
return ZX_ERR_INVALID_ARGS;
}
const zx::channel& rpc = *dev->rpc;
if (!rpc.is_valid()) {
return ZX_ERR_IO_REFUSED;
}
log_rpc(dev, "load-firmware");
zx_status_t call_status;
zx_status_t status = fuchsia_device_manager_CoordinatorLoadFirmware(
rpc.get(), path, strlen(path), &call_status, vmo, size);
log_rpc_result("load-firmware", status, call_status);
if (status != ZX_OK) {
return status;
}
if (call_status == ZX_OK && *vmo == ZX_HANDLE_INVALID) {
return ZX_ERR_INTERNAL;
}
return call_status;
}
zx_status_t devhost_get_metadata(const fbl::RefPtr<zx_device_t>& dev, uint32_t type, void* buf,
size_t buflen, size_t* actual) {
if (!buf) {
return ZX_ERR_INVALID_ARGS;
}
const zx::channel& rpc = *dev->rpc;
if (!rpc.is_valid()) {
return ZX_ERR_IO_REFUSED;
}
uint8_t data[fuchsia_device_manager_METADATA_MAX];
size_t length = 0;
log_rpc(dev, "get-metadata");
zx_status_t call_status;
zx_status_t status = fuchsia_device_manager_CoordinatorGetMetadata(
rpc.get(), type, &call_status, data, sizeof(data), &length);
if (status != ZX_OK) {
log(ERROR, "devhost: rpc:get-metadata sending failed: %d\n", status);
return status;
}
if (call_status != ZX_OK) {
if (call_status != ZX_ERR_NOT_FOUND) {
log(ERROR, "devhost: rpc:get-metadata failed: %d\n", call_status);
}
return call_status;
}
memcpy(buf, data, length);
if (actual != nullptr) {
*actual = length;
}
return ZX_OK;
}
zx_status_t devhost_add_metadata(const fbl::RefPtr<zx_device_t>& dev, uint32_t type,
const void* data, size_t length) {
if (!data && length) {
return ZX_ERR_INVALID_ARGS;
}
const zx::channel& rpc = *dev->rpc;
if (!rpc.is_valid()) {
return ZX_ERR_IO_REFUSED;
}
log_rpc(dev, "add-metadata");
zx_status_t call_status;
zx_status_t status = fuchsia_device_manager_CoordinatorAddMetadata(
rpc.get(), type, reinterpret_cast<const uint8_t*>(data), length, &call_status);
log_rpc_result("add-metadata", status, call_status);
if (status != ZX_OK) {
return status;
}
return call_status;
}
zx_status_t devhost_publish_metadata(const fbl::RefPtr<zx_device_t>& dev,
const char* path, uint32_t type,
const void* data, size_t length) {
if (!path || (!data && length)) {
return ZX_ERR_INVALID_ARGS;
}
const zx::channel& rpc = *dev->rpc;
if (!rpc.is_valid()) {
return ZX_ERR_IO_REFUSED;
}
log_rpc(dev, "publish-metadata");
zx_status_t call_status;
zx_status_t status = fuchsia_device_manager_CoordinatorPublishMetadata(
rpc.get(), path, strlen(path), type, reinterpret_cast<const uint8_t*>(data), length,
&call_status);
log_rpc_result("publish-metadata", status, call_status);
if (status != ZX_OK) {
return status;
}
return call_status;
}
zx_handle_t root_resource_handle;
zx_status_t devhost_start_connection(fbl::unique_ptr<DevfsConnection> conn, zx::channel h) {
conn->set_channel(std::move(h));
return DevfsConnection::BeginWait(std::move(conn), DevhostAsyncLoop()->dispatcher());
}
__EXPORT int device_host_main(int argc, char** argv) {
devhost_io_init();
log(TRACE, "devhost: main()\n");
zx::channel root_conn_channel(zx_take_startup_handle(PA_HND(PA_USER0, 0)));
if (!root_conn_channel.is_valid()) {
log(ERROR, "devhost: rpc handle invalid\n");
return -1;
}
root_resource_handle = zx_take_startup_handle(PA_HND(PA_RESOURCE, 0));
if (root_resource_handle == ZX_HANDLE_INVALID) {
log(ERROR, "devhost: no root resource handle!\n");
}
zx_status_t r;
#if ENABLE_DRIVER_TRACING
{
if (getenv_bool("driver.tracing.enable", true)) {
r = devhost_start_trace_provider();
if (r != ZX_OK) {
log(INFO, "devhost: error registering as trace provider: %d\n", r);
// This is not a fatal error.
}
}
}
#endif
if ((r = SetupRootDevcoordinatorConnection(std::move(root_conn_channel))) != ZX_OK) {
log(ERROR, "devhost: could not watch rpc channel: %d\n", r);
return -1;
}
r = DevhostAsyncLoop()->Run(zx::time::infinite(), false /* once */);
log(ERROR, "devhost: async loop finished: %d\n", r);
return 0;
}
} // namespace devmgr