281 lines
7.2 KiB
C
281 lines
7.2 KiB
C
/**
|
|
* \file
|
|
* \brief Event queue implementatino
|
|
*
|
|
* This code implements a thread-safe queue of pending events which are
|
|
* serviced by a single waitset.
|
|
*
|
|
* [!] WARNING: current realization of event queues is unsuitable for
|
|
* cross-core operation. Crux of the issue: trigger channel for waitset
|
|
* belonging to *another* core but waitsets are assumed to be local to a
|
|
* dispatcher.
|
|
*
|
|
* Example scenario:
|
|
* - Consumer C (on core 0) calls get_next_event and blocks on
|
|
* ws->waiting_threads.
|
|
* - Producer P (on core 1) invokes event_queue_add which in turn triggers
|
|
* channel. P disables its dispatcher, unblocks a thread and C is returned.
|
|
* Code assumes remote wakeup should not occur but C is on core 0.
|
|
*
|
|
* For further details see:
|
|
* https://lists.inf.ethz.ch/mailman/private/aos/2013/002746.html
|
|
*/
|
|
|
|
/*
|
|
* Copyright (c) 2010, 2012, 2015, ETH Zurich.
|
|
* All rights reserved.
|
|
*
|
|
* This file is distributed under the terms in the attached LICENSE file.
|
|
* If you do not find this file, copies can be found by writing to:
|
|
* ETH Zurich D-INFK, Universitaetsstrasse 6, CH-8092 Zurich. Attn: Systems Group.
|
|
*/
|
|
|
|
#include <aos/aos.h>
|
|
#include <aos/event_queue.h>
|
|
#include <aos/waitset_chan.h>
|
|
|
|
/**
|
|
* \brief Initialise a new event queue
|
|
*
|
|
* \param q Storage for event queue
|
|
* \param waitset Waitset that will service the queue
|
|
* \param mode Operating mode for the queue
|
|
*/
|
|
void event_queue_init(struct event_queue *q, struct waitset *waitset,
|
|
enum event_queue_mode mode)
|
|
{
|
|
waitset_chanstate_init(&q->waitset_state, CHANTYPE_EVENT_QUEUE);
|
|
thread_mutex_init(&q->mutex);
|
|
q->head = q->tail = NULL;
|
|
q->waitset = waitset;
|
|
q->mode = mode;
|
|
}
|
|
|
|
static struct event_queue_node *next_event(struct event_queue *q)
|
|
{
|
|
// dequeue the next node from the head
|
|
struct event_queue_node *qn = q->head;
|
|
|
|
if (qn == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
assert(qn->prev == NULL);
|
|
|
|
if (qn->next == NULL) {
|
|
assert(q->tail == qn);
|
|
q->head = q->tail = NULL;
|
|
} else {
|
|
qn->next->prev = NULL;
|
|
q->head = qn->next;
|
|
}
|
|
|
|
return qn;
|
|
}
|
|
|
|
static void event_queue_runner(void *arg)
|
|
{
|
|
struct event_queue *q = arg;
|
|
errval_t err;
|
|
|
|
assert(q->mode == EVENT_QUEUE_CONTINUOUS);
|
|
|
|
thread_mutex_lock(&q->mutex);
|
|
|
|
// dequeue the next node from the head
|
|
struct event_queue_node *qn = next_event(q);
|
|
if (qn == NULL) {
|
|
// an event was cancelled while we were pending
|
|
thread_mutex_unlock(&q->mutex);
|
|
return;
|
|
}
|
|
|
|
if (q->head != NULL) {
|
|
// queue is non-empty: trigger ourselves again
|
|
// (note: event registrations are single shot)
|
|
struct event_closure self = {
|
|
.handler = event_queue_runner,
|
|
.arg = arg
|
|
};
|
|
err = waitset_chan_trigger_closure(q->waitset, &q->waitset_state, self);
|
|
assert(err_is_ok(err)); // shouldn't fail
|
|
}
|
|
|
|
qn->run = true;
|
|
thread_mutex_unlock(&q->mutex);
|
|
|
|
// run closure
|
|
qn->event.handler(qn->event.arg);
|
|
}
|
|
|
|
/**
|
|
* \brief Cancel the runner if mode is continuous and queue is empty.
|
|
*
|
|
* Must hold this event_queue's mutex.
|
|
*
|
|
* \param q The event queue
|
|
*/
|
|
static errval_t
|
|
event_queue_cancel_runner(struct event_queue *q)
|
|
{
|
|
errval_t err = SYS_ERR_OK;
|
|
|
|
if (q->head == NULL && q->mode == EVENT_QUEUE_CONTINUOUS) {
|
|
assert(q->tail == NULL);
|
|
err = waitset_chan_deregister(&q->waitset_state);
|
|
if (err_is_fail(err)) {
|
|
// can fail if the event already fired, but this is ok
|
|
if (err_no(err) == LIB_ERR_CHAN_NOT_REGISTERED) {
|
|
err = SYS_ERR_OK;
|
|
}
|
|
}
|
|
}
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
* \brief Add a new event to an event queue
|
|
*
|
|
* \param q Event queue
|
|
* \param qn Storage for queue node (uninitialised)
|
|
* \param event Event closure
|
|
*/
|
|
void event_queue_add(struct event_queue *q, struct event_queue_node *qn,
|
|
struct event_closure event)
|
|
{
|
|
errval_t err;
|
|
|
|
qn->event = event;
|
|
qn->run = false;
|
|
|
|
thread_mutex_lock(&q->mutex);
|
|
|
|
// enqueue at tail
|
|
if (q->tail == NULL) {
|
|
assert(q->head == NULL);
|
|
qn->next = qn->prev = NULL;
|
|
q->head = q->tail = qn;
|
|
|
|
// was empty: need to trigger queue runner if in continuous mode
|
|
if (q->mode == EVENT_QUEUE_CONTINUOUS) {
|
|
struct event_closure runner = {
|
|
.handler = event_queue_runner,
|
|
.arg = q
|
|
};
|
|
err = waitset_chan_trigger_closure(q->waitset, &q->waitset_state,
|
|
runner);
|
|
// apparently there's a situation when we dont really need to
|
|
// trigger the queue runner here, as we can get an
|
|
// LIB_ERR_CHAN_ALREADY REGISTERED error from the call.
|
|
// -SG, 2013-07-31
|
|
assert(err_is_ok(err) ||
|
|
err_no(err) == LIB_ERR_CHAN_ALREADY_REGISTERED);
|
|
}
|
|
} else {
|
|
assert(q->tail != qn); // don't re-enqueue the same node!
|
|
assert(q->tail->next == NULL);
|
|
q->tail->next = qn;
|
|
qn->prev = q->tail;
|
|
qn->next = NULL;
|
|
q->tail = qn;
|
|
|
|
// runner is already active if it needs to be, don't need to do anything
|
|
}
|
|
|
|
thread_mutex_unlock(&q->mutex);
|
|
}
|
|
|
|
/**
|
|
* \brief Cancel an event previously added to an event queue
|
|
*
|
|
* \param q Event queue
|
|
* \param qn Queue node which was previously added to #q by event_queue_add()
|
|
*/
|
|
errval_t event_queue_cancel(struct event_queue *q, struct event_queue_node *qn)
|
|
{
|
|
errval_t err = SYS_ERR_OK;
|
|
|
|
if (qn->run) {
|
|
return LIB_ERR_EVENT_ALREADY_RUN;
|
|
}
|
|
|
|
thread_mutex_lock(&q->mutex);
|
|
|
|
if (qn->run) {
|
|
thread_mutex_unlock(&q->mutex);
|
|
return LIB_ERR_EVENT_ALREADY_RUN;
|
|
}
|
|
|
|
// dequeue
|
|
if (qn->next == NULL) {
|
|
assert(q->tail == qn);
|
|
q->tail = qn->prev;
|
|
} else {
|
|
qn->next->prev = qn->prev;
|
|
}
|
|
|
|
if (qn->prev == NULL) {
|
|
assert(q->head == qn);
|
|
q->head = qn->next;
|
|
} else {
|
|
qn->prev->next = qn->next;
|
|
}
|
|
|
|
// if the queue is now empty, we should cancel the runner
|
|
err = event_queue_cancel_runner(q);
|
|
|
|
thread_mutex_unlock(&q->mutex);
|
|
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
* \brief Flush all pending events from the event queue.
|
|
*
|
|
* \param q Event queue.
|
|
*/
|
|
errval_t
|
|
event_queue_flush(struct event_queue *q) {
|
|
errval_t err = SYS_ERR_OK;
|
|
|
|
thread_mutex_lock(&q->mutex);
|
|
|
|
struct event_queue_node *qn;
|
|
do {
|
|
qn = next_event(q);
|
|
} while (qn);
|
|
|
|
err = event_queue_cancel_runner(q);
|
|
|
|
thread_mutex_unlock(&q->mutex);
|
|
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
* \brief Trigger the next event on a queue which is operating in one-shot mode
|
|
*
|
|
* Must not be called before the previously-triggered event has run.
|
|
*
|
|
* \param q Event queue
|
|
*/
|
|
errval_t event_queue_trigger(struct event_queue *q)
|
|
{
|
|
assert(q->mode == EVENT_QUEUE_ONESHOT);
|
|
|
|
thread_mutex_lock(&q->mutex);
|
|
|
|
struct event_queue_node *qn = next_event(q);
|
|
|
|
if (qn == NULL) {
|
|
thread_mutex_unlock(&q->mutex);
|
|
return LIB_ERR_EVENT_QUEUE_EMPTY;
|
|
}
|
|
|
|
qn->run = true;
|
|
thread_mutex_unlock(&q->mutex);
|
|
|
|
// trigger closure on waitset
|
|
return waitset_chan_trigger_closure(q->waitset, &q->waitset_state, qn->event);
|
|
}
|