blob: 13c578934da36c68ce6d9b9a15d8bea9118b95ee [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/sched.h>
#include <kernel/thread.h>
#include <kernel/wait.h>
namespace internal {
void wait_queue_insert(WaitQueue* wait, Thread* t) TA_REQ(thread_lock);
void wait_queue_remove_head(Thread* t) TA_REQ(thread_lock);
void wait_queue_remove_thread(Thread* t) TA_REQ(thread_lock);
void wait_queue_timeout_handler(Timer* timer, zx_time_t now, void* arg);
// Used by WaitQueue and OwnedWaitQueue to manage changes to the maximum
// priority of a wait queue due to external effects (thread priority change,
// thread timeout, thread killed). Do not call this function from an external
// site.
bool wait_queue_waiters_priority_changed(WaitQueue* wq, int old_prio) TA_REQ(thread_lock);
// Remove a thread from a wait queue, maintain the wait queue's internal count,
// and update the wait_queue specific bookkeeping in the thread in the process.
inline void wait_queue_dequeue_thread_internal(WaitQueue* wait, Thread* t,
zx_status_t wait_queue_error) TA_REQ(thread_lock) {
DEBUG_ASSERT(t != nullptr);
DEBUG_ASSERT(list_in_list(&t->wait_queue_state_.queue_node_));
DEBUG_ASSERT(t->state_ == THREAD_BLOCKED || t->state_ == THREAD_BLOCKED_READ_LOCK);
DEBUG_ASSERT(t->blocking_wait_queue_ == wait);
wait_queue_remove_thread(t);
wait->collection_.count_--;
t->blocked_status_ = wait_queue_error;
t->blocking_wait_queue_ = NULL;
}
// Notes for wait_queue_block_etc_(pre|post).
//
// Currently, there are two variants of wait_queues 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 inline functions that we hide in
// internal:: instead. The first (pre) performs all of the checks and bookkeeping
// up-to the point of arming the timer and blocking, the second (post) finishes
// the job.
//
// The traditional WaitQueue implementation of
// wait_queue_block_etc 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
// wait_queue_block_etc/wait_queue_block (or the WaitQueue wrappers of the
// same), or OwnedWaitQueue::BlockAndAssignOwner instead.
//
inline zx_status_t wait_queue_block_etc_pre(WaitQueue* wait, const Deadline& deadline,
uint signal_mask, ResourceOwnership reason)
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 (current_thread->interruptable_ && (unlikely(current_thread->signals_ & ~signal_mask))) {
if (current_thread->signals_ & THREAD_SIGNAL_KILL) {
return ZX_ERR_INTERNAL_INTR_KILLED;
} else if (current_thread->signals_ & THREAD_SIGNAL_SUSPEND) {
return ZX_ERR_INTERNAL_INTR_RETRY;
}
}
wait_queue_insert(wait, current_thread);
wait->collection_.count_++;
current_thread->state_ =
(reason == ResourceOwnership::Normal) ? THREAD_BLOCKED : THREAD_BLOCKED_READ_LOCK;
current_thread->blocking_wait_queue_ = wait;
current_thread->blocked_status_ = ZX_OK;
return ZX_OK;
}
inline zx_status_t wait_queue_block_etc_post(WaitQueue* wait, 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, wait_queue_timeout_handler, (void*)current_thread);
}
ktrace_ptr(TAG_KWAIT_BLOCK, wait, 0, 0);
sched_block();
ktrace_ptr(TAG_KWAIT_UNBLOCK, wait, current_thread->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();
}
return current_thread->blocked_status_;
}
} // namespace internal
#endif // ZIRCON_KERNEL_INCLUDE_KERNEL_WAIT_QUEUE_INTERNAL_H_