blob: cf5188baca97c9e172ad04cc6c5b790b2c581c7f [file] [log] [blame]
// Copyright 2016 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 <assert.h>
#include <atomic>
#include <errno.h>
#include <fcntl.h>
#include <new>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <threads.h>
#include <unistd.h>
#include <utility>
#include <ddk/device.h>
#include <ddk/driver.h>
#include <zircon/assert.h>
#include <zircon/listnode.h>
#include <zircon/syscalls.h>
#include <zircon/types.h>
#include <fbl/auto_lock.h>
namespace devmgr {
#define TRACE 0
#if TRACE
#define xprintf(fmt...) printf(fmt)
#else
#define xprintf(fmt...) \
do { \
} while (0)
#endif
#define TRACE_ADD_REMOVE 0
namespace internal {
__LOCAL mtx_t devhost_api_lock = MTX_INIT;
__LOCAL std::atomic<thrd_t> devhost_api_lock_owner(0);
} // namespace internal
static thread_local CreationContext* g_creation_context;
// The creation context is setup before the bind() or create() ops are
// invoked to provide the ability to sanity check the required device_add()
// operations these hooks should be making.
void devhost_set_creation_context(CreationContext* ctx) {
g_creation_context = ctx;
}
static zx_status_t default_open(void* ctx, zx_device_t** out, uint32_t flags) {
return ZX_OK;
}
static zx_status_t default_open_at(void* ctx, zx_device_t** out, const char* path, uint32_t flags) {
return ZX_ERR_NOT_SUPPORTED;
}
static zx_status_t default_close(void* ctx, uint32_t flags) {
return ZX_OK;
}
static void default_unbind(void* ctx) {
}
static void default_release(void* ctx) {
}
static zx_status_t default_read(void* ctx, void* buf, size_t count, zx_off_t off, size_t* actual) {
return ZX_ERR_NOT_SUPPORTED;
}
static zx_status_t default_write(void* ctx, const void* buf, size_t count, zx_off_t off, size_t* actual) {
return ZX_ERR_NOT_SUPPORTED;
}
static zx_off_t default_get_size(void* ctx) {
return 0;
}
static zx_status_t default_ioctl(void* ctx, uint32_t op,
const void* in_buf, size_t in_len,
void* out_buf, size_t out_len, size_t* out_actual) {
return ZX_ERR_NOT_SUPPORTED;
}
static zx_status_t default_suspend(void* ctx, uint32_t flags) {
return ZX_ERR_NOT_SUPPORTED;
}
static zx_status_t default_resume(void* ctx, uint32_t flags) {
return ZX_ERR_NOT_SUPPORTED;
}
static zx_status_t default_rxrpc(void* ctx, zx_handle_t channel) {
return ZX_ERR_NOT_SUPPORTED;
}
static zx_status_t default_message(void *ctx, fidl_msg_t* msg, fidl_txn_t* txn) {
fidl_message_header_t* hdr = (fidl_message_header_t*) msg->bytes;
printf("devhost: Unsupported FIDL operation: 0x%x\n", hdr->ordinal);
zx_handle_close_many(msg->handles, msg->num_handles);
return ZX_ERR_NOT_SUPPORTED;
}
zx_protocol_device_t device_default_ops = []() {
zx_protocol_device_t ops = {};
ops.open = default_open;
ops.open_at = default_open_at;
ops.close = default_close;
ops.unbind = default_unbind;
ops.release = default_release;
ops.read = default_read;
ops.write = default_write;
ops.get_size = default_get_size;
ops.ioctl = default_ioctl;
ops.suspend = default_suspend;
ops.resume = default_resume;
ops.rxrpc = default_rxrpc;
ops.message = default_message;
return ops;
}();
[[noreturn]]
static void device_invalid_fatal(void* ctx) {
printf("devhost: FATAL: zx_device_t used after destruction.\n");
__builtin_trap();
}
static zx_protocol_device_t device_invalid_ops = []() {
zx_protocol_device_t ops = {};
ops.open = +[](void* ctx, zx_device_t**, uint32_t) -> zx_status_t {
device_invalid_fatal(ctx);
};
ops.open_at = +[](void* ctx, zx_device_t**, const char*, uint32_t) -> zx_status_t {
device_invalid_fatal(ctx);
};
ops.close = +[](void* ctx, uint32_t) -> zx_status_t {
device_invalid_fatal(ctx);
};
ops.unbind = +[](void* ctx) {
device_invalid_fatal(ctx);
};
ops.release = +[](void* ctx) {
device_invalid_fatal(ctx);
};
ops.read = +[](void* ctx, void*, size_t, size_t, size_t*) -> zx_status_t {
device_invalid_fatal(ctx);
};
ops.write = +[](void* ctx, const void*, size_t, size_t, size_t*) -> zx_status_t {
device_invalid_fatal(ctx);
};
ops.get_size = +[](void* ctx) -> zx_off_t {
device_invalid_fatal(ctx);
};
ops.ioctl = +[](void* ctx, uint32_t, const void*, size_t, void*, size_t, size_t*)
-> zx_status_t {
device_invalid_fatal(ctx);
};
ops.suspend = +[](void* ctx, uint32_t) -> zx_status_t {
device_invalid_fatal(ctx);
};
ops.resume = +[](void* ctx, uint32_t) -> zx_status_t {
device_invalid_fatal(ctx);
};
ops.rxrpc = +[](void* ctx, zx_handle_t) -> zx_status_t {
device_invalid_fatal(ctx);
};
ops.message = +[](void* ctx, fidl_msg_t*, fidl_txn_t*) -> zx_status_t {
device_invalid_fatal(ctx);
};
return ops;
}();
// Maximum number of dead devices to hold on the dead device list
// before we start free'ing the oldest when adding a new one.
#define DEAD_DEVICE_MAX 7
void devhost_device_destroy(zx_device_t* dev) REQ_DM_LOCK {
static fbl::DoublyLinkedList<zx_device*, zx_device::Node> dead_list;
static unsigned dead_count = 0;
// ensure any ops will be fatal
dev->ops = &device_invalid_ops;
dev->magic = 0xdeaddeaddeaddead;
// ensure all owned handles are invalid
dev->event.reset();
dev->local_event.reset();
// ensure all pointers are invalid
dev->ctx = nullptr;
dev->driver = nullptr;
dev->parent.reset();
dev->conn.store(nullptr);
{
fbl::AutoLock guard(&dev->proxy_ios_lock);
dev->proxy_ios = nullptr;
}
// Defer destruction to help catch use-after-free and also
// so the compiler can't (easily) optimize away the poisoning
// we do above.
dead_list.push_back(dev);
if (dead_count == DEAD_DEVICE_MAX) {
zx_device_t* to_delete = dead_list.pop_front();
delete to_delete;
} else {
dead_count++;
}
}
// defered work list
fbl::DoublyLinkedList<zx_device*, zx_device::DeferNode> defer_device_list;
int devhost_enumerators = 0;
void devhost_finalize() {
// Early exit if there's no work
if (defer_device_list.is_empty()) {
return;
}
// Otherwise we snapshot the list
auto list = std::move(defer_device_list);
// We detach all the devices from their parents list-of-children
// while under the DM lock to avoid an enumerator starting to mutate
// things before we're done detaching them.
for (auto& dev : list) {
if (dev.parent) {
dev.parent->children.erase(dev);
}
}
// Then we can get to the actual final teardown where we have
// to drop the lock to call the callback
zx_device* dev;
while ((dev = list.pop_front()) != nullptr) {
// invoke release op
if (dev->flags & DEV_FLAG_ADDED) {
ApiAutoRelock relock;
dev->ReleaseOp();
}
if (dev->parent) {
// If the parent wants rebinding when its children are gone,
// And the parent is not dead, And this was the last child...
if ((dev->parent->flags & DEV_FLAG_WANTS_REBIND) &&
(!(dev->parent->flags & DEV_FLAG_DEAD)) &&
dev->parent->children.is_empty()) {
// Clear the wants rebind flag and request the rebind
dev->parent->flags &= (~DEV_FLAG_WANTS_REBIND);
devhost_device_bind(dev->parent, "");
}
dev->parent.reset();
}
// destroy/deallocate the device
devhost_device_destroy(dev);
}
}
// enum_lock_{acquire,release}() are used whenever we're iterating
// on the device tree. When "enum locked" it is legal to add a new
// child to the end of a device's list-of-children, but it is not
// legal to remove a child. This avoids badness when we have to
// drop the DM lock to call into device ops while enumerating.
static void enum_lock_acquire() REQ_DM_LOCK {
devhost_enumerators++;
}
static void enum_lock_release() REQ_DM_LOCK {
if (--devhost_enumerators == 0) {
devhost_finalize();
}
}
zx_status_t devhost_device_create(zx_driver_t* drv, const fbl::RefPtr<zx_device_t>& parent,
const char* name, void* ctx,
zx_protocol_device_t* ops, fbl::RefPtr<zx_device_t>* out)
REQ_DM_LOCK {
if (!drv) {
printf("devhost: device_add could not find driver!\n");
return ZX_ERR_INVALID_ARGS;
}
fbl::RefPtr<zx_device> dev;
zx_status_t status = zx_device::Create(&dev);
if (status != ZX_OK) {
return status;
}
dev->ops = ops;
dev->driver = drv;
if (name == nullptr) {
printf("devhost: dev=%p has null name.\n", dev.get());
name = "invalid";
dev->magic = 0;
}
size_t len = strlen(name);
// TODO(teisenbe): I think this is overly aggresive, and could be changed
// to |len > ZX_DEVICE_NAME_MAX| and |len = ZX_DEVICE_NAME_MAX|.
if (len >= ZX_DEVICE_NAME_MAX) {
printf("devhost: dev=%p name too large '%s'\n", dev.get(), name);
len = ZX_DEVICE_NAME_MAX - 1;
dev->magic = 0;
}
memcpy(dev->name, name, len);
dev->name[len] = 0;
// TODO(teisenbe): Why do we default to dev.get() here? Why not just
// nullptr
dev->ctx = ctx ? ctx : dev.get();
*out = std::move(dev);
return ZX_OK;
}
#define DEFAULT_IF_NULL(ops,method) \
if (ops->method == nullptr) { \
ops->method = default_##method; \
}
static zx_status_t device_validate(const fbl::RefPtr<zx_device_t>& dev) REQ_DM_LOCK {
if (dev == nullptr) {
printf("INVAL: nullptr!\n");
return ZX_ERR_INVALID_ARGS;
}
if (dev->flags & DEV_FLAG_ADDED) {
printf("device already added: %p(%s)\n", dev.get(), dev->name);
return ZX_ERR_BAD_STATE;
}
if (dev->magic != DEV_MAGIC) {
return ZX_ERR_BAD_STATE;
}
if (dev->ops == nullptr) {
printf("device add: %p(%s): nullptr ops\n", dev.get(), dev->name);
return ZX_ERR_INVALID_ARGS;
}
if ((dev->protocol_id == ZX_PROTOCOL_MISC_PARENT) ||
(dev->protocol_id == ZX_PROTOCOL_ROOT)) {
// These protocols is only allowed for the special
// singleton misc or root parent devices.
return ZX_ERR_INVALID_ARGS;
}
// devices which do not declare a primary protocol
// are implied to be misc devices
if (dev->protocol_id == 0) {
dev->protocol_id = ZX_PROTOCOL_MISC;
}
// install default methods if needed
zx_protocol_device_t* ops = dev->ops;
DEFAULT_IF_NULL(ops, open);
DEFAULT_IF_NULL(ops, open_at);
DEFAULT_IF_NULL(ops, close);
DEFAULT_IF_NULL(ops, unbind);
DEFAULT_IF_NULL(ops, release);
DEFAULT_IF_NULL(ops, read);
DEFAULT_IF_NULL(ops, write);
DEFAULT_IF_NULL(ops, get_size);
DEFAULT_IF_NULL(ops, ioctl);
DEFAULT_IF_NULL(ops, suspend);
DEFAULT_IF_NULL(ops, resume);
DEFAULT_IF_NULL(ops, rxrpc);
DEFAULT_IF_NULL(ops, message);
return ZX_OK;
}
zx_status_t devhost_device_add(const fbl::RefPtr<zx_device_t>& dev,
const fbl::RefPtr<zx_device_t>& parent,
const zx_device_prop_t* props, uint32_t prop_count,
const char* proxy_args, zx::channel client_remote)
REQ_DM_LOCK {
auto fail = [&dev](zx_status_t status) {
if (dev) {
dev->flags |= DEV_FLAG_DEAD | DEV_FLAG_VERY_DEAD;
}
return status;
};
zx_status_t status;
if ((status = device_validate(dev)) < 0) {
return fail(status);
}
if (parent == nullptr) {
printf("device_add: cannot add %p(%s) to nullptr parent\n", dev.get(), dev->name);
return fail(ZX_ERR_NOT_SUPPORTED);
}
if (parent->flags & DEV_FLAG_DEAD) {
printf("device add: %p: is dead, cannot add child %p\n", parent.get(), dev.get());
return fail(ZX_ERR_BAD_STATE);
}
CreationContext* ctx = nullptr;
// if creation ctx (thread local) is set, we are in a thread
// that is handling a bind() or create() callback and if that
// ctx's parent matches the one provided to add we need to do
// some additional checking...
if ((g_creation_context != nullptr) && (g_creation_context->parent == parent)) {
ctx = g_creation_context;
// If the RPC channel exists, this is for create rather than bind.
if (ctx->rpc->is_valid()) {
// create() must create only one child
if (ctx->child != nullptr) {
printf("devhost: driver attempted to create multiple proxy devices!\n");
return ZX_ERR_BAD_STATE;
}
}
}
#if TRACE_ADD_REMOVE
printf("devhost: device add: %p(%s) parent=%p(%s)\n",
dev.get(), dev->name, parent.get(), parent->name);
#endif
// Don't create an event handle if we alredy have one
if (!dev->event.is_valid() &&
((status = zx::eventpair::create(0, &dev->event, &dev->local_event)) < 0)) {
printf("device add: %p(%s): cannot create event: %d\n",
dev.get(), dev->name, status);
return fail(status);
}
dev->flags |= DEV_FLAG_BUSY;
// proxy devices are created through this handshake process
if (ctx && (ctx->rpc->is_valid())) {
if (dev->flags & DEV_FLAG_INVISIBLE) {
printf("devhost: driver attempted to create invisible device in create()\n");
return ZX_ERR_INVALID_ARGS;
}
dev->flags |= DEV_FLAG_ADDED;
dev->flags &= (~DEV_FLAG_BUSY);
dev->rpc = zx::unowned_channel(ctx->rpc);
ctx->child = dev;
return ZX_OK;
}
dev->parent = parent;
// attach to our parent
parent->children.push_back(dev.get());
if (!(dev->flags & DEV_FLAG_INSTANCE)) {
// devhost_add always consumes the handle
status = devhost_add(parent, dev, proxy_args, props, prop_count, std::move(client_remote));
if (status < 0) {
printf("devhost: %p(%s): remote add failed %d\n",
dev.get(), dev->name, status);
dev->parent->children.erase(*dev);
dev->parent.reset();
// since we are under the lock the whole time, we added the node
// to the tail and then we peeled it back off the tail when we
// failed, we don't need to interact with the enum lock mechanism
dev->flags &= (~DEV_FLAG_BUSY);
return status;
}
}
dev->flags |= DEV_FLAG_ADDED;
dev->flags &= (~DEV_FLAG_BUSY);
// record this device in the creation context if there is one
if (ctx && (ctx->child == nullptr)) {
ctx->child = dev;
}
return ZX_OK;
}
#define REMOVAL_BAD_FLAGS \
(DEV_FLAG_DEAD | DEV_FLAG_BUSY |\
DEV_FLAG_INSTANCE | DEV_FLAG_MULTI_BIND)
static const char* removal_problem(uint32_t flags) {
if (flags & DEV_FLAG_DEAD) {
return "already dead";
}
if (flags & DEV_FLAG_BUSY) {
return "being created";
}
if (flags & DEV_FLAG_INSTANCE) {
return "ephemeral device";
}
if (flags & DEV_FLAG_MULTI_BIND) {
return "multi-bind-able device";
}
return "?";
}
static void devhost_unbind_children(const fbl::RefPtr<zx_device_t>& dev) REQ_DM_LOCK {
#if TRACE_ADD_REMOVE
printf("devhost_unbind_children: %p(%s)\n", dev.get(), dev->name);
#endif
enum_lock_acquire();
for (auto& child : dev->children) {
if (!(child.flags & DEV_FLAG_DEAD)) {
// Try to get a reference to the child. This will fail if the last
// reference to it went away and fbl_recycle() is going to blocked
// waiting for the DM lock
auto child_ref = fbl::MakeRefPtrUpgradeFromRaw(&child,
&::devmgr::internal::devhost_api_lock);
if (child_ref) {
devhost_device_unbind(std::move(child_ref));
}
}
}
enum_lock_release();
}
zx_status_t devhost_device_remove(fbl::RefPtr<zx_device_t> dev) REQ_DM_LOCK {
if (dev->flags & REMOVAL_BAD_FLAGS) {
printf("device: %p(%s): cannot be removed (%s)\n",
dev.get(), dev->name, removal_problem(dev->flags));
return ZX_ERR_INVALID_ARGS;
}
#if TRACE_ADD_REMOVE
printf("device: %p(%s): is being removed\n", dev.get(), dev->name);
#endif
dev->flags |= DEV_FLAG_DEAD;
devhost_unbind_children(dev);
// cause the vfs entry to be unpublished to avoid further open() attempts
xprintf("device: %p: devhost->devmgr remove rpc\n", dev.get());
devhost_remove(dev);
dev->flags |= DEV_FLAG_VERY_DEAD;
return ZX_OK;
}
zx_status_t devhost_device_rebind(const fbl::RefPtr<zx_device_t>& dev) REQ_DM_LOCK {
// note that we want to be rebound when our children are all gone
dev->flags |= DEV_FLAG_WANTS_REBIND;
// request that any existing children go away
devhost_unbind_children(dev);
return ZX_OK;
}
zx_status_t devhost_device_unbind(const fbl::RefPtr<zx_device_t>& dev) REQ_DM_LOCK {
if (!(dev->flags & DEV_FLAG_UNBOUND)) {
dev->flags |= DEV_FLAG_UNBOUND;
// Call dev's unbind op.
if (dev->ops->unbind) {
#if TRACE_ADD_REMOVE
printf("call unbind dev: %p(%s)\n", dev.get(), dev->name);
#endif
ApiAutoRelock relock;
dev->UnbindOp();
}
}
return ZX_OK;
}
zx_status_t devhost_device_open_at(const fbl::RefPtr<zx_device_t>& dev,
fbl::RefPtr<zx_device_t>* out,
const char* path, uint32_t flags)
REQ_DM_LOCK {
if (dev->flags & DEV_FLAG_DEAD) {
printf("device open: %p(%s) is dead!\n", dev.get(), dev->name);
return ZX_ERR_BAD_STATE;
}
fbl::RefPtr<zx_device_t> new_ref(dev);
zx_status_t r;
zx_device_t* opened_dev = nullptr;
{
ApiAutoRelock relock;
if (path) {
r = dev->OpenAtOp(&opened_dev, path, flags);
} else {
r = dev->OpenOp(&opened_dev, flags);
}
}
if (r < 0) {
new_ref.reset();
} else if (opened_dev != nullptr) {
// open created a per-instance device for us
new_ref.reset();
// Claim the reference from open
new_ref = fbl::internal::MakeRefPtrNoAdopt(opened_dev);
if (!(opened_dev->flags & DEV_FLAG_INSTANCE)) {
printf("device open: %p(%s) in bad state %x\n", opened_dev, opened_dev->name, flags);
panic();
}
}
*out = std::move(new_ref);
return r;
}
zx_status_t devhost_device_close(fbl::RefPtr<zx_device_t> dev, uint32_t flags) REQ_DM_LOCK {
ApiAutoRelock relock;
return dev->CloseOp(flags);
}
static zx_status_t devhost_device_suspend_locked(const fbl::RefPtr<zx_device>& dev,
uint32_t flags) REQ_DM_LOCK {
// first suspend children (so we suspend from leaf up)
zx_status_t st;
for (auto& child : dev->children) {
if (!(child.flags & DEV_FLAG_DEAD)) {
// Try to get a reference to the child. This will fail if the last
// reference to it went away and fbl_recycle() is going to blocked
// waiting for the DM lock
auto child_ref = fbl::MakeRefPtrUpgradeFromRaw(&child,
&::devmgr::internal::devhost_api_lock);
if (child_ref) {
st = devhost_device_suspend(std::move(child_ref), flags);
if (st != ZX_OK) {
return st;
}
}
}
}
// then invoke our suspend hook
{
ApiAutoRelock relock;
st = dev->ops->suspend(dev->ctx, flags);
}
// default_suspend() returns ZX_ERR_NOT_SUPPORTED
if ((st != ZX_OK) && (st != ZX_ERR_NOT_SUPPORTED)) {
return st;
} else {
return ZX_OK;
}
}
zx_status_t devhost_device_suspend(const fbl::RefPtr<zx_device>& dev,
uint32_t flags) REQ_DM_LOCK {
//TODO this should eventually be two-pass using SUSPENDING/SUSPENDED flags
enum_lock_acquire();
zx_status_t r = devhost_device_suspend_locked(dev, flags);
enum_lock_release();
return r;
}
} // namespace devmgr