diff --git a/include/kernel.h b/include/kernel.h index 3e14ef2bfa1..dd42117e123 100644 --- a/include/kernel.h +++ b/include/kernel.h @@ -96,6 +96,15 @@ typedef sys_dlist_t _wait_q_t; #define _OBJECT_TRACING_NEXT_PTR(type) #endif +#ifdef CONFIG_POLL +#define _POLL_EVENT_OBJ_INIT \ + .poll_event = NULL, +#define _POLL_EVENT struct k_poll_event *poll_event +#else +#define _POLL_EVENT_OBJ_INIT +#define _POLL_EVENT +#endif + #define tcs k_thread struct k_thread; struct k_mutex; @@ -110,6 +119,8 @@ struct k_stack; struct k_mem_slab; struct k_mem_pool; struct k_timer; +struct k_poll_event; +struct k_poll_signal; typedef struct k_thread *k_tid_t; @@ -1093,6 +1104,7 @@ extern uint32_t k_cycle_get_32(void); struct k_fifo { _wait_q_t wait_q; sys_slist_t data_q; + _POLL_EVENT; _OBJECT_TRACING_NEXT_PTR(k_fifo); }; @@ -1101,6 +1113,7 @@ struct k_fifo { { \ .wait_q = SYS_DLIST_STATIC_INIT(&obj.wait_q), \ .data_q = SYS_SLIST_STATIC_INIT(&obj.data_q), \ + _POLL_EVENT_OBJ_INIT \ _OBJECT_TRACING_INIT \ } @@ -1834,6 +1847,7 @@ struct k_sem { _wait_q_t wait_q; unsigned int count; unsigned int limit; + _POLL_EVENT; _OBJECT_TRACING_NEXT_PTR(k_sem); }; @@ -1843,6 +1857,7 @@ struct k_sem { .wait_q = SYS_DLIST_STATIC_INIT(&obj.wait_q), \ .count = initial_count, \ .limit = count_limit, \ + _POLL_EVENT_OBJ_INIT \ _OBJECT_TRACING_INIT \ } @@ -3087,6 +3102,245 @@ extern void k_free(void *ptr); * @} end defgroup heap_apis */ +/* polling API - PRIVATE */ + +/* private - implementation data created as needed, per-type */ +struct _poller { + struct k_thread *thread; +}; + +/* private - types bit positions */ +enum _poll_types_bits { + /* can be used to ignore an event */ + _POLL_TYPE_IGNORE, + + /* to be signaled by k_poll_signal() */ + _POLL_TYPE_SIGNAL, + + /* semaphore availability */ + _POLL_TYPE_SEM_AVAILABLE, + + /* fifo data availability */ + _POLL_TYPE_FIFO_DATA_AVAILABLE, + + _POLL_NUM_TYPES +}; + +#define _POLL_TYPE_BIT(type) (1 << ((type) - 1)) + +/* private - states bit positions */ +enum _poll_states_bits { + /* default state when creating event */ + _POLL_STATE_NOT_READY, + + /* there was another poller already on the object */ + _POLL_STATE_EADDRINUSE, + + /* signaled by k_poll_signal() */ + _POLL_STATE_SIGNALED, + + /* semaphore is available */ + _POLL_STATE_SEM_AVAILABLE, + + /* data is available to read on fifo */ + _POLL_STATE_FIFO_DATA_AVAILABLE, + + _POLL_NUM_STATES +}; + +#define _POLL_STATE_BIT(state) (1 << ((state) - 1)) + +#define _POLL_EVENT_NUM_UNUSED_BITS \ + (32 - (_POLL_NUM_TYPES + _POLL_NUM_STATES + 1 /* modes */)) + +#if _POLL_EVENT_NUM_UNUSED_BITS < 0 +#error overflow of 32-bit word in struct k_poll_event +#endif + +/* end of polling API - PRIVATE */ + + +/** + * @defgroup poll_apis Async polling APIs + * @ingroup kernel_apis + * @{ + */ + +/* Public polling API */ + +/* public - values for k_poll_event.type bitfield */ +#define K_POLL_TYPE_IGNORE 0 +#define K_POLL_TYPE_SIGNAL _POLL_TYPE_BIT(_POLL_TYPE_SIGNAL) +#define K_POLL_TYPE_SEM_AVAILABLE _POLL_TYPE_BIT(_POLL_TYPE_SEM_AVAILABLE) +#define K_POLL_TYPE_FIFO_DATA_AVAILABLE \ + _POLL_TYPE_BIT(_POLL_TYPE_FIFO_DATA_AVAILABLE) + +/* public - polling modes */ +enum k_poll_modes { + /* polling thread does not take ownership of objects when available */ + K_POLL_MODE_NOTIFY_ONLY = 0, + + K_POLL_NUM_MODES +}; + +/* public - values for k_poll_event.state bitfield */ +#define K_POLL_STATE_NOT_READY 0 +#define K_POLL_STATE_EADDRINUSE _POLL_STATE_BIT(_POLL_STATE_EADDRINUSE) +#define K_POLL_STATE_SIGNALED _POLL_STATE_BIT(_POLL_STATE_SIGNALED) +#define K_POLL_STATE_SEM_AVAILABLE _POLL_STATE_BIT(_POLL_STATE_SEM_AVAILABLE) +#define K_POLL_STATE_FIFO_DATA_AVAILABLE \ + _POLL_STATE_BIT(_POLL_STATE_FIFO_DATA_AVAILABLE) + +/* public - poll signal object */ +struct k_poll_signal { + /* PRIVATE - DO NOT TOUCH */ + struct k_poll_event *poll_event; + + /* + * 1 if the event has been signaled, 0 otherwise. Stays set to 1 until + * user resets it to 0. + */ + unsigned int signaled; + + /* custom result value passed to k_poll_signal() if needed */ + int result; +}; + +#define K_POLL_SIGNAL_INITIALIZER() \ + { \ + .poll_event = NULL, \ + .signaled = 0, \ + .result = 0, \ + } + +struct k_poll_event { + /* PRIVATE - DO NOT TOUCH */ + struct _poller *poller; + + /* bitfield of event types (bitwise-ORed K_POLL_TYPE_xxx values) */ + uint32_t type:_POLL_NUM_TYPES; + + /* bitfield of event states (bitwise-ORed K_POLL_STATE_xxx values) */ + uint32_t state:_POLL_NUM_STATES; + + /* mode of operation, from enum k_poll_modes */ + uint32_t mode:1; + + /* unused bits in 32-bit word */ + uint32_t unused:_POLL_EVENT_NUM_UNUSED_BITS; + + /* per-type data */ + union { + void *obj; + struct k_poll_signal *signal; + struct k_sem *sem; + struct k_fifo *fifo; + }; +}; + +#define K_POLL_EVENT_INITIALIZER(event_type, event_mode, event_data) \ + { \ + .poller = NULL, \ + .type = event_type, \ + .state = K_POLL_STATE_NOT_READY, \ + .mode = event_mode, \ + .unused = 0, \ + { .obj = event_data }, \ + } + +/** + * @brief Initialize one struct k_poll_event instance + * + * After this routine is called on a poll event, the event it ready to be + * placed in an event array to be passed to k_poll(). + * + * @param event The event to initialize. + * @param type A bitfield of the types of event, from the K_POLL_TYPE_xxx + * values. Only values that apply to the same object being polled + * can be used together. Choosing K_POLL_TYPE_IGNORE disables the + * event. + * @param mode Future. Use K_POLL_MODE_INFORM_ONLY. + * @param obj Kernel object or poll signal. + * + * @return N/A + */ + +extern void k_poll_event_init(struct k_poll_event *event, uint32_t type, + int mode, void *obj); + +/** + * @brief Wait for one or many of multiple poll events to occur + * + * This routine allows a thread to wait concurrently for one or many of + * multiple poll events to have occurred. Such events can be a kernel object + * being available, like a semaphore, or a poll signal event. + * + * When an event notifies that a kernel object is available, the kernel object + * is not "given" to the thread calling k_poll(): it merely signals the fact + * that the object was available when the k_poll() call was in effect. Also, + * all threads trying to acquire an object the regular way, i.e. by pending on + * the object, have precedence over the thread polling on the object. This + * means that the polling thread will never get the poll event on an object + * until the object becomes available and its pend queue is empty. For this + * reason, the k_poll() call is more effective when the objects being polled + * only have one thread, the polling thread, trying to acquire them. + * + * Only one thread can be polling for a particular object at a given time. If + * another thread tries to poll on it, the k_poll() call returns -EADDRINUSE + * and returns as soon as it has finished handling the other events. This means + * that k_poll() can return -EADDRINUSE and have the state value of some events + * be non-K_POLL_STATE_NOT_READY. When this condition occurs, the @a timeout + * parameter is ignored. + * + * When k_poll() returns 0 or -EADDRINUSE, the caller should loop on all the + * events that were passed to k_poll() and check the state field for the values + * that were expected and take the associated actions. + * + * Before being reused for another call to k_poll(), the user has to reset the + * state field to K_POLL_STATE_NOT_READY. + * + * @param events An array of pointers to events to be polled for. + * @param num_events The number of events in the array. + * @param timeout Waiting period for an event to be ready (in milliseconds), + * or one of the special values K_NO_WAIT and K_FOREVER. + * + * @retval 0 One or more events are ready. + * @retval -EADDRINUSE One or more objects already had a poller. + * @retval -EAGAIN Waiting period timed out. + */ + +extern int k_poll(struct k_poll_event *events, int num_events, + int32_t timeout); + +/** + * @brief Signal a poll signal object. + * + * This routine makes ready a poll signal, which is basically a poll event of + * type K_POLL_TYPE_SIGNAL. If a thread was polling on that event, it will be + * made ready to run. A @a result value can be specified. + * + * The poll signal contains a 'signaled' field that, when set by + * k_poll_signal(), stays set until the user sets it back to 0. It thus has to + * be reset by the user before being passed again to k_poll() or k_poll() will + * consider it being signaled, and will return immediately. + * + * @param signal A poll signal. + * @param result The value to store in the result field of the signal. + * + * @retval 0 The signal was delivered successfully. + * @retval -EAGAIN The polling thread's timeout is in the process of expiring. + */ + +extern int k_poll_signal(struct k_poll_signal *signal, int result); + +/* private internal function */ +extern int _handle_obj_poll_event(struct k_poll_event **obj_poll_event, + uint32_t state); + +/** + * @} end defgroup poll_apis + */ + /** * @brief Make the CPU idle. * diff --git a/kernel/Kconfig b/kernel/Kconfig index a556da8ed73..59207dbedd3 100644 --- a/kernel/Kconfig +++ b/kernel/Kconfig @@ -289,6 +289,16 @@ config TIMESLICE_PRIORITY takes effect; threads having a higher priority than this ceiling are not subject to time slicing. +config POLL + bool + prompt "async I/O framework" + default n + help + Asynchronous notification framework. Enable the k_poll() and + k_poll_signal() APIs. The former can wait on multiple events + concurrently, which can be either directly triggered or triggered by + the availability of some kernel objects (semaphores and fifos). + endmenu menu "Other Kernel Object Options" diff --git a/kernel/Makefile b/kernel/Makefile index d0c8deb3afd..9374b119b60 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -39,3 +39,4 @@ lib-$(CONFIG_STACK_CANARIES) += compiler_stack_protect.o lib-$(CONFIG_SYS_CLOCK_EXISTS) += timer.o lib-$(CONFIG_LEGACY_KERNEL) += legacy_timer.o lib-$(CONFIG_ATOMIC_OPERATIONS_C) += atomic_c.o +lib-$(CONFIG_POLL) += poll.o diff --git a/kernel/fifo.c b/kernel/fifo.c index a820ac2b1b4..1228a4c3942 100644 --- a/kernel/fifo.c +++ b/kernel/fifo.c @@ -62,6 +62,19 @@ static void prepare_thread_to_run(struct k_thread *thread, void *data) _set_thread_return_value_with_data(thread, 0, data); } +/* returns 1 if a reschedule must take place, 0 otherwise */ +static inline int handle_poll_event(struct k_fifo *fifo) +{ +#ifdef CONFIG_POLL + uint32_t state = K_POLL_STATE_FIFO_DATA_AVAILABLE; + + return fifo->poll_event ? + _handle_obj_poll_event(&fifo->poll_event, state) : 0; +#else + return 0; +#endif +} + void k_fifo_put(struct k_fifo *fifo, void *data) { struct k_thread *first_pending_thread; @@ -79,6 +92,10 @@ void k_fifo_put(struct k_fifo *fifo, void *data) } } else { sys_slist_append(&fifo->data_q, data); + if (handle_poll_event(fifo)) { + (void)_Swap(key); + return; + } } irq_unlock(key); diff --git a/kernel/poll.c b/kernel/poll.c new file mode 100644 index 00000000000..8b5af2ef69b --- /dev/null +++ b/kernel/poll.c @@ -0,0 +1,342 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include + +void k_poll_event_init(struct k_poll_event *event, uint32_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->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(struct k_thread *thread) +{ + return _is_thread_polling(thread); +} + +/* must be called with interrupts locked */ +static inline int is_condition_met(struct k_poll_event *event, uint32_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_FIFO_DATA_AVAILABLE: + if (!k_fifo_is_empty(event->fifo)) { + *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_FIFO_DATA_AVAILABLE: + __ASSERT(event->fifo, "invalid fifo\n"); + if (event->fifo->poll_event) { + return -EADDRINUSE; + } + event->fifo->poll_event = event; + break; + case K_POLL_TYPE_SIGNAL: + __ASSERT(event->fifo, "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_FIFO_DATA_AVAILABLE: + __ASSERT(event->fifo, "invalid fifo\n"); + event->fifo->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, uint32_t state) +{ + event->poller = NULL; + event->state |= state; +} + +int k_poll(struct k_poll_event *events, int num_events, int32_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++) { + uint32_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 && !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(_current)) { + 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, uint32_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, uint32_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; +} + +int k_poll_signal(struct k_poll_signal *signal, int result) +{ + unsigned int key = irq_lock(); + int must_reschedule; + + signal->result = result; + + if (!signal->poll_event) { + signal->signaled = 1; + 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; +} diff --git a/kernel/sem.c b/kernel/sem.c index fa41afcebd4..75a42d9adaa 100644 --- a/kernel/sem.c +++ b/kernel/sem.c @@ -231,6 +231,19 @@ static int handle_sem_group(struct k_sem *sem, struct k_thread *thread) #define handle_sem_group(sem, thread) 0 #endif +/* returns 1 if a reschedule must take place, 0 otherwise */ +static inline int handle_poll_event(struct k_sem *sem) +{ +#ifdef CONFIG_POLL + uint32_t state = K_POLL_STATE_SEM_AVAILABLE; + + return sem->poll_event ? + _handle_obj_poll_event(&sem->poll_event, state) : 0; +#else + return 0; +#endif +} + /** * @brief Common semaphore give code * @@ -248,7 +261,8 @@ static bool sem_give_common(struct k_sem *sem) * its limit has already been reached. */ sem->count += (sem->count != sem->limit); - return false; + + return handle_poll_event(sem); } _abort_thread_timeout(thread);