blob: 1a0c80bc9ccae56243742083f461f741524533eb [file] [log] [blame]
// 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 "unbind_task.h"
#include <zircon/status.h>
#include "coordinator.h"
#include "src/devices/lib/log/log.h"
UnbindTask::UnbindTask(fbl::RefPtr<Device> device, UnbindTaskOpts opts, Completion completion)
: Task(device->coordinator->dispatcher(), std::move(completion), opts.post_on_create),
device_(std::move(device)),
do_unbind_(opts.do_unbind),
driver_host_requested_(opts.driver_host_requested) {}
UnbindTask::~UnbindTask() = default;
fbl::RefPtr<UnbindTask> UnbindTask::Create(fbl::RefPtr<Device> device, UnbindTaskOpts opts,
Completion completion) {
return fbl::MakeRefCounted<UnbindTask>(std::move(device), opts, std::move(completion));
}
// Schedules the unbind tasks for the device's children.
void UnbindTask::ScheduleUnbindChildren() {
auto remove_task = device_->GetActiveRemove();
if (remove_task == nullptr) {
LOGF(ERROR, "Unbind task failed, but no remove task exists for device %p '%s'", device_.get(),
device_->name().data());
return;
}
// Remove task needs to wait for the current unbind task to complete.
remove_task->AddDependency(fbl::RefPtr(this));
fbl::RefPtr<UnbindTask> proxy_unbind_task = nullptr;
if (device_->proxy() != nullptr) {
switch (device_->proxy()->state()) {
case Device::State::kDead:
// We are already in the process of unbinding ourselves and our children,
// no need to create a new one.
case Device::State::kUnbinding:
break;
// The created unbind task will wait for the init to complete.
case Device::State::kInitializing:
case Device::State::kSuspended:
// The created unbind task will wait for the suspend to complete.
case Device::State::kSuspending:
case Device::State::kResuming:
case Device::State::kResumed:
case Device::State::kActive: {
device_->proxy()->CreateUnbindRemoveTasks(UnbindTaskOpts{
.do_unbind = false, .post_on_create = false, .driver_host_requested = false});
proxy_unbind_task = device_->proxy()->GetActiveUnbind();
// The proxy's unbind task may have already completed, in which case we only
// have to wait on the remove task.
if (proxy_unbind_task) {
proxy_unbind_task->AddDependency(fbl::RefPtr(this));
}
// The device should not be removed until its children have been removed.
remove_task->AddDependency(device_->proxy()->GetActiveRemove());
}
}
// A device may have both a proxy device and children devices,
// so continue rather than returning early.
}
auto children = device_->children();
// Though we try to schedule the unbind tasks for both a device's proxy and its children,
// its possible for ScheduleRemove() to be called directly on a proxy unbind task, such as in the
// case of a forced remove.
// To handle this, we need to schedule unbind tasks for the proxy "children", which are actually
// stored in our parent's children list.
// This means we may end up adding the children as dependent on a proxy device twice,
// but that is handled by the task logic.
if (device_->flags & DEV_CTX_PROXY && device_->parent()) {
children = device_->parent()->children();
}
for (auto& child : children) {
// Use a switch statement here so that this gets reconsidered if we add
// more states.
switch (child.state()) {
case Device::State::kDead:
case Device::State::kUnbinding:
continue;
case Device::State::kInitializing:
case Device::State::kSuspended:
case Device::State::kSuspending:
case Device::State::kResuming:
case Device::State::kResumed:
case Device::State::kActive:
break;
}
child.CreateUnbindRemoveTasks(
UnbindTaskOpts{.do_unbind = true, .post_on_create = false, .driver_host_requested = false});
auto parent = device_->proxy() != nullptr ? device_->proxy() : device_;
// The child unbind task may have already completed, in which case we only need to wait
// for the child's remove task.
auto child_unbind_task = child.GetActiveUnbind();
if (child_unbind_task) {
auto parent_unbind_task = parent->GetActiveUnbind();
if (parent_unbind_task) {
child_unbind_task->AddDependency(parent_unbind_task);
}
}
// Since the child is not dead, the remove task must exist.
auto child_remove_task = child.GetActiveRemove();
ZX_ASSERT(child_remove_task != nullptr);
auto parent_remove_task = parent->GetActiveRemove();
if (parent_remove_task) {
parent_remove_task->AddDependency(child_remove_task);
}
}
}
void UnbindTask::Run() {
LOGF(INFO, "Running unbind task for device %p '%s', do_unbind %b", device_.get(),
device_->name().data(), do_unbind_);
if (device_->state() == Device::State::kInitializing) {
auto init_task = device_->GetActiveInit();
ZX_ASSERT(init_task != nullptr);
AddDependency(init_task);
return;
}
// The device is currently suspending, wait for it to complete.
if (device_->state() == Device::State::kSuspending) {
auto suspend_task = device_->GetActiveSuspend();
ZX_ASSERT(suspend_task != nullptr);
AddDependency(suspend_task);
return;
}
if (device_->state() == Device::State::kResuming) {
auto resume_task = device_->GetActiveResume();
ZX_ASSERT(resume_task != nullptr);
AddDependency(resume_task);
return;
}
// We need to schedule the child tasks before completing the unbind task runs,
// as composite device disassociation may occur.
ScheduleUnbindChildren();
auto completion = [this](zx_status_t status) {
// If this unbind task failed, force remove all devices from the driver_host.
bool failed_unbind = status != ZX_OK && status != ZX_ERR_UNAVAILABLE;
if (failed_unbind && device_->state() != Device::State::kDead) {
LOGF(ERROR, "Unbind task failed, force removing device %p '%s': %s", device_.get(),
device_->name().data(), zx_status_get_string(status));
device_->coordinator->RemoveDevice(device_, true /* forced */);
}
// The forced removal will schedule new unbind tasks if needed (e.g. for proxy tasks),
// so we should not propagate errors other than ZX_ERR_UNAVAILABLE.
Complete(status == ZX_OK ? ZX_OK : ZX_ERR_UNAVAILABLE);
};
// Check if we should send the unbind request to the driver_host. We do not want to send it if:
// - This device is not in a driver_host. This happens for the top-level devices like /sys
// provided by devcoordinator, or if the device has already been removed.
// - device_remove does not call unbind on the device.
bool send_unbind = (device_->host() != nullptr) && do_unbind_;
zx_status_t status = ZX_OK;
if (send_unbind) {
status = device_->SendUnbind(std::move(completion));
if (status == ZX_OK) {
// Sent the unbind request, the driver_host will call our completion when ready.
return;
}
}
// Save a copy of the device in case this task's destructor runs after the
// completion returns.
fbl::RefPtr<Device> device = device_;
// No unbind request sent, need to call the completion now.
completion(status);
// Since the device didn't successfully send an Unbind request, it will not
// drop our unbind task reference. We need to drop it now unless the error was
// that the unbind request had already been sent (ZX_ERR_UNAVAILABLE).
if (status != ZX_ERR_UNAVAILABLE) {
device->DropUnbindTask();
}
}
RemoveTask::RemoveTask(fbl::RefPtr<Device> device, Completion completion)
: Task(device->coordinator->dispatcher(), std::move(completion), false /* post_on_create */),
device_(std::move(device)) {}
RemoveTask::~RemoveTask() = default;
fbl::RefPtr<RemoveTask> RemoveTask::Create(fbl::RefPtr<Device> device, Completion completion) {
return fbl::MakeRefCounted<RemoveTask>(std::move(device), std::move(completion));
}
void RemoveTask::Run() {
LOGF(INFO, "Running remove task for device %p '%s'", device_.get(), device_->name().data());
auto completion = [this](zx_status_t status) {
// If this remove task failed, force remove all devices from the driver_host.
bool failed_remove = status != ZX_OK && status != ZX_ERR_UNAVAILABLE;
if (failed_remove && device_->state() != Device::State::kDead) {
LOGF(ERROR, "Remove task failed, forcing remove of device %p '%s': %s", device_.get(),
device_->name().data(), zx_status_get_string(status));
device_->coordinator->RemoveDevice(device_, true /* forced */);
}
// The forced removal will schedule new remove tasks if needed (e.g. for proxy tasks),
// so we should not propagate errors other than ZX_ERR_UNAVAILABLE.
Complete(status == ZX_OK ? ZX_OK : ZX_ERR_UNAVAILABLE);
};
zx_status_t status = ZX_OK;
if (device_->host() != nullptr) {
status = device_->SendCompleteRemoval(std::move(completion));
if (status == ZX_OK) {
// Sent the remove request, the driver_host will call our completion when ready.
return;
}
}
// Save a copy of the device in case this task's destructor runs after the
// completion returns.
fbl::RefPtr<Device> device = device_;
// No remove request sent, need to call the completion now.
completion(status);
// Since the device didn't successfully send an CompleteRemoval request, it will not
// drop our remove task reference. We need to drop it now unless the error was
// that the remove request had already been sent (ZX_ERR_UNAVAILABLE).
if (status != ZX_ERR_UNAVAILABLE) {
device->DropRemoveTask();
}
}