blob: 2c6e71d2cdee4e5378ce3fbc0e76d0e34b459e74 [file] [log] [blame]
// Copyright 2021 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 "third_party/iwlwifi/platform/task-internal.h"
#include <lib/async/time.h>
#include <lib/stdcompat/atomic.h>
#include <zircon/assert.h>
#include <limits>
namespace wlan::iwlwifi {
namespace {
enum TaskState : zx_futex_t {
kInvalid = 0,
kIdle = 1,
kQueued = 2,
kExecuting = 3,
};
} // namespace
TaskInternal::TaskInternal(async_dispatcher_t* dispatcher, FuncType func, void* data)
: async_task_t{}, dispatcher_(dispatcher), func_(func), data_(data), state_(TaskState::kIdle) {
this->handler = [](async_dispatcher_t* dispatcher, async_task_t* task, zx_status_t status) {
auto task_internal = static_cast<TaskInternal*>(task);
cpp20::atomic_ref<zx_futex_t> state_ref(task_internal->state_);
ZX_DEBUG_ASSERT(state_ref.load() == TaskState::kQueued);
if (status == ZX_OK) {
state_ref.store(TaskState::kExecuting, std::memory_order_release);
(*task_internal->func_)(task_internal->data_);
}
state_ref.store(TaskState::kIdle, std::memory_order_release);
zx_futex_wake(&task_internal->state_, std::numeric_limits<uint32_t>::max());
};
}
TaskInternal::~TaskInternal() {
CancelSync();
auto state = cpp20::atomic_ref<zx_futex_t>(state_).load();
ZX_DEBUG_ASSERT_MSG(state == TaskState::kIdle, "Task state is %u, but expected %u", state,
TaskState::kIdle);
}
zx_status_t TaskInternal::Post(zx_duration_t delay) {
zx_status_t status = ZX_OK;
// The async_dpsatcher interface does not allow tasks to be multiply posted.
if ((status = CancelSync()) != ZX_OK) {
if (status != ZX_ERR_NOT_FOUND) {
return status;
}
}
zx_futex_t expected = TaskState::kIdle;
zx_futex_t desired = TaskState::kQueued;
cpp20::atomic_ref<zx_futex_t> state_ref(state_);
if (!state_ref.compare_exchange_strong(expected, desired, std::memory_order_acq_rel)) {
// Post() is being called simultaneously from multiple threads, so we just early-return the
// calls after the first.
return ZX_OK;
}
// Post the task.
this->deadline = async_now(dispatcher_) + delay;
if ((status = async_post_task(dispatcher_, this)) != ZX_OK) {
state_ref.store(TaskState::kIdle, std::memory_order_release);
zx_futex_wake(&state_, std::numeric_limits<uint32_t>::max());
return status;
}
return ZX_OK;
}
zx_status_t TaskInternal::Wait() {
zx_status_t status = ZX_OK;
cpp20::atomic_ref<zx_futex_t> state_ref(state_);
zx_futex_t value = state_ref.load(std::memory_order_acquire);
while (value != TaskState::kIdle) {
if ((status = zx_futex_wait(&state_, value, ZX_HANDLE_INVALID, ZX_TIME_INFINITE)) != ZX_OK) {
if (status != ZX_ERR_BAD_STATE) {
return status;
}
// ZX_ERR_BAD_STATE means that `state_` has already changed to be different than `value`,
// which is not an error condition here.
}
value = state_ref.load(std::memory_order_acquire);
}
return ZX_OK;
}
zx_status_t TaskInternal::Cancel() {
zx_status_t status = async_cancel_task(dispatcher_, this);
if (status != ZX_OK) {
return status;
}
cpp20::atomic_ref<zx_futex_t> state_ref(state_);
ZX_DEBUG_ASSERT(state_ref.load() == TaskState::kQueued);
state_ref.store(TaskState::kIdle, std::memory_order_release);
zx_futex_wake(&state_, std::numeric_limits<uint32_t>::max());
return ZX_OK;
}
zx_status_t TaskInternal::CancelSync() {
// Cancel the task.
zx_status_t status = Cancel();
// Cancelling the task can fail if the task is currently executing.
// There might be an edge case here where we fail to cancel the task because the task is
// executing, but by the time we reach this check the task finishes executing and has returned to
// TaskState::kIdle. In that case, it's okay to not call Wait() and just return the status. We can
// consider that case equivalent to the task being idle before calling Cancel().
if (status != ZX_OK && cpp20::atomic_ref<zx_futex_t>(state_).load() == TaskState::kExecuting) {
// For CancelSync(), we'll allow the task to finish executing by calling Wait().
return Wait();
}
// Otherwise, the following cases can occur:
// * Task was already idle, and cancelling failed => return error
// * Task was queued and cancelled successfully => return ZX_OK
// * Task was queued but not cancelled successfully => return error
return status;
}
} // namespace wlan::iwlwifi