blob: f046556b3aacd22d088b401f7396da78e9187f8d [file] [log] [blame]
/* Copyright (c) 2013 The Regents of the University of California
* Barret Rhoden <brho@cs.berkeley.edu>
* See LICENSE for details.
*
* Plan9 style Rendezvous (http://plan9.bell-labs.com/sys/doc/sleep.html)
*
* We implement it with CVs, and it can handle multiple sleepers/wakers. */
#include <rendez.h>
#include <kthread.h>
#include <alarm.h>
#include <assert.h>
#include <smp.h>
#include <err.h>
void rendez_init(struct rendez *rv)
{
cv_init_irqsave(&rv->cv);
}
void rendez_sleep(struct rendez *rv, int (*cond)(void*), void *arg)
{
int8_t irq_state = 0;
struct cv_lookup_elm cle;
assert(can_block(this_pcpui_ptr()));
/* Do a quick check before registering and sleeping. this is the
* 'check, signal, check again' pattern, where the first check is an
* optimization. Many rendezes will already be satisfied, so we want to
* avoid excessive locking associated with reg/dereg. */
cv_lock_irqsave(&rv->cv, &irq_state);
if (cond(arg)) {
cv_unlock_irqsave(&rv->cv, &irq_state);
return;
}
__reg_abortable_cv(&cle, &rv->cv);
/* Mesa-style semantics, which is definitely what you want. See the
* discussion at the end of the URL above. */
while (!cond(arg)) {
/* it's okay if we miss the ABORT flag; we hold the cv lock, so
* an aborter's broadcast is waiting until we unlock. */
if (should_abort(&cle)) {
cv_unlock_irqsave(&rv->cv, &irq_state);
dereg_abortable_cv(&cle);
error(EINTR, "syscall aborted");
}
cv_wait(&rv->cv);
cpu_relax();
}
cv_unlock_irqsave(&rv->cv, &irq_state);
dereg_abortable_cv(&cle);
}
/* Force a wakeup of all waiters on the rv, including non-timeout users. For
* those, they will just wake up, see the condition is still false (probably)
* and go back to sleep. */
static void rendez_alarm_handler(struct alarm_waiter *awaiter)
{
struct rendez *rv = (struct rendez*)awaiter->data;
rendez_wakeup(rv);
}
void rendez_debug_waiter(struct alarm_waiter *awaiter)
{
struct rendez *rv = (struct rendez*)awaiter->data;
struct cond_var *cv = &rv->cv;
struct kthread *kth;
int8_t irq_state = 0;
cv_lock_irqsave(cv, &irq_state);
TAILQ_FOREACH(kth, &cv->waiters, link) {
print_lock();
printk("-------- kth %s ----------\n", kth->name);
backtrace_kthread(kth);
printk("-----------------\n");
print_unlock();
}
cv_unlock_irqsave(cv, &irq_state);
}
/* Like sleep, but it will timeout in 'usec' microseconds. */
void rendez_sleep_timeout(struct rendez *rv, int (*cond)(void*), void *arg,
uint64_t usec)
{
int8_t irq_state = 0;
struct alarm_waiter awaiter;
struct cv_lookup_elm cle;
struct timer_chain *pcpui_tchain = &per_cpu_info[core_id()].tchain;
assert(can_block(this_pcpui_ptr()));
if (!usec)
return;
/* Doing this cond check early, but then unlocking again. Mostly just
* to avoid weird issues with the CV lock and the alarm tchain lock. */
cv_lock_irqsave(&rv->cv, &irq_state);
if (cond(arg)) {
cv_unlock_irqsave(&rv->cv, &irq_state);
return;
}
cv_unlock_irqsave(&rv->cv, &irq_state);
/* The handler will call rendez_wake, but won't mess with the condition
* state. It's enough to break us out of cv_wait() to see .on_tchain is
* clear, which is a proxy for "has my alarm fired or will it soon." */
init_awaiter(&awaiter, rendez_alarm_handler);
awaiter.data = rv;
set_awaiter_rel(&awaiter, usec);
/* Set our alarm on this cpu's tchain. Note that when we sleep in
* cv_wait, we could be migrated, and later on we could be unsetting the
* alarm remotely. */
set_alarm(pcpui_tchain, &awaiter);
cv_lock_irqsave(&rv->cv, &irq_state);
__reg_abortable_cv(&cle, &rv->cv);
/* We could wake early for a few reasons. Legit wakeups after a changed
* condition (and we should exit), other alarms with different timeouts
* (and we should go back to sleep), etc. Note it is possible for our
* alarm to fire immediately upon setting it: before we even cv_lock. */
while (!cond(arg) && !alarm_expired(&awaiter)) {
if (should_abort(&cle)) {
cv_unlock_irqsave(&rv->cv, &irq_state);
unset_alarm(pcpui_tchain, &awaiter);
dereg_abortable_cv(&cle);
error(EINTR, "syscall aborted");
}
cv_wait(&rv->cv);
cpu_relax();
}
cv_unlock_irqsave(&rv->cv, &irq_state);
dereg_abortable_cv(&cle);
/* Turn off our alarm. If it already fired, this is a no-op. Note this
* could be cross-core. */
unset_alarm(pcpui_tchain, &awaiter);
}
/* plan9 rendez returned a pointer to the proc woken up. we return "true" if we
* woke someone up. */
bool rendez_wakeup(struct rendez *rv)
{
int8_t irq_state = 0;
bool ret;
/* The plan9 style "one sleeper, one waker" could get by with a signal
* here. But we want to make sure all potential waiters are woken up.
*/
cv_lock_irqsave(&rv->cv, &irq_state);
ret = rv->cv.nr_waiters ? TRUE : FALSE;
__cv_broadcast(&rv->cv);
cv_unlock_irqsave(&rv->cv, &irq_state);
return ret;
}