blob: d1466b53260233adc621920987a04889d8b423d2 [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 <fuchsia/device/manager/c/fidl.h>
#include <fuchsia/device/manager/llcpp/fidl.h>
#include <fuchsia/io/c/fidl.h>
#include <inttypes.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/async/cpp/receiver.h>
#include <lib/async/cpp/wait.h>
#include <lib/fdio/fdio.h>
#include <lib/fidl/coding.h>
#include <lib/zx/debuglog.h>
#include <lib/zx/resource.h>
#include <lib/zx/vmo.h>
#include <lib/zxio/inception.h>
#include <lib/zxio/null.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <zircon/dlfcn.h>
#include <zircon/process.h>
#include <zircon/processargs.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>
#include <zircon/syscalls/log.h>
#include <memory>
#include <new>
#include <utility>
#include <vector>
#include <ddk/binding.h>
#include <ddk/debug.h>
#include <ddk/device.h>
#include <ddk/driver.h>
#include <fbl/auto_call.h>
#include <fbl/auto_lock.h>
#include <fbl/function.h>
#include "async-loop-owned-rpc-handler.h"
#include "composite-device.h"
#include "connection-destroyer.h"
#include "device-controller-connection.h"
#include "env.h"
#include "fidl_txn.h"
#include "log.h"
#include "main.h"
#include "proxy-iostate.h"
#include "scheduler_profile.h"
#include "tracing.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;
static fbl::DoublyLinkedList<fbl::RefPtr<zx_driver>> dh_drivers;
DevhostContext& DevhostCtx() {
static DevhostContext ctx(&kAsyncLoopConfigAttachToCurrentThread);
return ctx;
}
// Access the devhost's async event loop
async::Loop* DevhostAsyncLoop() { return &DevhostCtx().loop(); }
static zx_status_t SetupRootDevcoordinatorConnection(zx::channel ch) {
auto conn = std::make_unique<DevhostControllerConnection>();
if (conn == nullptr) {
return ZX_ERR_NO_MEMORY;
}
conn->set_channel(std::move(ch));
return DevhostControllerConnection::BeginWait(std::move(conn), DevhostAsyncLoop()->dispatcher());
}
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);
}
}
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();
}
auto 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();
}
auto ops = static_cast<const zx_driver_ops_t**>(dlsym(dl, "__zircon_driver_ops__"));
auto 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();
}
// TODO(kulakowski) Eventually just check __zircon_driver_ops__,
// when bind programs are standalone.
if (ops == nullptr) {
ops = &dr->ops;
}
if (!(*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 ((*ops)->version != DRIVER_OPS_VERSION) {
log(ERROR,
"devhost: driver '%s' has bad driver ops version %" PRIx64 ", expecting %" PRIx64 "\n",
c_libname, (*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(*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();
}
void DevhostControllerConnection::CreateDevice(zx::channel coordinator_rpc,
zx::channel device_controller_rpc,
::fidl::StringView driver_path_view,
::zx::vmo driver_vmo, ::zx::handle parent_proxy,
::fidl::StringView proxy_args,
uint64_t local_device_id,
CreateDeviceCompleter::Sync completer) {
fbl::StringPiece driver_path(driver_path_view.data(), driver_path_view.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(ERROR, "devhost: create device drv='%.*s' args='%.*s'\n",
static_cast<int>(driver_path.size()), driver_path.data(), static_cast<int>(proxy_args.size()),
proxy_args.data());
// 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: driver load failed: %d\n", r);
return;
}
if (!drv->has_create_op()) {
log(ERROR, "devhost: driver create() not supported\n");
return;
}
// 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) {
log(ERROR, "devhost: device create() failed: %d\n", r);
return;
}
// 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,
.device_controller_rpc = zx::unowned_channel(device_controller_rpc),
.coordinator_rpc = zx::unowned_channel(coordinator_rpc),
};
r = drv->CreateOp(&creation_context, creation_context.parent, "proxy", proxy_args.data(),
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_DEAD;
if (r != ZX_OK) {
log(ERROR, "devhost: driver create() failed: %d\n", r);
return;
}
auto new_device = std::move(creation_context.child);
if (new_device == nullptr) {
log(ERROR, "devhost: driver create() failed to create a device!");
return;
}
new_device->set_local_id(local_device_id);
std::unique_ptr<DeviceControllerConnection> newconn;
r = DeviceControllerConnection::Create(std::move(new_device), std::move(device_controller_rpc),
std::move(coordinator_rpc), &newconn);
if (r != ZX_OK) {
return;
}
// TODO: inform devcoord
log(RPC_IN, "devhost: creating '%.*s' conn=%p\n", static_cast<int>(driver_path.size()),
driver_path.data(), newconn.get());
if ((r = DeviceControllerConnection::BeginWait(std::move(newconn),
DevhostAsyncLoop()->dispatcher())) != ZX_OK) {
return;
}
}
void DevhostControllerConnection::CreateCompositeDevice(
zx::channel coordinator_rpc, zx::channel device_controller_rpc,
::fidl::VectorView<uint64_t> components, ::fidl::StringView name, uint64_t local_device_id,
CreateCompositeDeviceCompleter::Sync completer) {
log(RPC_IN, "devhost: create composite device %.*s'\n", static_cast<int>(name.size()),
name.data());
// Convert the component IDs into zx_device references
CompositeComponents components_list(new fbl::RefPtr<zx_device>[components.count()],
components.count());
{
// Acquire the API lock so that we don't have to worry about concurrent
// device removes
ApiAutoLock lock;
for (size_t i = 0; i < components.count(); ++i) {
uint64_t local_id = components.data()[i];
fbl::RefPtr<zx_device_t> dev = zx_device::GetDeviceFromLocalId(local_id);
if (dev == nullptr || (dev->flags & DEV_FLAG_DEAD)) {
completer.Reply(ZX_ERR_NOT_FOUND);
return;
}
components_list[i] = std::move(dev);
}
}
fbl::RefPtr<zx_device_t> dev;
zx_status_t status = zx_device::Create(&dev);
if (status != ZX_OK) {
completer.Reply(status);
return;
}
static_assert(fuchsia_device_manager_DEVICE_NAME_MAX + 1 >= sizeof(dev->name));
memcpy(dev->name, name.data(), name.size());
dev->name[name.size()] = 0;
dev->set_local_id(local_device_id);
std::unique_ptr<DeviceControllerConnection> newconn;
status = DeviceControllerConnection::Create(dev, std::move(device_controller_rpc),
std::move(coordinator_rpc), &newconn);
if (status != ZX_OK) {
completer.Reply(status);
return;
}
status = InitializeCompositeDevice(dev, std::move(components_list));
if (status != ZX_OK) {
completer.Reply(status);
return;
}
log(RPC_IN, "devhost: creating new composite conn=%p\n", newconn.get());
if ((status = DeviceControllerConnection::BeginWait(std::move(newconn),
DevhostAsyncLoop()->dispatcher())) != ZX_OK) {
completer.Reply(status);
return;
}
completer.Reply(ZX_OK);
}
void DevhostControllerConnection::CreateDeviceStub(zx::channel coordinator_rpc,
zx::channel device_controller_rpc,
uint32_t protocol_id, uint64_t local_device_id,
CreateDeviceStubCompleter::Sync completer) {
log(RPC_IN, "devhost: create device stub\n");
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;
}
strcpy(dev->name, "proxy");
dev->protocol_id = protocol_id;
dev->ops = &device_default_ops;
dev->set_local_id(local_device_id);
std::unique_ptr<DeviceControllerConnection> newconn;
r = DeviceControllerConnection::Create(dev, std::move(device_controller_rpc),
std::move(coordinator_rpc), &newconn);
if (r != ZX_OK) {
return;
}
log(RPC_IN, "devhost: creating new stub conn=%p\n", newconn.get());
if ((r = DeviceControllerConnection::BeginWait(std::move(newconn),
DevhostAsyncLoop()->dispatcher())) != ZX_OK) {
return;
}
}
zx_status_t DevhostControllerConnection::HandleRead() {
zx::unowned_channel conn = channel();
uint8_t msg[ZX_CHANNEL_MAX_MSG_BYTES];
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;
}
auto hdr = static_cast<fidl_message_header_t*>(fidl_msg.bytes);
DevmgrFidlTxn txn(std::move(conn), hdr->txid);
fuchsia::device::manager::DevhostController::Dispatch(this, &fidl_msg, &txn);
return txn.Status();
}
// handles devcoordinator rpc
void DevhostControllerConnection::HandleRpc(std::unique_ptr<DevhostControllerConnection> 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) {
status = conn->HandleRead();
if (status != ZX_OK) {
log(ERROR, "devhost: devmgr rpc unhandleable ios=%p r=%s. fatal.\n", conn.get(),
zx_status_get_string(status));
abort();
}
BeginWait(std::move(conn), dispatcher);
return;
}
if (signal->observed & ZX_CHANNEL_PEER_CLOSED) {
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);
}
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->CancelLocked(DevhostAsyncLoop()->dispatcher());
}
}
static zxio_t* devhost_zxio_logger = nullptr;
} // namespace devmgr
__EXPORT void driver_printf(uint32_t flags, const char* fmt, ...) {
if (devmgr::devhost_zxio_logger == nullptr) {
return;
}
char buffer[512];
va_list ap;
va_start(ap, fmt);
size_t r = vsnprintf(buffer, sizeof(buffer), fmt, ap);
va_end(ap);
size_t actual;
zxio_write(devmgr::devhost_zxio_logger, buffer, std::min(r, sizeof(buffer)), 0, &actual);
}
namespace devmgr {
zx_handle_t root_resource_handle;
static void devhost_io_init() {
zx::debuglog handle;
if (zx::debuglog::create(*zx::unowned_resource(root_resource_handle), 0, &handle) != ZX_OK) {
return;
}
zxio_storage_t* storage;
fdio_t* io = fdio_zxio_create(&storage);
if (io == nullptr) {
return;
}
if (zxio_debuglog_init(storage, std::move(handle)) != ZX_OK) {
return;
}
devhost_zxio_logger = &storage->io;
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));
bool add_invisible = child->flags & DEV_FLAG_INVISIBLE;
fuchsia::device::manager::AddDeviceConfig add_device_config;
if (child->flags & DEV_FLAG_ALLOW_MULTI_COMPOSITE) {
add_device_config |= fuchsia::device::manager::AddDeviceConfig::ALLOW_MULTI_COMPOSITE;
}
zx_status_t status;
zx::channel coordinator, coordinator_remote;
if ((status = zx::channel::create(0, &coordinator, &coordinator_remote)) != ZX_OK) {
return status;
}
zx::channel device_controller, device_controller_remote;
if ((status = zx::channel::create(0, &device_controller, &device_controller_remote)) != ZX_OK) {
return status;
}
std::unique_ptr<DeviceControllerConnection> conn;
status = DeviceControllerConnection::Create(child, std::move(device_controller),
std::move(coordinator), &conn);
if (status != ZX_OK) {
return status;
}
const zx::channel& rpc = *parent->coordinator_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 = ZX_OK;
static_assert(sizeof(zx_device_prop_t) == sizeof(uint64_t));
uint64_t device_id = 0;
if (add_invisible) {
auto response = fuchsia::device::manager::Coordinator::Call::AddDeviceInvisible(
zx::unowned_channel(rpc.get()), std::move(coordinator_remote),
std::move(device_controller_remote),
::fidl::VectorView(reinterpret_cast<uint64_t*>(const_cast<zx_device_prop_t*>(props)),
prop_count),
::fidl::StringView(child->name, strlen(child->name)), child->protocol_id,
::fidl::StringView(child->driver->libname()),
::fidl::StringView(proxy_args, proxy_args_len), child->ops->init /* has_init */,
std::move(client_remote));
status = response.status();
if (status == ZX_OK) {
if (response.Unwrap()->result.is_response()) {
device_id = response.Unwrap()->result.response().local_device_id;
} else {
call_status = response.Unwrap()->result.err();
}
}
} else {
auto response = fuchsia::device::manager::Coordinator::Call::AddDevice(
zx::unowned_channel(rpc.get()), std::move(coordinator_remote),
std::move(device_controller_remote),
::fidl::VectorView(reinterpret_cast<uint64_t*>(const_cast<zx_device_prop_t*>(props)),
prop_count),
::fidl::StringView(child->name, strlen(child->name)), child->protocol_id,
::fidl::StringView(child->driver->libname()),
::fidl::StringView(proxy_args, proxy_args_len), add_device_config,
child->ops->init /* has_init */, std::move(client_remote));
status = response.status();
if (status == ZX_OK) {
if (response.Unwrap()->result.is_response()) {
device_id = response.Unwrap()->result.response().local_device_id;
} else {
call_status = response.Unwrap()->result.err();
}
}
}
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->set_local_id(device_id);
status = DeviceControllerConnection::BeginWait(std::move(conn), DevhostAsyncLoop()->dispatcher());
if (status != ZX_OK) {
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 = ZX_OK) {
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 device_make_visible_args_t* args) {
ZX_ASSERT_MSG(!dev->ops->init,
"Cannot call device_make_visible if init hook is implemented."
"The device will automatically be made visible once the init hook is replied to.");
const zx::channel& rpc = *dev->coordinator_rpc;
if (!rpc.is_valid()) {
return;
}
if (args && args->power_states && args->power_state_count != 0) {
dev->SetPowerStates(args->power_states, args->power_state_count);
}
if (args && args->performance_states && (args->performance_state_count != 0)) {
dev->SetPerformanceStates(args->performance_states, args->performance_state_count);
}
// TODO(teisenbe): Handle failures here...
log_rpc(dev, "make-visible");
auto response =
fuchsia::device::manager::Coordinator::Call::MakeVisible(zx::unowned_channel(rpc.get()));
zx_status_t status = response.status();
zx_status_t call_status = ZX_OK;
if (status == ZX_OK) {
if (response.Unwrap()->result.is_err()) {
call_status = response.Unwrap()->result.err();
}
}
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(fbl::RefPtr<zx_device_t> dev) {
DeviceControllerConnection* 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);
// respond to the remove fidl call
dev->removal_cb(ZX_OK);
// Forget our local ID, to release the reference stored by the local ID map
dev->set_local_id(0);
// Forget about our rpc channel since after the port_queue below it may be
// closed.
dev->rpc = zx::unowned_channel();
dev->coordinator_rpc = zx::unowned_channel();
// queue an event to destroy the connection
ConnectionDestroyer::Get()->QueueDeviceControllerConnection(DevhostAsyncLoop()->dispatcher(),
conn);
// shut down our proxy rpc channel if it exists
proxy_ios_destroy(dev);
return ZX_OK;
}
zx_status_t devhost_schedule_remove(const fbl::RefPtr<zx_device_t>& dev, bool unbind_self) {
const zx::channel& rpc = *dev->coordinator_rpc;
ZX_ASSERT(rpc.is_valid());
log_rpc(dev, "schedule-remove");
auto resp = fuchsia::device::manager::Coordinator::Call::ScheduleRemove(
zx::unowned_channel(rpc.get()), unbind_self);
log_rpc_result("schedule-remove called", resp.status());
return resp.status();
}
zx_status_t devhost_schedule_unbind_children(const fbl::RefPtr<zx_device_t>& dev) {
const zx::channel& rpc = *dev->coordinator_rpc;
ZX_ASSERT(rpc.is_valid());
log_rpc(dev, "schedule-unbind-children");
auto resp = fuchsia::device::manager::Coordinator::Call::ScheduleUnbindChildren(
zx::unowned_channel(rpc.get()));
log_rpc_result("schedule-unbind-children", resp.status());
return resp.status();
}
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->coordinator_rpc;
if (!rpc.is_valid()) {
return ZX_ERR_IO_REFUSED;
}
log_rpc(remote_dev, "get-topo-path");
auto response = fuchsia::device::manager::Coordinator::Call::GetTopologicalPath(
zx::unowned_channel(rpc.get()));
zx_status_t status = response.status();
zx_status_t call_status = ZX_OK;
if (status == ZX_OK) {
if (response.Unwrap()->result.is_err()) {
call_status = response.Unwrap()->result.err();
} else {
auto r = response.Unwrap()->result.response();
memcpy(path, r.path.data(), r.path.size());
*actual = r.path.size();
}
}
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->coordinator_rpc;
if (!rpc.is_valid()) {
log_rpc(dev, "bind failed");
return ZX_ERR_IO_REFUSED;
}
log_rpc(dev, "bind-device");
auto driver_path = ::fidl::StringView(drv_libname, strlen(drv_libname));
auto response = fuchsia::device::manager::Coordinator::Call::BindDevice(
zx::unowned_channel(rpc.get()), driver_path);
zx_status_t status = response.status();
zx_status_t call_status = ZX_OK;
if (status == ZX_OK && response.Unwrap()->result.is_err()) {
call_status = response.Unwrap()->result.err();
}
log_rpc_result("bind-device finished!", status, call_status);
if (status != ZX_OK) {
return status;
}
return call_status;
}
zx_status_t devhost_device_run_compatibility_tests(const fbl::RefPtr<zx_device_t>& dev,
int64_t hook_wait_time) {
const zx::channel& rpc = *dev->coordinator_rpc;
if (!rpc.is_valid()) {
return ZX_ERR_IO_REFUSED;
}
log_rpc(dev, "run-compatibility-test");
auto response = fuchsia::device::manager::Coordinator::Call::RunCompatibilityTests(
zx::unowned_channel(rpc.get()), hook_wait_time);
zx_status_t status = response.status();
zx_status_t call_status = ZX_OK;
if (status == ZX_OK && response.Unwrap()->result.is_err()) {
call_status = response.Unwrap()->result.err();
}
log_rpc_result("run-compatibility-test", 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_handle, size_t* size) {
if ((vmo_handle == nullptr) || (size == nullptr)) {
return ZX_ERR_INVALID_ARGS;
}
zx::vmo vmo;
const zx::channel& rpc = *dev->coordinator_rpc;
if (!rpc.is_valid()) {
return ZX_ERR_IO_REFUSED;
}
log_rpc(dev, "load-firmware");
auto str_path = ::fidl::StringView(path, strlen(path));
auto response = fuchsia::device::manager::Coordinator::Call::LoadFirmware(
zx::unowned_channel(rpc.get()), str_path);
zx_status_t status = response.status();
zx_status_t call_status = ZX_OK;
auto result = std::move(response.Unwrap()->result);
if (result.is_err()) {
call_status = result.err();
} else {
auto resp = std::move(result.mutable_response());
*size = resp.size;
vmo = std::move(resp.vmo);
}
log_rpc_result("load-firmware", status, call_status);
if (status != ZX_OK) {
return status;
}
*vmo_handle = vmo.release();
if (call_status == ZX_OK && *vmo_handle == 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->coordinator_rpc;
if (!rpc.is_valid()) {
return ZX_ERR_IO_REFUSED;
}
log_rpc(dev, "get-metadata");
auto response = fuchsia::device::manager::Coordinator::Call::GetMetadata(
zx::unowned_channel(rpc.get()), type);
zx_status_t status = response.status();
zx_status_t call_status = ZX_OK;
if (status == ZX_OK && response.Unwrap()->result.is_err()) {
call_status = response.Unwrap()->result.err();
}
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;
}
auto r = response.Unwrap()->result.response();
if (r.data.count() > buflen) {
return ZX_ERR_BUFFER_TOO_SMALL;
}
memcpy(buf, r.data.data(), r.data.count());
if (actual != nullptr) {
*actual = r.data.count();
}
return ZX_OK;
}
zx_status_t devhost_get_metadata_size(const fbl::RefPtr<zx_device_t>& dev, uint32_t type,
size_t* out_length) {
const zx::channel& rpc = *dev->coordinator_rpc;
if (!rpc.is_valid()) {
return ZX_ERR_IO_REFUSED;
}
log_rpc(dev, "get-metadata");
auto response = fuchsia::device::manager::Coordinator::Call::GetMetadataSize(
zx::unowned_channel(rpc.get()), type);
zx_status_t status = response.status();
zx_status_t call_status = ZX_OK;
if (status == ZX_OK && response.Unwrap()->result.is_err()) {
call_status = response.Unwrap()->result.err();
}
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;
}
*out_length = response.Unwrap()->result.response().size;
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->coordinator_rpc;
if (!rpc.is_valid()) {
return ZX_ERR_IO_REFUSED;
}
log_rpc(dev, "add-metadata");
auto response = fuchsia::device::manager::Coordinator::Call::AddMetadata(
zx::unowned_channel(rpc.get()), type,
::fidl::VectorView(reinterpret_cast<uint8_t*>(const_cast<void*>(data)), length));
zx_status_t status = response.status();
zx_status_t call_status = ZX_OK;
if (status == ZX_OK && response.Unwrap()->result.is_err()) {
call_status = response.Unwrap()->result.err();
}
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->coordinator_rpc;
if (!rpc.is_valid()) {
return ZX_ERR_IO_REFUSED;
}
log_rpc(dev, "publish-metadata");
auto response = fuchsia::device::manager::Coordinator::Call::PublishMetadata(
zx::unowned_channel(rpc.get()), ::fidl::StringView(path, strlen(path)), type,
::fidl::VectorView(reinterpret_cast<uint8_t*>(const_cast<void*>(data)), length));
zx_status_t status = response.status();
zx_status_t call_status = ZX_OK;
if (status == ZX_OK && response.Unwrap()->result.is_err()) {
call_status = response.Unwrap()->result.err();
}
log_rpc_result("publish-metadata", status, call_status);
if (status != ZX_OK) {
return status;
}
return call_status;
}
zx_status_t devhost_device_add_composite(const fbl::RefPtr<zx_device_t>& dev, const char* name,
const composite_device_desc_t* comp_desc) {
if (comp_desc == nullptr || (comp_desc->props == nullptr && comp_desc->props_count > 0) ||
comp_desc->components == nullptr || name == nullptr) {
return ZX_ERR_INVALID_ARGS;
}
const zx::channel& rpc = *dev->coordinator_rpc;
if (!rpc.is_valid()) {
return ZX_ERR_IO_REFUSED;
}
std::vector<fuchsia::device::manager::DeviceComponent> compvec = {};
for (size_t i = 0; i < comp_desc->components_count; i++) {
::fidl::Array<fuchsia::device::manager::DeviceComponentPart, 16> parts{};
for (uint32_t j = 0; j < comp_desc->components[i].parts_count; j++) {
::fidl::Array<fuchsia::device::manager::BindInstruction, 32> bind_instructions{};
for (uint32_t k = 0; k < comp_desc->components[i].parts[j].instruction_count; k++) {
bind_instructions[k] = fuchsia::device::manager::BindInstruction{
.op = comp_desc->components[i].parts[j].match_program[k].op,
.arg = comp_desc->components[i].parts[j].match_program[k].arg,
};
}
auto part = fuchsia::device::manager::DeviceComponentPart{
.match_program_count = comp_desc->components[i].parts[j].instruction_count,
.match_program = bind_instructions,
};
parts[j] = part;
}
auto dc = fuchsia::device::manager::DeviceComponent{
.parts_count = comp_desc->components[i].parts_count,
.parts = parts,
};
compvec.push_back(dc);
}
std::vector<fuchsia::device::manager::DeviceMetadata> metadata = {};
for (size_t i = 0; i < comp_desc->metadata_count; i++) {
auto meta = fuchsia::device::manager::DeviceMetadata{
.key = comp_desc->metadata_list[i].type,
.data = ::fidl::VectorView(
reinterpret_cast<uint8_t*>(const_cast<void*>(comp_desc->metadata_list[i].data)),
comp_desc->metadata_list[i].length)};
metadata.push_back(meta);
}
fuchsia::device::manager::CompositeDeviceDescriptor comp_dev = {
.props = ::fidl::VectorView(
reinterpret_cast<uint64_t*>(const_cast<zx_device_prop*>(comp_desc->props)),
comp_desc->props_count),
.components = ::fidl::VectorView(compvec),
.coresident_device_index = comp_desc->coresident_device_index,
.metadata = ::fidl::VectorView(metadata)};
log_rpc(dev, "create-composite");
static_assert(sizeof(comp_desc->props[0]) == sizeof(uint64_t));
auto response = fuchsia::device::manager::Coordinator::Call::AddCompositeDevice(
zx::unowned_channel(rpc.get()), ::fidl::StringView(name, strlen(name)), comp_dev);
zx_status_t status = response.status();
zx_status_t call_status = ZX_OK;
if (status == ZX_OK && response.Unwrap()->result.is_err()) {
call_status = response.Unwrap()->result.err();
}
if (status != ZX_OK) {
return status;
}
log_rpc_result("create-composite", status, call_status);
return call_status;
}
zx_status_t devhost_schedule_work(const fbl::RefPtr<zx_device_t>& dev, void (*callback)(void*),
void* cookie) {
if (!callback) {
return ZX_ERR_INVALID_ARGS;
}
DevhostCtx().PushWorkItem(dev, [callback, cookie]() { callback(cookie); });
return ZX_OK;
}
zx_status_t devhost_start_connection(fbl::RefPtr<DevfsConnection> conn, zx::channel h) {
conn->set_channel(std::move(h));
return DevfsConnection::BeginWait(std::move(conn), DevhostAsyncLoop()->dispatcher());
}
int device_host_main(int argc, char** argv) {
root_resource_handle = zx_take_startup_handle(PA_HND(PA_RESOURCE, 0));
if (root_resource_handle == ZX_HANDLE_INVALID) {
log(TRACE, "devhost: no root resource handle!\n");
}
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;
}
zx_status_t r;
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.
}
}
r = devhost_connect_scheduler_profile_provider();
if (r != ZX_OK) {
log(INFO, "devhost: error connecting to profile provider: %d\n", r);
return -1;
}
if ((r = SetupRootDevcoordinatorConnection(std::move(root_conn_channel))) != ZX_OK) {
log(ERROR, "devhost: could not watch rpc channel: %d\n", r);
return -1;
}
if (r = DevhostCtx().SetupEventWaiter(); r != ZX_OK) {
log(ERROR, "devhost: could not setup event watcher: %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
__EXPORT int devmgr_device_host_main(int argc, char** argv) {
return devmgr::device_host_main(argc, argv);
}