blob: 7272f0c243d9506a14d4e94c27232dbceb854105 [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();
ZX_DEBUG_ASSERT(cpp20::atomic_ref<zx_futex_t>(state_).load() == 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 = ZX_OK;
if ((status = Cancel()) != ZX_OK) {
return status;
}
// Now wait for completion.
return Wait();
}
} // namespace wlan::iwlwifi