| // Copyright 2019 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 "suspend_task.h" |
| |
| #include "coordinator.h" |
| |
| namespace { |
| |
| bool IsDeviceBeingRemoved(fbl::RefPtr<Device> device) { |
| auto remove_task = device->GetActiveRemove(); |
| if ((device->state() == Device::State::kUnbinding) || (device->state() == Device::State::kDead) || |
| (remove_task != nullptr)) { |
| return true; |
| } |
| return false; |
| } |
| |
| } // namespace |
| |
| SuspendTask::SuspendTask(fbl::RefPtr<Device> device, uint32_t flags, Completion completion) |
| : Task(device->coordinator->dispatcher(), std::move(completion)), |
| device_(std::move(device)), |
| flags_(flags) {} |
| |
| SuspendTask::~SuspendTask() = default; |
| |
| fbl::RefPtr<SuspendTask> SuspendTask::Create(fbl::RefPtr<Device> device, uint32_t flags, |
| Completion completion) { |
| return fbl::MakeRefCounted<SuspendTask>(std::move(device), flags, std::move(completion)); |
| } |
| |
| void SuspendTask::Run() { |
| bool found_more_dependencies = false; |
| for (auto& child : device_->children()) { |
| // Use a switch statement here so that this gets reconsidered if we add |
| // more states. |
| switch (child.state()) { |
| // If the device is dead, any existing suspend task would have been forcibly completed. |
| case Device::State::kDead: |
| case Device::State::kUnbinding: |
| case Device::State::kSuspended: |
| continue; |
| case Device::State::kInitializing: |
| case Device::State::kSuspending: |
| case Device::State::kActive: |
| case Device::State::kResuming: |
| case Device::State::kResumed: |
| break; |
| } |
| if (!IsDeviceBeingRemoved(fbl::RefPtr(&child))) { |
| AddDependency(child.RequestSuspendTask(flags_)); |
| found_more_dependencies = true; |
| } |
| } |
| if (found_more_dependencies) { |
| return; |
| } |
| |
| // Handle the device proxy, if it exists, after children since they might |
| // depend on it. |
| if (device_->proxy() != nullptr) { |
| switch (device_->proxy()->state()) { |
| case Device::State::kDead: |
| case Device::State::kSuspended: |
| case Device::State::kResuming: |
| case Device::State::kResumed: |
| break; |
| case Device::State::kInitializing: |
| case Device::State::kUnbinding: |
| case Device::State::kSuspending: |
| case Device::State::kActive: { |
| AddDependency(device_->proxy()->RequestSuspendTask(flags_)); |
| return; |
| } |
| } |
| } |
| |
| if (device_->state() == Device::State::kInitializing) { |
| auto init_task = device_->GetActiveInit(); |
| ZX_ASSERT(init_task != nullptr); |
| AddDependency(init_task); |
| return; |
| } |
| |
| // The device is about to be removed, complete suspend right away |
| if (IsDeviceBeingRemoved(device_)) { |
| return Complete(ZX_OK); |
| } |
| |
| // The device is about to be resumed, wait for it to complete. |
| if (device_->state() == Device::State::kResuming) { |
| auto resume_task = device_->GetActiveResume(); |
| AddDependency(resume_task); |
| return; |
| } |
| |
| // Check if this device is not in a driver_host. This happens for the |
| // top-level devices like /sys provided by devcoordinator, |
| // or the device is already dead. |
| if (device_->host() == nullptr) { |
| // device shouldn't be set to suspended if it's already dead |
| if (device_->state() != Device::State::kDead) { |
| device_->set_state(Device::State::kSuspended); |
| } |
| return Complete(ZX_OK); |
| } |
| |
| auto completion = [this](zx_status_t status) { Complete(status); }; |
| zx_status_t status = device_->SendSuspend(flags_, std::move(completion)); |
| if (status != ZX_OK) { |
| Complete(status); |
| } |
| } |