| // 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 <assert.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <threads.h> |
| #include <unistd.h> |
| #include <zircon/assert.h> |
| #include <zircon/errors.h> |
| #include <zircon/listnode.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/types.h> |
| |
| #include <array> |
| #include <atomic> |
| #include <new> |
| #include <utility> |
| |
| #include <ddk/device.h> |
| #include <ddk/driver.h> |
| #include <fbl/auto_call.h> |
| #include <fbl/auto_lock.h> |
| |
| #include "composite-device.h" |
| #include "devhost.h" |
| #include "log.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 BindContext* g_bind_context; |
| static thread_local CreationContext* g_creation_context; |
| |
| // The bind and creation contexts 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_bind_context(BindContext* ctx) { g_bind_context = ctx; } |
| |
| void devhost_set_creation_context(CreationContext* ctx) { |
| ZX_DEBUG_ASSERT(!ctx || ctx->device_controller_rpc->is_valid() || |
| ctx->coordinator_rpc->is_valid()); |
| 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_close(void* ctx, uint32_t flags) { return ZX_OK; } |
| |
| static void default_unbind(void* ctx) {} |
| static void default_suspend(void* ctx, uint8_t requested_state, bool enable_wake, |
| uint8_t suspend_reason) {} |
| 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_resume(void* ctx, uint32_t target_system_state) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| static zx_status_t default_set_performance_state(void* ctx, uint32_t requested_state, |
| uint32_t* out_state) { |
| 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%lx\n", hdr->ordinal); |
| zx_handle_close_many(msg->handles, msg->num_handles); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| static void default_child_pre_release(void* ctx, void* child_ctx) {} |
| |
| zx_protocol_device_t device_default_ops = []() { |
| zx_protocol_device_t ops = {}; |
| ops.open = default_open; |
| 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.resume = default_resume; |
| ops.suspend_new = default_suspend; |
| ops.rxrpc = default_rxrpc; |
| ops.message = default_message; |
| ops.set_performance_state = default_set_performance_state; |
| ops.child_pre_release = default_child_pre_release; |
| 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.close = +[](void* ctx, uint32_t) -> zx_status_t { device_invalid_fatal(ctx); }; |
| ops.unbind = +[](void* ctx) { device_invalid_fatal(ctx); }; |
| ops.suspend_new = +[](void* ctx, uint8_t requested_state, bool enable_wake, |
| uint8_t suspend_reason) { 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.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); }; |
| ops.set_performance_state = |
| +[](void* ctx, uint32_t requested_state, uint32_t* out_state) -> 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 { |
| // Wrap the deferred-deletion list in a struct, so we can give it a proper |
| // dtor. Otherwise, this causes the binary to crash on exit due to an |
| // is_empty assert in fbl::DoublyLinkedList. This was particularly a |
| // problem for unit tests. |
| struct DeadList { |
| ~DeadList() { |
| while (!devices.is_empty()) { |
| delete devices.pop_front(); |
| } |
| } |
| fbl::DoublyLinkedList<zx_device*, zx_device::Node> devices; |
| }; |
| |
| static DeadList 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.devices.push_back(dev); |
| |
| if (dead_count == DEAD_DEVICE_MAX) { |
| zx_device_t* to_delete = dead_list.devices.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) { |
| if (dev->parent) { |
| ApiAutoRelock relock; |
| dev->parent->ChildPreReleaseOp(dev->ctx); |
| } |
| 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); |
| std::string drv = dev->parent->get_rebind_drv_name().value_or(""); |
| zx_status_t status = devhost_device_bind(dev->parent, drv.c_str()); |
| if (status != ZX_OK) { |
| if (auto rebind = dev->take_rebind_conn(); rebind) { |
| rebind(status); |
| } |
| } |
| } |
| |
| 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 char* name, void* ctx, |
| const 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; |
| } |
| |
| 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; |
| } |
| |
| 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 mark_dead = fbl::MakeAutoCall([&dev]() { |
| if (dev) { |
| dev->flags |= DEV_FLAG_DEAD; |
| } |
| }); |
| |
| zx_status_t status; |
| if ((status = device_validate(dev)) < 0) { |
| return status; |
| } |
| if (parent == nullptr) { |
| printf("device_add: cannot add %p(%s) to nullptr parent\n", dev.get(), dev->name); |
| return 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 ZX_ERR_BAD_STATE; |
| } |
| |
| BindContext* bind_ctx = nullptr; |
| CreationContext* creation_ctx = nullptr; |
| |
| // If the bind or creation ctx (thread locals) are 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_bind_context != nullptr) && (g_bind_context->parent == parent)) { |
| bind_ctx = g_bind_context; |
| } |
| if ((g_creation_context != nullptr) && (g_creation_context->parent == parent)) { |
| creation_ctx = g_creation_context; |
| // create() must create only one child |
| if (creation_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 status; |
| } |
| |
| dev->flags |= DEV_FLAG_BUSY; |
| |
| // proxy devices are created through this handshake process |
| if (creation_ctx) { |
| 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(creation_ctx->device_controller_rpc); |
| dev->coordinator_rpc = zx::unowned_channel(creation_ctx->coordinator_rpc); |
| creation_ctx->child = dev; |
| mark_dead.cancel(); |
| 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 bind context if there is one |
| if (bind_ctx && (bind_ctx->child == nullptr)) { |
| bind_ctx->child = dev; |
| } |
| mark_dead.cancel(); |
| return ZX_OK; |
| } |
| |
| zx_status_t devhost_device_init(const fbl::RefPtr<zx_device_t>& dev) REQ_DM_LOCK { |
| if (dev->flags & DEV_FLAG_INITIALIZING) { |
| return ZX_ERR_BAD_STATE; |
| } |
| // Call dev's init op. |
| if (dev->ops->init) { |
| dev->flags |= DEV_FLAG_INITIALIZING; |
| ApiAutoRelock relock; |
| dev->InitOp(); |
| } else { |
| dev->init_cb(ZX_OK); |
| } |
| return ZX_OK; |
| } |
| |
| void devhost_device_init_reply(const fbl::RefPtr<zx_device_t>& dev, zx_status_t status, |
| const device_init_reply_args_t* args) REQ_DM_LOCK { |
| if (!(dev->flags & DEV_FLAG_INITIALIZING)) { |
| ZX_PANIC("device: %p(%s): cannot reply to init, flags are: (%x)\n", dev.get(), dev->name, |
| dev->flags); |
| } |
| if (status == ZX_OK) { |
| 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); |
| } |
| } |
| if (dev->init_cb) { |
| dev->init_cb(status); |
| } else { |
| ZX_PANIC("device: %p(%s): cannot reply to init, no callback set, flags are 0x%x\n", dev.get(), |
| dev->name, dev->flags); |
| } |
| } |
| |
| #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 "?"; |
| } |
| |
| zx_status_t devhost_device_remove(const fbl::RefPtr<zx_device_t>& dev, |
| bool unbind_self) 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 scheduled for removal\n", dev.get(), dev->name); |
| #endif |
| // Ask the devcoordinator to schedule the removal of this device and its children. |
| devhost_schedule_remove(dev, unbind_self); |
| return ZX_OK; |
| } |
| |
| void devhost_device_suspend_reply(const fbl::RefPtr<zx_device_t>& dev, zx_status_t status, |
| uint8_t out_state) REQ_DM_LOCK { |
| // There are 3 references when this function gets called in repsonse to |
| // selective suspend on a device. 1. When we create a connection in ReadMessage |
| // 2. When we wrap the txn in devmgr::Transaction. |
| // 3. When we make the suspend txn asynchronous using ToAsync() |
| if (dev->outstanding_transactions > 3) { |
| ZX_PANIC("device: %p(%s): cannot reply to suspend, currently has %d outstanding transactions\n", |
| dev.get(), dev->name, dev->outstanding_transactions.load()); |
| } |
| |
| if (dev->suspend_cb) { |
| dev->suspend_cb(status, out_state); |
| } else { |
| ZX_PANIC("device: %p(%s): cannot reply to suspend, no callback set\n", dev.get(), dev->name); |
| } |
| } |
| |
| void devhost_device_unbind_reply(const fbl::RefPtr<zx_device_t>& dev) REQ_DM_LOCK { |
| if (dev->flags & REMOVAL_BAD_FLAGS) { |
| ZX_PANIC("device: %p(%s): cannot reply to unbind, bad flags: (%s)\n", dev.get(), dev->name, |
| removal_problem(dev->flags)); |
| } |
| if (!(dev->flags & DEV_FLAG_UNBOUND)) { |
| ZX_PANIC("device: %p(%s): cannot reply to unbind, not in unbinding state, flags are 0x%x\n", |
| dev.get(), dev->name, dev->flags); |
| } |
| if (dev->outstanding_transactions > 0) { |
| ZX_PANIC("device: %p(%s): cannot reply to unbind, currently has %d outstanding transactions\n", |
| dev.get(), dev->name, dev->outstanding_transactions.load()); |
| } |
| |
| #if TRACE_ADD_REMOVE |
| printf("device: %p(%s): sending unbind completed\n", dev.get(), dev->name); |
| #endif |
| if (dev->unbind_cb) { |
| dev->unbind_cb(ZX_OK); |
| } else { |
| ZX_PANIC("device: %p(%s): cannot reply to unbind, no callback set, flags are 0x%x\n", dev.get(), |
| dev->name, dev->flags); |
| } |
| } |
| |
| zx_status_t devhost_device_remove_deprecated(const fbl::RefPtr<zx_device_t>& dev) REQ_DM_LOCK { |
| // This removal is in response to the unbind hook. |
| if (dev->flags & DEV_FLAG_UNBOUND) { |
| devhost_device_unbind_reply(dev); |
| return ZX_OK; |
| } |
| return devhost_device_remove(dev, false /* unbind_self */); |
| } |
| |
| zx_status_t devhost_device_rebind(const fbl::RefPtr<zx_device_t>& dev) REQ_DM_LOCK { |
| if (!dev->children.is_empty() || dev->has_composite()) { |
| // 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_schedule_unbind_children(dev); |
| } else { |
| std::string drv = dev->get_rebind_drv_name().value_or(""); |
| return devhost_device_bind(dev, drv.c_str()); |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t devhost_device_unbind(const fbl::RefPtr<zx_device_t>& dev) REQ_DM_LOCK { |
| enum_lock_acquire(); |
| |
| 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(); |
| } else { |
| // We should reply to the unbind hook so we don't get stuck. |
| dev->unbind_cb(ZX_OK); |
| } |
| } |
| enum_lock_release(); |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t devhost_device_complete_removal(const fbl::RefPtr<zx_device_t>& dev) REQ_DM_LOCK { |
| #if TRACE_ADD_REMOVE |
| printf("device: %p(%s): is being removed (removal requested)\n", dev.get(), dev->name); |
| #endif |
| |
| // This recovers the leaked reference that happened in device_add_from_driver(). |
| auto dev_add_ref = fbl::ImportFromRawPtr(dev.get()); |
| devhost_remove(std::move(dev_add_ref)); |
| |
| dev->flags |= DEV_FLAG_DEAD; |
| return ZX_OK; |
| } |
| |
| zx_status_t devhost_device_open(const fbl::RefPtr<zx_device_t>& dev, fbl::RefPtr<zx_device_t>* out, |
| 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; |
| 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::ImportFromRawPtr(opened_dev); |
| |
| if (!(opened_dev->flags & DEV_FLAG_INSTANCE)) { |
| ZX_PANIC("device open: %p(%s) in bad state %x\n", opened_dev, opened_dev->name, flags); |
| } |
| } |
| *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); |
| } |
| |
| uint8_t devhost_device_get_suspend_reason(fuchsia_device_manager_SystemPowerState power_state) { |
| switch (power_state) { |
| case fuchsia_device_manager_SystemPowerState_SYSTEM_POWER_STATE_REBOOT: |
| return DEVICE_SUSPEND_REASON_REBOOT; |
| case fuchsia_device_manager_SystemPowerState_SYSTEM_POWER_STATE_REBOOT_RECOVERY: |
| return DEVICE_SUSPEND_REASON_REBOOT_RECOVERY; |
| case fuchsia_device_manager_SystemPowerState_SYSTEM_POWER_STATE_REBOOT_BOOTLOADER: |
| return DEVICE_SUSPEND_REASON_REBOOT_BOOTLOADER; |
| case fuchsia_device_manager_SystemPowerState_SYSTEM_POWER_STATE_MEXEC: |
| return DEVICE_SUSPEND_REASON_MEXEC; |
| case fuchsia_device_manager_SystemPowerState_SYSTEM_POWER_STATE_POWEROFF: |
| return DEVICE_SUSPEND_REASON_POWEROFF; |
| case fuchsia_device_manager_SystemPowerState_SYSTEM_POWER_STATE_SUSPEND_RAM: |
| return DEVICE_SUSPEND_REASON_SUSPEND_RAM; |
| default: |
| return DEVICE_SUSPEND_REASON_SELECTIVE_SUSPEND; |
| } |
| } |
| |
| zx_status_t devhost_device_get_dev_power_state_from_mapping( |
| const fbl::RefPtr<zx_device>& dev, uint32_t flags, |
| ::llcpp::fuchsia::device::SystemPowerStateInfo* info, uint8_t* suspend_reason) { |
| // TODO(ravoorir) : When the usage of suspend flags is replaced with |
| // system power states, this function will not need the switch case. |
| // Some suspend flags might be translated to system power states with |
| // additional hints (ex: REBOOT/REBOOT_BOOTLOADER/REBOOT_RECOVERY/MEXEC). |
| // For now, each of these flags are treated as an individual state. |
| fuchsia_device_manager_SystemPowerState sys_state; |
| switch (flags & DEVICE_SUSPEND_REASON_MASK) { |
| case DEVICE_SUSPEND_FLAG_REBOOT: |
| sys_state = fuchsia_device_manager_SystemPowerState_SYSTEM_POWER_STATE_REBOOT; |
| break; |
| case DEVICE_SUSPEND_FLAG_REBOOT_RECOVERY: |
| sys_state = fuchsia_device_manager_SystemPowerState_SYSTEM_POWER_STATE_REBOOT_RECOVERY; |
| break; |
| case DEVICE_SUSPEND_FLAG_REBOOT_BOOTLOADER: |
| sys_state = fuchsia_device_manager_SystemPowerState_SYSTEM_POWER_STATE_REBOOT_BOOTLOADER; |
| break; |
| case DEVICE_SUSPEND_FLAG_MEXEC: |
| sys_state = fuchsia_device_manager_SystemPowerState_SYSTEM_POWER_STATE_MEXEC; |
| break; |
| case DEVICE_SUSPEND_FLAG_POWEROFF: |
| sys_state = fuchsia_device_manager_SystemPowerState_SYSTEM_POWER_STATE_POWEROFF; |
| break; |
| default: |
| return ZX_ERR_INVALID_ARGS; |
| } |
| auto& sys_power_states = dev->GetSystemPowerStateMapping(); |
| *info = sys_power_states[sys_state]; |
| *suspend_reason = devhost_device_get_suspend_reason(sys_state); |
| return ZX_OK; |
| } |
| |
| void devhost_device_system_suspend(const fbl::RefPtr<zx_device>& dev, uint32_t flags) REQ_DM_LOCK { |
| if (dev->auto_suspend_configured()) { |
| dev->ops->configure_auto_suspend(dev->ctx, false, |
| fuchsia_device_DevicePowerState_DEVICE_POWER_STATE_D0); |
| log(INFO, "Devhost: system suspend overriding auto suspend for %s\n", dev->name); |
| } |
| zx_status_t status = ZX_ERR_NOT_SUPPORTED; |
| // If new suspend hook is implemented, prefer that. |
| if (dev->ops->suspend_new) { |
| ::llcpp::fuchsia::device::SystemPowerStateInfo new_state_info; |
| uint8_t suspend_reason = DEVICE_SUSPEND_REASON_SELECTIVE_SUSPEND; |
| |
| status = devhost_device_get_dev_power_state_from_mapping(dev, flags, &new_state_info, |
| &suspend_reason); |
| if (status == ZX_OK) { |
| enum_lock_acquire(); |
| { |
| ApiAutoRelock relock; |
| dev->ops->suspend_new(dev->ctx, static_cast<uint8_t>(new_state_info.dev_state), |
| new_state_info.wakeup_enable, suspend_reason); |
| } |
| enum_lock_release(); |
| return; |
| } |
| } |
| |
| // If suspend hook is not implemented, do not throw error during system suspend. |
| if (status == ZX_ERR_NOT_SUPPORTED) { |
| status = ZX_OK; |
| } |
| |
| dev->suspend_cb(status, 0); |
| } |
| |
| zx_status_t devhost_device_resume(const fbl::RefPtr<zx_device>& dev, |
| uint32_t target_system_state) REQ_DM_LOCK { |
| enum_lock_acquire(); |
| |
| zx_status_t status = ZX_ERR_NOT_SUPPORTED; |
| // If new suspend hook is implemented, prefer that. |
| if (dev->ops->resume_new) { |
| fuchsia_device_DevicePowerState out_state; |
| ApiAutoRelock relock; |
| auto& sys_power_states = dev->GetSystemPowerStateMapping(); |
| status = dev->ops->resume_new( |
| dev->ctx, static_cast<uint8_t>(sys_power_states[target_system_state].dev_state), |
| &out_state); |
| } else if (dev->ops->resume) { |
| // Invoke resume hook otherwise. |
| ApiAutoRelock relock; |
| status = dev->ops->resume(dev->ctx, target_system_state); |
| } |
| |
| enum_lock_release(); |
| |
| // default_resume() returns ZX_ERR_NOT_SUPPORTED |
| if (status != ZX_ERR_NOT_SUPPORTED) { |
| return status; |
| } |
| return ZX_OK; |
| } |
| |
| void devhost_device_suspend_new(const fbl::RefPtr<zx_device>& dev, |
| ::llcpp::fuchsia::device::DevicePowerState requested_state) { |
| if (dev->auto_suspend_configured()) { |
| log(INFO, "Devhost: Suspending %s failed: AutoSuspend is enabled\n", dev->name); |
| dev->suspend_cb( |
| ZX_ERR_NOT_SUPPORTED, |
| static_cast<uint8_t>(::llcpp::fuchsia::device::DevicePowerState::DEVICE_POWER_STATE_D0)); |
| return; |
| } |
| if (!(dev->IsPowerStateSupported(requested_state))) { |
| dev->suspend_cb( |
| ZX_ERR_INVALID_ARGS, |
| static_cast<uint8_t>(::llcpp::fuchsia::device::DevicePowerState::DEVICE_POWER_STATE_D0)); |
| return; |
| } |
| |
| if (dev->ops->suspend_new) { |
| dev->ops->suspend_new(dev->ctx, static_cast<uint8_t>(requested_state), |
| DEVICE_SUSPEND_REASON_SELECTIVE_SUSPEND, false /* wake_configured */); |
| return; |
| } |
| dev->suspend_cb( |
| ZX_ERR_NOT_SUPPORTED, |
| static_cast<uint8_t>(::llcpp::fuchsia::device::DevicePowerState::DEVICE_POWER_STATE_D0)); |
| } |
| |
| zx_status_t devhost_device_set_performance_state(const fbl::RefPtr<zx_device>& dev, |
| uint32_t requested_state, uint32_t* out_state) { |
| if (!(dev->IsPerformanceStateSupported(requested_state))) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| if (dev->ops->set_performance_state) { |
| return dev->ops->set_performance_state(dev->ctx, requested_state, out_state); |
| } |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t devhost_device_resume_new(const fbl::RefPtr<zx_device>& dev, |
| ::llcpp::fuchsia::device::DevicePowerState requested_state, |
| ::llcpp::fuchsia::device::DevicePowerState* out_state) { |
| zx_status_t status = ZX_OK; |
| if (dev->ops->resume_new) { |
| uint8_t raw_out; |
| status = dev->ops->resume_new(dev->ctx, static_cast<uint8_t>(requested_state), &raw_out); |
| *out_state = static_cast<::llcpp::fuchsia::device::DevicePowerState>(raw_out); |
| } |
| return status; |
| } |
| |
| zx_status_t devhost_device_configure_auto_suspend( |
| const fbl::RefPtr<zx_device>& dev, bool enable, |
| ::llcpp::fuchsia::device::DevicePowerState requested_state) { |
| if (enable && !(dev->IsPowerStateSupported(requested_state))) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| if (dev->ops->configure_auto_suspend) { |
| zx_status_t status = |
| dev->ops->configure_auto_suspend(dev->ctx, enable, static_cast<uint8_t>(requested_state)); |
| if (status != ZX_OK) { |
| return status; |
| } |
| dev->set_auto_suspend_configured(enable); |
| return ZX_OK; |
| } |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| } // namespace devmgr |