Boot Linux faster!

Check our new training course

Boot Linux faster!

Check our new training course
and Creative Commons CC-BY-SA
lecture and lab materials

Bootlin logo

Elixir Cross Referencer

/*
 * Copyright (c) 2017 Wind River Systems, Inc.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @file
 *
 * @brief Kernel asynchronous event polling interface.
 *
 * This polling mechanism allows waiting on multiple events concurrently,
 * either events triggered directly, or from kernel objects or other kernel
 * constructs.
 */

#include <kernel.h>
#include <kernel_structs.h>
#include <wait_q.h>
#include <ksched.h>
#include <misc/slist.h>
#include <misc/dlist.h>
#include <misc/__assert.h>

void k_poll_event_init(struct k_poll_event *event, u32_t type,
		       int mode, void *obj)
{
	__ASSERT(mode == K_POLL_MODE_NOTIFY_ONLY,
		 "only NOTIFY_ONLY mode is supported\n");
	__ASSERT(type < (1 << _POLL_NUM_TYPES), "invalid type\n");
	__ASSERT(obj, "must provide an object\n");

	event->poller = NULL;
	/* event->tag is left uninitialized: the user will set it if needed */
	event->type = type;
	event->state = K_POLL_STATE_NOT_READY;
	event->mode = mode;
	event->unused = 0;
	event->obj = obj;
}

/* must be called with interrupts locked */
static inline void set_polling_state(struct k_thread *thread)
{
	_mark_thread_as_polling(thread);
}

/* must be called with interrupts locked */
static inline void clear_polling_state(struct k_thread *thread)
{
	_mark_thread_as_not_polling(thread);
}

/* must be called with interrupts locked */
static inline int is_polling(void)
{
	return _is_thread_polling(_current);
}

/* must be called with interrupts locked */
static inline int is_condition_met(struct k_poll_event *event, u32_t *state)
{
	switch (event->type) {
	case K_POLL_TYPE_SEM_AVAILABLE:
		if (k_sem_count_get(event->sem) > 0) {
			*state = K_POLL_STATE_SEM_AVAILABLE;
			return 1;
		}
		break;
	case K_POLL_TYPE_DATA_AVAILABLE:
		if (!k_queue_is_empty(event->queue)) {
			*state = K_POLL_STATE_FIFO_DATA_AVAILABLE;
			return 1;
		}
		break;
	case K_POLL_TYPE_SIGNAL:
		if (event->signal->signaled) {
			*state = K_POLL_STATE_SIGNALED;
			return 1;
		}
		break;
	case K_POLL_TYPE_IGNORE:
		return 0;
	default:
		__ASSERT(0, "invalid event type (0x%x)\n", event->type);
		break;
	}

	return 0;
}

/* must be called with interrupts locked */
static inline int register_event(struct k_poll_event *event)
{
	switch (event->type) {
	case K_POLL_TYPE_SEM_AVAILABLE:
		__ASSERT(event->sem, "invalid semaphore\n");
		if (event->sem->poll_event) {
			return -EADDRINUSE;
		}
		event->sem->poll_event = event;
		break;
	case K_POLL_TYPE_DATA_AVAILABLE:
		__ASSERT(event->queue, "invalid queue\n");
		if (event->queue->poll_event) {
			return -EADDRINUSE;
		}
		event->queue->poll_event = event;
		break;
	case K_POLL_TYPE_SIGNAL:
		__ASSERT(event->queue, "invalid poll signal\n");
		if (event->signal->poll_event) {
			return -EADDRINUSE;
		}
		event->signal->poll_event = event;
		break;
	case K_POLL_TYPE_IGNORE:
		/* nothing to do */
		break;
	default:
		__ASSERT(0, "invalid event type\n");
		break;
	}

	return 0;
}

/* must be called with interrupts locked */
static inline void clear_event_registration(struct k_poll_event *event)
{
	event->poller = NULL;

	switch (event->type) {
	case K_POLL_TYPE_SEM_AVAILABLE:
		__ASSERT(event->sem, "invalid semaphore\n");
		event->sem->poll_event = NULL;
		break;
	case K_POLL_TYPE_DATA_AVAILABLE:
		__ASSERT(event->queue, "invalid queue\n");
		event->queue->poll_event = NULL;
		break;
	case K_POLL_TYPE_SIGNAL:
		__ASSERT(event->signal, "invalid poll signal\n");
		event->signal->poll_event = NULL;
		break;
	case K_POLL_TYPE_IGNORE:
		/* nothing to do */
		break;
	default:
		__ASSERT(0, "invalid event type\n");
		break;
	}
}

/* must be called with interrupts locked */
static inline void clear_event_registrations(struct k_poll_event *events,
					      int last_registered,
					      unsigned int key)
{
	for (; last_registered >= 0; last_registered--) {
		clear_event_registration(&events[last_registered]);
		irq_unlock(key);
		key = irq_lock();
	}
}

static inline void set_event_ready(struct k_poll_event *event, u32_t state)
{
	event->poller = NULL;
	event->state |= state;
}

