blob: ca9ad1071d7b19a333400d66bd19ce46d2e1aa32 [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 <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_OK) && (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