| // 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/errors.h> |
| #include <zircon/listnode.h> |
| #include <zircon/status.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 <ddktl/resume-txn.h> |
| #include <fbl/auto_call.h> |
| #include <fbl/auto_lock.h> |
| |
| #include "composite_device.h" |
| #include "driver_host.h" |
| #include "log.h" |
| |
| using llcpp::fuchsia::device::DevicePowerState; |
| using llcpp::fuchsia::hardware::power::statecontrol::SystemPowerState; |
| |
| namespace internal { |
| |
| static thread_local internal::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 DeviceAdd() operations these hooks should be making. |
| void set_bind_context(internal::BindContext* ctx) { g_bind_context = ctx; } |
| |
| void set_creation_context(CreationContext* ctx) { |
| ZX_DEBUG_ASSERT(!ctx || ctx->device_controller_rpc->is_valid() || ctx->coordinator_client); |
| g_creation_context = ctx; |
| } |
| |
| } // namespace internal |
| |
| 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_resume(void* ctx, uint32_t requested_state) {} |
| 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_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_incoming_msg_t* msg, fidl_txn_t* txn) { |
| fidl_message_header_t* hdr = (fidl_message_header_t*)msg->bytes; |
| LOGF(WARNING, "Unsupported FIDL protocol (ordinal %#16lx)", hdr->ordinal); |
| FidlHandleInfoCloseMany(msg->handles, msg->num_handles); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| static void default_child_pre_release(void* ctx, void* child_ctx) {} |
| |
| const zx_protocol_device_t internal::kDeviceDefaultOps = []() { |
| 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.suspend = default_suspend; |
| ops.resume = default_resume; |
| 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) { |
| LOGF(FATAL, "Device used after destruction"); |
| __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 = +[](void* ctx, uint8_t requested_state, bool enable_wake, uint8_t suspend_reason) { |
| device_invalid_fatal(ctx); |
| }; |
| ops.resume = +[](void* ctx, uint32_t) { 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.rxrpc = +[](void* ctx, zx_handle_t) -> zx_status_t { device_invalid_fatal(ctx); }; |
| ops.message = +[](void* ctx, fidl_incoming_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; |
| }(); |
| |
| void DriverHostContext::DeviceDestroy(zx_device_t* dev) { |
| inspect_.DeviceDestroyStats().Update(); |
| |
| // ensure any ops will be fatal |
| dev->set_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->set_parent(nullptr); |
| dev->conn.store(nullptr); |
| dev->FreeInspect(); |
| dev->driver = 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. |
| ZX_DEBUG_ASSERT(!fbl::InContainer<zx_device::ChildrenListTag>(*dev)); |
| dead_devices_.push_back(dev); |
| |
| if (dead_devices_count_ == DEAD_DEVICE_MAX) { |
| zx_device_t* to_delete = dead_devices_.pop_front(); |
| delete to_delete; |
| } else { |
| dead_devices_count_++; |
| } |
| } |
| |
| void DriverHostContext::FinalizeDyingDevices() { |
| // 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()->remove_child(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()) { |
| api_lock_.Release(); |
| dev->parent()->ChildPreReleaseOp(dev->ctx); |
| api_lock_.Acquire(); |
| } |
| api_lock_.Release(); |
| dev->ReleaseOp(); |
| api_lock_.Acquire(); |
| } |
| |
| if (dev->parent()) { |
| // When all the children are gone, complete the pending unbind request. |
| if ((!(dev->parent()->flags() & DEV_FLAG_DEAD)) && dev->parent()->children().is_empty()) { |
| if (auto unbind_children = dev->parent()->take_unbind_children_conn(); unbind_children) { |
| unbind_children(ZX_OK); |
| } |
| } |
| // 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()->unset_flag(DEV_FLAG_WANTS_REBIND); |
| std::string drv = dev->parent()->get_rebind_drv_name().value_or(""); |
| zx_status_t status = DeviceBind(dev->parent(), drv.c_str()); |
| if (status != ZX_OK) { |
| if (auto rebind = dev->parent()->take_rebind_conn(); rebind) { |
| rebind(status); |
| } |
| } |
| } |
| |
| dev->set_parent(nullptr); |
| } |
| |
| // destroy/deallocate the device |
| DeviceDestroy(dev); |
| } |
| } |
| |
| zx_status_t DriverHostContext::DeviceValidate(const fbl::RefPtr<zx_device_t>& dev) { |
| if (dev == nullptr) { |
| LOGF(ERROR, "Invalid device"); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| if (dev->flags() & DEV_FLAG_ADDED) { |
| LOGD(ERROR, *dev, "Already added device %p", dev.get()); |
| return ZX_ERR_BAD_STATE; |
| } |
| if (dev->magic != DEV_MAGIC) { |
| LOGD(ERROR, *dev, "Invalid signature for device %p: %#lx", dev.get(), dev->magic); |
| return ZX_ERR_BAD_STATE; |
| } |
| if (dev->ops() == nullptr) { |
| LOGD(ERROR, *dev, "Invalid ops for device %p", dev.get()); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| if ((dev->protocol_id() == ZX_PROTOCOL_MISC_PARENT) || (dev->protocol_id() == ZX_PROTOCOL_ROOT)) { |
| LOGD(ERROR, *dev, "Invalid protocol for device %p: %#x", dev.get(), dev->protocol_id()); |
| // 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->set_protocol_id(ZX_PROTOCOL_MISC); |
| } |
| |
| return ZX_OK; |
| } |
| |
| namespace internal { |
| |
| namespace { |
| |
| #define REMOVAL_BAD_FLAGS (DEV_FLAG_DEAD | DEV_FLAG_BUSY | DEV_FLAG_INSTANCE | DEV_FLAG_MULTI_BIND) |
| |
| 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 "?"; |
| } |
| |
| uint8_t device_get_suspend_reason(SystemPowerState power_state) { |
| switch (power_state) { |
| case SystemPowerState::REBOOT: |
| return DEVICE_SUSPEND_REASON_REBOOT; |
| case SystemPowerState::REBOOT_RECOVERY: |
| return DEVICE_SUSPEND_REASON_REBOOT_RECOVERY; |
| case SystemPowerState::REBOOT_BOOTLOADER: |
| return DEVICE_SUSPEND_REASON_REBOOT_BOOTLOADER; |
| case SystemPowerState::MEXEC: |
| return DEVICE_SUSPEND_REASON_MEXEC; |
| case SystemPowerState::POWEROFF: |
| return DEVICE_SUSPEND_REASON_POWEROFF; |
| case SystemPowerState::SUSPEND_RAM: |
| return DEVICE_SUSPEND_REASON_SUSPEND_RAM; |
| default: |
| return DEVICE_SUSPEND_REASON_SELECTIVE_SUSPEND; |
| } |
| } |
| |
| zx_status_t 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. |
| SystemPowerState sys_state; |
| switch (flags) { |
| case DEVICE_SUSPEND_FLAG_REBOOT: |
| sys_state = SystemPowerState::REBOOT; |
| break; |
| case DEVICE_SUSPEND_FLAG_REBOOT_RECOVERY: |
| sys_state = SystemPowerState::REBOOT_RECOVERY; |
| break; |
| case DEVICE_SUSPEND_FLAG_REBOOT_BOOTLOADER: |
| sys_state = SystemPowerState::REBOOT_BOOTLOADER; |
| break; |
| case DEVICE_SUSPEND_FLAG_MEXEC: |
| sys_state = SystemPowerState::MEXEC; |
| break; |
| case DEVICE_SUSPEND_FLAG_POWEROFF: |
| sys_state = SystemPowerState::POWEROFF; |
| break; |
| default: |
| return ZX_ERR_INVALID_ARGS; |
| } |
| auto& sys_power_states = dev->GetSystemPowerStateMapping(); |
| *info = sys_power_states[static_cast<unsigned long>(sys_state)]; |
| *suspend_reason = internal::device_get_suspend_reason(sys_state); |
| return ZX_OK; |
| } |
| |
| } // namespace |
| |
| uint32_t get_perf_state(const fbl::RefPtr<zx_device>& dev, uint32_t requested_perf_state) { |
| // Give preference to the performance state that is explicitly for this device. |
| if (dev->current_performance_state() != ::llcpp::fuchsia::device::DEVICE_PERFORMANCE_STATE_P0) { |
| return dev->current_performance_state(); |
| } |
| return requested_perf_state; |
| } |
| |
| } // namespace internal |
| |
| zx_status_t DriverHostContext::DeviceCreate(zx_driver_t* drv, const char* name, void* ctx, |
| const zx_protocol_device_t* ops, |
| fbl::RefPtr<zx_device_t>* out) { |
| inspect_.DeviceCreateStats().Update(); |
| if (!drv) { |
| LOGF(ERROR, "Cannot find driver"); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| std::string device_name; |
| if (name == nullptr) { |
| LOGF(WARNING, "Invalid name for device"); |
| device_name = "invalid"; |
| } else { |
| device_name = std::string(name); |
| } |
| |
| fbl::RefPtr<zx_device> dev; |
| zx_status_t status = zx_device::Create(this, device_name, drv, &dev); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| if (name == nullptr) { |
| dev->magic = 0; |
| } |
| |
| dev->set_ops(ops); |
| |
| // 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; |
| } |
| |
| zx_status_t DriverHostContext::DeviceAdd(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::vmo inspect, |
| zx::channel client_remote) { |
| inspect_.DeviceAddStats().Update(); |
| auto mark_dead = fbl::MakeAutoCall([&dev]() { |
| if (dev) { |
| dev->set_flag(DEV_FLAG_DEAD); |
| } |
| }); |
| |
| zx_status_t status; |
| if ((status = DeviceValidate(dev)) < 0) { |
| return status; |
| } |
| if (parent == nullptr) { |
| LOGD(ERROR, *dev, "Cannot add device %p to invalid parent", dev.get()); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| if (parent->flags() & DEV_FLAG_DEAD) { |
| LOGD(ERROR, *dev, "Cannot add device %p to dead parent %p", dev.get(), parent.get()); |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| internal::BindContext* bind_ctx = nullptr; |
| internal::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 ((internal::g_bind_context != nullptr) && (internal::g_bind_context->parent == parent)) { |
| bind_ctx = internal::g_bind_context; |
| } |
| if ((internal::g_creation_context != nullptr) && |
| (internal::g_creation_context->parent == parent)) { |
| creation_ctx = internal::g_creation_context; |
| // create() must create only one child |
| if (creation_ctx->child != nullptr) { |
| LOGD(ERROR, *dev, "Driver attempted to create multiple proxy devices"); |
| return ZX_ERR_BAD_STATE; |
| } |
| } |
| VLOGD(1, *dev, "Adding device %p (parent %p)", dev.get(), parent.get()); |
| |
| // 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)) { |
| return status; |
| } |
| |
| dev->set_flag(DEV_FLAG_BUSY); |
| |
| // proxy devices are created through this handshake process |
| if (creation_ctx) { |
| if (dev->flags() & DEV_FLAG_INVISIBLE) { |
| LOGD(ERROR, *dev, "Driver attempted to create invisible device in create()"); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| dev->set_flag(DEV_FLAG_ADDED); |
| dev->unset_flag(DEV_FLAG_BUSY); |
| dev->rpc = zx::unowned_channel(creation_ctx->device_controller_rpc); |
| dev->coordinator_client = creation_ctx->coordinator_client; |
| creation_ctx->child = dev; |
| mark_dead.cancel(); |
| return ZX_OK; |
| } |
| |
| dev->set_parent(parent); |
| |
| // attach to our parent |
| parent->add_child(dev.get()); |
| |
| if (!(dev->flags() & DEV_FLAG_INSTANCE)) { |
| // Add always consumes the handle |
| status = DriverManagerAdd(parent, dev, proxy_args, props, prop_count, std::move(inspect), |
| std::move(client_remote)); |
| if (status < 0) { |
| LOGD(ERROR, *dev, "Failed to add device %p to driver_manager: %s", dev.get(), |
| zx_status_get_string(status)); |
| dev->parent()->remove_child(*dev); |
| dev->set_parent(nullptr); |
| |
| // 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->unset_flag(DEV_FLAG_BUSY); |
| return status; |
| } |
| } |
| dev->set_flag(DEV_FLAG_ADDED); |
| dev->unset_flag(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 DriverHostContext::DeviceInit(const fbl::RefPtr<zx_device_t>& dev) { |
| if (dev->flags() & DEV_FLAG_INITIALIZING) { |
| return ZX_ERR_BAD_STATE; |
| } |
| // Call dev's init op. |
| if (dev->ops()->init) { |
| dev->set_flag(DEV_FLAG_INITIALIZING); |
| api_lock_.Release(); |
| dev->InitOp(); |
| api_lock_.Acquire(); |
| } else { |
| dev->init_cb(ZX_OK); |
| } |
| return ZX_OK; |
| } |
| |
| void DriverHostContext::DeviceInitReply(const fbl::RefPtr<zx_device_t>& dev, zx_status_t status, |
| const device_init_reply_args_t* args) { |
| if (!(dev->flags() & DEV_FLAG_INITIALIZING)) { |
| LOGD(FATAL, *dev, "Device %p cannot reply to init (flags %#x)", dev.get(), 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 == nullptr) { |
| LOGD(FATAL, *dev, "Device %p cannot reply to init, no callback set (flags %#x)", dev.get(), |
| dev->flags()); |
| } |
| |
| dev->init_cb(status); |
| // Device is no longer invisible. |
| dev->unset_flag(DEV_FLAG_INVISIBLE); |
| // If all children completed intializing, |
| // complete pending bind and rebind connections. |
| bool complete_bind_rebind = true; |
| for (auto& child : dev->parent()->children()) { |
| if (child.flags() & DEV_FLAG_INVISIBLE) { |
| complete_bind_rebind = false; |
| } |
| } |
| if (complete_bind_rebind && dev->parent()->complete_bind_rebind_after_init()) { |
| if (auto bind_conn = dev->parent()->take_bind_conn(); bind_conn) { |
| bind_conn(status); |
| } |
| if (auto rebind_conn = dev->parent()->take_rebind_conn(); rebind_conn) { |
| rebind_conn(status); |
| } |
| } |
| } |
| |
| zx_status_t DriverHostContext::DeviceRemove(const fbl::RefPtr<zx_device_t>& dev, bool unbind_self) { |
| if (dev->flags() & REMOVAL_BAD_FLAGS) { |
| LOGD(ERROR, *dev, "Cannot remove device %p: %s", dev.get(), |
| internal::removal_problem(dev->flags())); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| if (dev->flags() & DEV_FLAG_INVISIBLE) { |
| // We failed during init and the device is being removed. Complete the pending |
| // bind/rebind conn of parent if any. |
| if (auto bind_conn = dev->parent()->take_bind_conn(); bind_conn) { |
| bind_conn(ZX_ERR_IO); |
| } |
| if (auto rebind_conn = dev->parent()->take_rebind_conn(); rebind_conn) { |
| rebind_conn(ZX_ERR_IO); |
| } |
| } |
| VLOGD(1, *dev, "Device %p is being scheduled for removal", dev.get()); |
| // Ask the devcoordinator to schedule the removal of this device and its children. |
| ScheduleRemove(dev, unbind_self); |
| return ZX_OK; |
| } |
| |
| zx_status_t DriverHostContext::DeviceCompleteRemoval(const fbl::RefPtr<zx_device_t>& dev) { |
| VLOGD(1, *dev, "Device %p is being removed (removal requested)", dev.get()); |
| |
| // This recovers the leaked reference that happened in device_add_from_driver(). |
| auto dev_add_ref = fbl::ImportFromRawPtr(dev.get()); |
| DriverManagerRemove(std::move(dev_add_ref)); |
| |
| dev->set_flag(DEV_FLAG_DEAD); |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t DriverHostContext::DeviceUnbind(const fbl::RefPtr<zx_device_t>& dev) { |
| enum_lock_acquire(); |
| |
| if (!(dev->flags() & DEV_FLAG_UNBOUND)) { |
| dev->set_flag(DEV_FLAG_UNBOUND); |
| // Call dev's unbind op. |
| if (dev->ops()->unbind) { |
| VLOGD(1, *dev, "Device %p is being unbound", dev.get()); |
| api_lock_.Release(); |
| dev->UnbindOp(); |
| api_lock_.Acquire(); |
| } 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; |
| } |
| |
| void DriverHostContext::DeviceUnbindReply(const fbl::RefPtr<zx_device_t>& dev) { |
| if (dev->flags() & REMOVAL_BAD_FLAGS) { |
| LOGD(FATAL, *dev, "Device %p cannot reply to unbind, bad flags: %s", dev.get(), |
| internal::removal_problem(dev->flags())); |
| } |
| if (!(dev->flags() & DEV_FLAG_UNBOUND)) { |
| LOGD(FATAL, *dev, "Device %p cannot reply to unbind, not in unbinding state (flags %#x)", |
| dev.get(), dev->flags()); |
| } |
| if (dev->vnode->inflight_transactions() > 0) { |
| LOGD(FATAL, *dev, "Device %p cannot reply to unbind, has %zu outstanding transactions", |
| dev.get(), dev->vnode->inflight_transactions()); |
| } |
| |
| VLOGD(1, *dev, "Device %p unbind completed", dev.get()); |
| if (dev->unbind_cb) { |
| dev->CloseAllConnections(); |
| dev->unbind_cb(ZX_OK); |
| } else { |
| LOGD(FATAL, *dev, "Device %p cannot reply to unbind, no callback set (flags %#x)", dev.get(), |
| dev->flags()); |
| } |
| } |
| |
| void DriverHostContext::DeviceSuspendReply(const fbl::RefPtr<zx_device_t>& dev, zx_status_t status, |
| uint8_t out_state) { |
| // 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 Transaction. |
| // 3. When we make the suspend txn asynchronous using ToAsync() |
| if (dev->vnode->inflight_transactions() > 3) { |
| LOGD(FATAL, *dev, "Device %p cannot reply to suspend, has %zu outstanding transactions", |
| dev.get(), dev->vnode->inflight_transactions()); |
| } |
| |
| if (dev->suspend_cb) { |
| dev->suspend_cb(status, out_state); |
| } else { |
| LOGD(FATAL, *dev, "Device %p cannot reply to suspend, no callback set", dev.get()); |
| } |
| } |
| |
| void DriverHostContext::DeviceResumeReply(const fbl::RefPtr<zx_device_t>& dev, zx_status_t status, |
| uint8_t out_power_state, uint32_t out_perf_state) { |
| if (dev->resume_cb) { |
| if (out_power_state == static_cast<uint8_t>(DevicePowerState::DEVICE_POWER_STATE_D0)) { |
| // Update the current performance state. |
| dev->set_current_performance_state(out_perf_state); |
| } |
| dev->resume_cb(status, out_power_state, out_perf_state); |
| } else { |
| LOGD(FATAL, *dev, "Device %p cannot reply to resume, no callback set", dev.get()); |
| } |
| } |
| |
| zx_status_t DriverHostContext::DeviceRebind(const fbl::RefPtr<zx_device_t>& dev) { |
| if (!dev->children().is_empty() || dev->has_composite()) { |
| // note that we want to be rebound when our children are all gone |
| dev->set_flag(DEV_FLAG_WANTS_REBIND); |
| // request that any existing children go away |
| ScheduleUnbindChildren(dev); |
| } else { |
| std::string drv = dev->get_rebind_drv_name().value_or(""); |
| return DeviceBind(dev, drv.c_str()); |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t DriverHostContext::DeviceOpen(const fbl::RefPtr<zx_device_t>& dev, |
| fbl::RefPtr<zx_device_t>* out, uint32_t flags) { |
| inspect_.DeviceOpenStats().Update(); |
| if (dev->flags() & DEV_FLAG_DEAD) { |
| LOGD(ERROR, *dev, "Cannot open device %p, device is dead", dev.get()); |
| return ZX_ERR_BAD_STATE; |
| } |
| fbl::RefPtr<zx_device_t> new_ref(dev); |
| zx_status_t r; |
| zx_device_t* opened_dev = nullptr; |
| { |
| api_lock_.Release(); |
| r = dev->OpenOp(&opened_dev, flags); |
| if (r == ZX_OK) { |
| dev->inspect().increment_open_count(); |
| } |
| api_lock_.Acquire(); |
| } |
| 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)) { |
| LOGD(FATAL, *new_ref, "Cannot open device %p, bad state %#x", opened_dev, flags); |
| } |
| } |
| *out = std::move(new_ref); |
| return r; |
| } |
| |
| zx_status_t DriverHostContext::DeviceClose(fbl::RefPtr<zx_device_t> dev, uint32_t flags) { |
| inspect_.DeviceCloseStats().Update(); |
| api_lock_.Release(); |
| zx_status_t status = dev->CloseOp(flags); |
| if (status == ZX_OK) { |
| dev->inspect().increment_close_count(); |
| } |
| api_lock_.Acquire(); |
| return status; |
| } |
| |
| void DriverHostContext::DeviceSystemSuspend(const fbl::RefPtr<zx_device>& dev, uint32_t flags) { |
| if (dev->auto_suspend_configured()) { |
| dev->ops()->configure_auto_suspend(dev->ctx, false, |
| fuchsia_device_DevicePowerState_DEVICE_POWER_STATE_D0); |
| LOGF(INFO, "System suspend overriding auto suspend for device %p '%s'", dev.get(), dev->name()); |
| } |
| zx_status_t status = ZX_ERR_NOT_SUPPORTED; |
| // If new suspend hook is implemented, prefer that. |
| if (dev->ops()->suspend) { |
| llcpp::fuchsia::device::SystemPowerStateInfo new_state_info; |
| uint8_t suspend_reason = DEVICE_SUSPEND_REASON_SELECTIVE_SUSPEND; |
| |
| status = internal::device_get_dev_power_state_from_mapping(dev, flags, &new_state_info, |
| &suspend_reason); |
| if (status == ZX_OK) { |
| enum_lock_acquire(); |
| { |
| api_lock_.Release(); |
| dev->ops()->suspend(dev->ctx, static_cast<uint8_t>(new_state_info.dev_state), |
| new_state_info.wakeup_enable, suspend_reason); |
| api_lock_.Acquire(); |
| } |
| 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); |
| } |
| |
| void DriverHostContext::DeviceSystemResume(const fbl::RefPtr<zx_device>& dev, |
| uint32_t target_system_state) { |
| if (dev->auto_suspend_configured()) { |
| dev->ops()->configure_auto_suspend(dev->ctx, false, |
| fuchsia_device_DevicePowerState_DEVICE_POWER_STATE_D0); |
| LOGF(INFO, "System resume overriding auto suspend for device %p '%s'", dev.get(), dev->name()); |
| } |
| |
| zx_status_t status = ZX_ERR_NOT_SUPPORTED; |
| // If new resume hook is implemented, prefer that. |
| if (dev->ops()->resume) { |
| enum_lock_acquire(); |
| { |
| api_lock_.Release(); |
| auto& sys_power_states = dev->GetSystemPowerStateMapping(); |
| uint32_t requested_perf_state = |
| internal::get_perf_state(dev, sys_power_states[target_system_state].performance_state); |
| dev->ops()->resume(dev->ctx, requested_perf_state); |
| api_lock_.Acquire(); |
| } |
| enum_lock_release(); |
| return; |
| } |
| |
| // default_resume() returns ZX_ERR_NOT_SUPPORTED |
| if (status == ZX_ERR_NOT_SUPPORTED) { |
| status = ZX_OK; |
| } |
| dev->resume_cb(status, static_cast<uint8_t>(DevicePowerState::DEVICE_POWER_STATE_D0), |
| llcpp::fuchsia::device::DEVICE_PERFORMANCE_STATE_P0); |
| } |
| |
| void DriverHostContext::DeviceSuspendNew(const fbl::RefPtr<zx_device>& dev, |
| DevicePowerState requested_state) { |
| if (dev->auto_suspend_configured()) { |
| LOGF(INFO, "Failed to suspend device %p '%s', auto suspend is enabled", dev.get(), dev->name()); |
| dev->suspend_cb(ZX_ERR_NOT_SUPPORTED, |
| static_cast<uint8_t>(DevicePowerState::DEVICE_POWER_STATE_D0)); |
| return; |
| } |
| if (!(dev->IsPowerStateSupported(requested_state))) { |
| dev->suspend_cb(ZX_ERR_INVALID_ARGS, |
| static_cast<uint8_t>(DevicePowerState::DEVICE_POWER_STATE_D0)); |
| return; |
| } |
| |
| if (dev->ops()->suspend) { |
| dev->ops()->suspend(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>(DevicePowerState::DEVICE_POWER_STATE_D0)); |
| } |
| |
| zx_status_t DriverHostContext::DeviceSetPerformanceState(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) { |
| zx_status_t status = dev->ops()->set_performance_state(dev->ctx, requested_state, out_state); |
| if (!(dev->IsPerformanceStateSupported(*out_state))) { |
| LOGD(FATAL, *dev, |
| "Device %p 'set_performance_state' hook returned an unsupported performance state", |
| dev.get()); |
| } |
| dev->set_current_performance_state(*out_state); |
| return status; |
| } |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| void DriverHostContext::DeviceResumeNew(const fbl::RefPtr<zx_device>& dev) { |
| if (dev->auto_suspend_configured()) { |
| LOGF(INFO, "Failed to resume device %p '%s', auto suspend is enabled", dev.get(), dev->name()); |
| dev->resume_cb(ZX_ERR_NOT_SUPPORTED, |
| static_cast<uint8_t>(DevicePowerState::DEVICE_POWER_STATE_D0), |
| llcpp::fuchsia::device::DEVICE_PERFORMANCE_STATE_P0); |
| return; |
| } |
| // If new resume hook is implemented, prefer that. |
| if (dev->ops()->resume) { |
| uint32_t requested_perf_state = |
| internal::get_perf_state(dev, llcpp::fuchsia::device::DEVICE_PERFORMANCE_STATE_P0); |
| dev->ops()->resume(dev->ctx, requested_perf_state); |
| return; |
| } |
| dev->resume_cb(ZX_ERR_NOT_SUPPORTED, |
| static_cast<uint8_t>(DevicePowerState::DEVICE_POWER_STATE_D0), |
| llcpp::fuchsia::device::DEVICE_PERFORMANCE_STATE_P0); |
| } |
| |
| zx_status_t DriverHostContext::DeviceConfigureAutoSuspend(const fbl::RefPtr<zx_device>& dev, |
| bool enable, |
| 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; |
| } |
| |
| void DriverHostContext::QueueDeviceForFinalization(zx_device_t* device) { |
| // Put on the defered work list for finalization |
| defer_device_list_.push_back(device); |
| |
| // Immediately finalize if there's not an active enumerator |
| if (enumerators_ == 0) { |
| FinalizeDyingDevices(); |
| } |
| } |