int k_poll(struct k_poll_event *events, int num_events, s32_t timeout)
{
	__ASSERT(!_is_in_isr(), "");
	__ASSERT(events, "NULL events\n");
	__ASSERT(num_events > 0, "zero events\n");

	int last_registered = -1, in_use = 0, rc;
	unsigned int key;

	key = irq_lock();
	set_polling_state(_current);
	irq_unlock(key);

	/*
	 * We can get by with one poller structure for all events for now:
	 * if/when we allow multiple threads to poll on the same object, we
	 * will need one per poll event associated with an object.
	 */
	struct _poller poller = { .thread = _current };

	/* find events whose condition is already fulfilled */
	for (int ii = 0; ii < num_events; ii++) {
		u32_t state;

		key = irq_lock();
		if (is_condition_met(&events[ii], &state)) {
			set_event_ready(&events[ii], state);
			clear_polling_state(_current);
		} else if (timeout != K_NO_WAIT && is_polling() && !in_use) {
			rc = register_event(&events[ii]);
			if (rc == 0) {
				events[ii].poller = &poller;
				++last_registered;
			} else if (rc == -EADDRINUSE) {
				/* setting in_use also prevents any further
				 * registrations by the current thread
				 */
				in_use = -EADDRINUSE;
				events[ii].state = K_POLL_STATE_EADDRINUSE;
				clear_polling_state(_current);
			} else {
				__ASSERT(0, "unexpected return code\n");
			}
		}
		irq_unlock(key);
	}

	key = irq_lock();

	/*
	 * If we're not polling anymore, it means that at least one event
	 * condition is met, either when looping through the events here or
	 * because one of the events registered has had its state changed, or
	 * that one of the objects we wanted to poll on already had a thread
	 * polling on it. We can remove all registrations and return either
	 * success or a -EADDRINUSE error. In the case of a -EADDRINUSE error,
	 * the events that were available are still flagged as such, and it is
	 * valid for the caller to consider them available, as if this function
	 * returned success.
	 */
	if (!is_polling()) {
		clear_event_registrations(events, last_registered, key);
		irq_unlock(key);
		return in_use;
	}

	clear_polling_state(_current);

	if (timeout == K_NO_WAIT) {
		irq_unlock(key);
		return -EAGAIN;
	}

	_wait_q_t wait_q = _WAIT_Q_INIT(&wait_q);

	_pend_current_thread(&wait_q, timeout);

	int swap_rc = _Swap(key);

	/*
	 * Clear all event registrations. If events happen while we're in this
	 * loop, and we already had one that triggered, that's OK: they will
	 * end up in the list of events that are ready; if we timed out, and
	 * events happen while we're in this loop, that is OK as well since
	 * we've already know the return code (-EAGAIN), and even if they are
	 * added to the list of events that occurred, the user has to check the
	 * return code first, which invalidates the whole list of event states.
	 */
	key = irq_lock();
	clear_event_registrations(events, last_registered, key);
	irq_unlock(key);

	return swap_rc;
}

/* must be called with interrupts locked */
static int _signal_poll_event(struct k_poll_event *event, u32_t state,
			      int *must_reschedule)
{
	*must_reschedule = 0;

	if (!event->poller) {
		goto ready_event;
	}

	struct k_thread *thread = event->poller->thread;

	__ASSERT(event->poller->thread, "poller should have a thread\n");

	clear_polling_state(thread);

	if (!_is_thread_pending(thread)) {
		goto ready_event;
	}

	if (_is_thread_timeout_expired(thread)) {
		return -EAGAIN;
	}

	_unpend_thread(thread);
	_abort_thread_timeout(thread);
	_set_thread_return_value(thread, 0);

	if (!_is_thread_ready(thread)) {
		goto ready_event;
	}

	_add_thread_to_ready_q(thread);
	*must_reschedule = !_is_in_isr() && _must_switch_threads();

ready_event:
	set_event_ready(event, state);
	return 0;
}

/* returns 1 if a reschedule must take place, 0 otherwise */
/* *obj_poll_event is guaranteed to not be NULL */
int _handle_obj_poll_event(struct k_poll_event **obj_poll_event, u32_t state)
{
	struct k_poll_event *poll_event = *obj_poll_event;
	int must_reschedule;

	*obj_poll_event = NULL;
	(void)_signal_poll_event(poll_event, state, &must_reschedule);
	return must_reschedule;
}

void k_poll_signal_init(struct k_poll_signal *signal)
{
	signal->poll_event = NULL;
	signal->signaled = 0;
	/* signal->result is left unitialized */
}

int k_poll_signal(struct k_poll_signal *signal, int result)
{
	unsigned int key = irq_lock();
	int must_reschedule;

	signal->result = result;
	signal->signaled = 1;

	if (!signal->poll_event) {
		irq_unlock(key);
		return 0;
	}

	int rc = _signal_poll_event(signal->poll_event, K_POLL_STATE_SIGNALED,
				    &must_reschedule);

	if (must_reschedule) {
		(void)_Swap(key);
	} else {
		irq_unlock(key);
	}

	return rc;
}