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
#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()) {
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;
if (reason == ResourceOwnership::Normal) {
} else {
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);
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) {
current_thread->wait_queue_state().interruptible_ = Interruptible::No;
return current_thread->wait_queue_state().blocked_status_;