blob: 8c12a86abfc108fc8209d3b8d4abff70b958e7b0 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#ifndef ZIRCON_KERNEL_INCLUDE_KERNEL_WAIT_QUEUE_INTERNAL_H_
#define ZIRCON_KERNEL_INCLUDE_KERNEL_WAIT_QUEUE_INTERNAL_H_
#include <lib/ktrace.h>
#include <platform.h>
#include <zircon/errors.h>
#include <kernel/scheduler.h>
#include <kernel/thread.h>
#include <kernel/wait.h>
// Notes for WaitQueue::BlockEtcPreamble and BlockEtcPostamble.
//
// Currently, there are two variants of WaitQueues in Zircon. The standard
// WaitQueue (used for most tasks) and the specialized
// OwnedWaitQueues (used for mutexes/futexes/brwlocks, and anything else which
// needs to have a concept of priority inheritance).
//
// The "Block" operation for these two versions are _almost_ identical. The
// only real difference between the two is that the OWQ implementation needs to
// stop after we have decided that we are actually going to block the thread,
// but before the timeout timer is armed and the thread is actually blocked, in
// order to update it's PI chain bookkeeping.
//
// Instead of duplicating the code, or exposing a code-injection
// mechanism into the public API, we split the code into two private
// inline member functions that we hide in instead. The first
// (BlockEtcPreamble) performs all of the checks and bookkeeping up-to
// the point of arming the timer and blocking, the second
// (BlockEtcPostamble) finishes the job.
//
// The traditional WaitQueue implementation of
// WaitQueue::BlockEtc just calls these two functions back to back, relying on
// the inlining to generate the original function. The OwnedWaitQueue
// implementation does the same, but injects its bookkeeping at the appropriate
// point.
//
// Nothing but these two specific pieces of code should *ever* need to
// call these functions. Users should *always* be using either
// WaitQueue::BlockEtc/Block, or OwnedWaitQueue::BlockAndAssignOwner
// instead.
//
inline zx_status_t WaitQueue::BlockEtcPreamble(const Deadline& deadline, uint signal_mask,
ResourceOwnership reason,
Interruptible interruptible) TA_REQ(thread_lock) {
Thread* current_thread = Thread::Current::Get();
if (deadline.when() != ZX_TIME_INFINITE && deadline.when() <= current_time()) {
return ZX_ERR_TIMED_OUT;
}
if (interruptible == Interruptible::Yes && (unlikely(current_thread->signals() & ~signal_mask))) {
zx_status_t status = current_thread->CheckKillOrSuspendSignal();
if (status != ZX_OK) {
return status;
}
}
WaitQueueCollection::ThreadState& state = current_thread->wait_queue_state();
state.interruptible_ = interruptible;
collection_.Insert(current_thread);
if (reason == ResourceOwnership::Normal) {
current_thread->set_blocked();
} else {
current_thread->set_blocked_read_lock();
}
state.blocking_wait_queue_ = this;
state.blocked_status_ = ZX_OK;
return ZX_OK;
}
inline zx_status_t WaitQueue::BlockEtcPostamble(const Deadline& deadline) TA_REQ(thread_lock) {
Thread* current_thread = Thread::Current::Get();
Timer timer;
// if the deadline is nonzero or noninfinite, set a callback to yank us out of the queue
if (deadline.when() != ZX_TIME_INFINITE) {
timer.Set(deadline, &WaitQueue::TimeoutHandler, (void*)current_thread);
}
ktrace_ptr(TAG_KWAIT_BLOCK, this, 0, 0);
Scheduler::Block();
ktrace_ptr(TAG_KWAIT_UNBLOCK, this, current_thread->wait_queue_state().blocked_status_, 0);
// we don't really know if the timer fired or not, so it's better safe to try to cancel it
if (deadline.when() != ZX_TIME_INFINITE) {
timer.Cancel();
}
current_thread->wait_queue_state().interruptible_ = Interruptible::No;
return current_thread->wait_queue_state().blocked_status_;
}
#endif // ZIRCON_KERNEL_INCLUDE_KERNEL_WAIT_QUEUE_INTERNAL_H_