| /* |
| * Copyright (c) 2022 Raspberry Pi (Trading) Ltd. |
| * |
| * SPDX-License-Identifier: BSD-3-Clause |
| */ |
| |
| /** \file pico/async_context.h |
| * \defgroup pico_async_context pico_async_context |
| * |
| * \brief An \ref async_context provides a logically single-threaded context for performing work, and responding |
| * to asynchronous events. Thus an async_context instance is suitable for servicing third-party libraries |
| * that are not re-entrant. |
| * |
| * The "context" in async_context refers to the fact that when calling workers or timeouts within the |
| * async_context various pre-conditions hold: |
| * |
| * <ol> |
| * <li>That there is a single logical thread of execution; i.e. that the context does not call any worker |
| * functions concurrently. |
| * <li>That the context always calls workers from the same processor core, as most uses of async_context rely on interaction |
| * with IRQs which are themselves core-specific. |
| * </ol> |
| * |
| * THe async_context provides two mechanisms for asynchronous work: |
| * |
| * * <em>when_pending</em> workers, which are processed whenever they have work pending. See \ref async_context_add_when_pending_worker, |
| * \ref async_context_remove_when_pending_worker, and \ref async_context_set_work_pending, the latter of which can be used from an interrupt handler |
| * to signal that servicing work is required to be performed by the worker from the regular async_context. |
| * * <em>at_time</em> workers, that are executed after at a specific time. |
| * |
| * Note: "when pending" workers with work pending are executed before "at time" workers. |
| * |
| * The async_context provides locking mechanisms, see \ref async_context_acquire_lock_blocking, |
| * \ref async_context_release_lock and \ref async_context_lock_check which can be used by |
| * external code to ensure execution of external code does not happen concurrently with worker code. |
| * Locked code runs on the calling core, however \ref async_context_execute_sync is provided to |
| * synchronously run a function from the core of the async_context. |
| * |
| * The SDK ships with the following default async_contexts: |
| * |
| * async_context_poll - this context is not thread-safe, and the user is responsible for calling |
| * \ref async_context_poll() periodically, and can use async_context_wait_for_work_until() to sleep |
| * between calls until work is needed if the user has nothing else to do. |
| * |
| * async_context_threadsafe_background - in order to work in the background, a low priority IRQ is used |
| * to handle callbacks. Code is usually invoked from this IRQ context, but may be invoked after any other code |
| * that uses the async context in another (non-IRQ) context on the same core. Calling \ref async_context_poll() is |
| * not required, and is a no-op. This context implements async_context locking and is thus safe to call |
| * from either core, according to the specific notes on each API. |
| * |
| * async_context_freertos - Work is performed from a separate "async_context" task, however once again, code may |
| * also be invoked after a direct use of the async_context on the same core that the async_context belongs to. Calling |
| * \ref async_context_poll() is not required, and is a no-op. This context implements async_context locking and is thus |
| * safe to call from any task, and from either core, according to the specific notes on each API. |
| * |
| * Each async_context provides bespoke methods of instantiation which are provided in the corresponding headers (e.g. |
| * async_context_poll.h, async_context_threadsafe_background.h, asycn_context_freertos.h). |
| * async_contexts are de-initialized by the common async_context_deint() method. |
| * |
| * Multiple async_context instances can be used by a single application, and they will operate independently. |
| */ |
| |
| #ifndef _PICO_ASYNC_CONTEXT_H |
| #define _PICO_ASYNC_CONTEXT_H |
| |
| #include "pico.h" |
| #include "pico/time.h" |
| |
| #ifdef __cplusplus |
| extern "C" { |
| #endif |
| |
| enum { |
| ASYNC_CONTEXT_POLL = 1, |
| ASYNC_CONTEXT_THREADSAFE_BACKGROUND = 2, |
| ASYNC_CONTEXT_FREERTOS = 3, |
| }; |
| |
| typedef struct async_context async_context_t; |
| |
| /*! \brief A "timeout" instance used by an async_context |
| * \ingroup pico_async_context |
| * |
| * A "timeout" represents some future action that must be taken at a specific time. |
| * Its methods are called from the async_context under lock at the given time |
| * |
| * \see async_context_add_worker_at |
| * \see async_context_add_worker_in_ms |
| */ |
| typedef struct async_work_on_timeout { |
| /*! |
| * \brief private link list pointer |
| */ |
| struct async_work_on_timeout *next; |
| /*! |
| * \brief Method called when the timeout is reached; may not be NULL |
| * |
| * Note, that when this method is called, the timeout has been removed from the async_context, so |
| * if you want the timeout to repeat, you should re-add it during this callback |
| * @param context |
| * @param timeout |
| */ |
| void (*do_work)(async_context_t *context, struct async_work_on_timeout *timeout); |
| /*! |
| * \brief The next timeout time; this should only be modified during the above methods |
| * or via async_context methods |
| */ |
| absolute_time_t next_time; |
| /*! |
| * \brief User data associated with the timeout instance |
| */ |
| void *user_data; |
| } async_at_time_worker_t; |
| |
| /*! \brief A "worker" instance used by an async_context |
| * \ingroup pico_async_context |
| * |
| * A "worker" represents some external entity that must do work in response |
| * to some external stimulus (usually an IRQ). |
| * Its methods are called from the async_context under lock at the given time |
| * |
| * \see async_context_add_worker_at |
| * \see async_context_add_worker_in_ms |
| */ |
| typedef struct async_when_pending_worker { |
| /*! |
| * \brief private link list pointer |
| */ |
| struct async_when_pending_worker *next; |
| /*! |
| * \brief Called by the async_context when the worker has been marked as having "work pending" |
| * |
| * @param context the async_context |
| * @param worker the function to be called when work is pending |
| */ |
| void (*do_work)(async_context_t *context, struct async_when_pending_worker *worker); |
| /** |
| * \brief True if the worker need do_work called |
| */ |
| bool work_pending; |
| /*! |
| * \brief User data associated with the worker instance |
| */ |
| void *user_data; |
| } async_when_pending_worker_t; |
| |
| #define ASYNC_CONTEXT_FLAG_CALLBACK_FROM_NON_IRQ 0x1 |
| #define ASYNC_CONTEXT_FLAG_CALLBACK_FROM_IRQ 0x2 |
| #define ASYNC_CONTEXT_FLAG_POLLED 0x4 |
| |
| /*! |
| * \brief Implementation of an async_context type, providing methods common to that type |
| * \ingroup pico_async_context |
| */ |
| typedef struct async_context_type { |
| uint16_t type; |
| // see wrapper functions for documentation |
| void (*acquire_lock_blocking)(async_context_t *self); |
| void (*release_lock)(async_context_t *self); |
| void (*lock_check)(async_context_t *self); |
| uint32_t (*execute_sync)(async_context_t *context, uint32_t (*func)(void *param), void *param); |
| bool (*add_at_time_worker)(async_context_t *self, async_at_time_worker_t *worker); |
| bool (*remove_at_time_worker)(async_context_t *self, async_at_time_worker_t *worker); |
| bool (*add_when_pending_worker)(async_context_t *self, async_when_pending_worker_t *worker); |
| bool (*remove_when_pending_worker)(async_context_t *self, async_when_pending_worker_t *worker); |
| void (*set_work_pending)(async_context_t *self, async_when_pending_worker_t *worker); |
| void (*poll)(async_context_t *self); // may be NULL |
| void (*wait_until)(async_context_t *self, absolute_time_t until); |
| void (*wait_for_work_until)(async_context_t *self, absolute_time_t until); |
| void (*deinit)(async_context_t *self); |
| } async_context_type_t; |
| |
| /*! |
| * \brief Base structure type of all async_contexts. For details about its use, see \ref pico_async_context. |
| * \ingroup pico_async_context |
| * |
| * Individual async_context_types with additional state, should contain this structure at the start. |
| */ |
| struct async_context { |
| const async_context_type_t *type; |
| async_when_pending_worker_t *when_pending_list; |
| async_at_time_worker_t *at_time_list; |
| absolute_time_t next_time; |
| uint16_t flags; |
| uint8_t core_num; |
| }; |
| |
| /*! |
| * \brief Acquire the async_context lock |
| * \ingroup pico_async_context |
| * |
| * The owner of the async_context lock is the logic owner of the async_context |
| * and other work related to this async_context will not happen concurrently. |
| * |
| * This method may be called in a nested fashion by the the lock owner. |
| * |
| * \note the async_context lock is nestable by the same caller, so an internal count is maintained |
| * |
| * \note for async_contexts that provide locking (not async_context_poll), this method is threadsafe. and may be called from within any |
| * worker method called by the async_context or from any other non-IRQ context. |
| * |
| * \param context the async_context |
| * |
| * \see async_context_release_lock |
| */ |
| static inline void async_context_acquire_lock_blocking(async_context_t *context) { |
| context->type->acquire_lock_blocking(context); |
| } |
| |
| /*! |
| * \brief Release the async_context lock |
| * \ingroup pico_async_context |
| * |
| * \note the async_context lock may be called in a nested fashion, so an internal count is maintained. On the outermost |
| * release, When the outermost lock is released, a check is made for work which might have been skipped while the lock was held, |
| * and any such work may be performed during this call IF the call is made from the same core that the async_context belongs to. |
| * |
| * \note for async_contexts that provide locking (not async_context_poll), this method is threadsafe. and may be called from within any |
| * worker method called by the async_context or from any other non-IRQ context. |
| * |
| * \param context the async_context |
| * |
| * \see async_context_acquire_lock_blocking |
| */ |
| static inline void async_context_release_lock(async_context_t *context) { |
| context->type->release_lock(context); |
| } |
| |
| /*! |
| * \brief Assert if the caller does not own the lock for the async_context |
| * \ingroup pico_async_context |
| * \note this method is thread-safe |
| * |
| * \param context the async_context |
| */ |
| static inline void async_context_lock_check(async_context_t *context) { |
| context->type->lock_check(context); |
| } |
| |
| /*! |
| * \brief Execute work synchronously on the core the async_context belongs to. |
| * \ingroup pico_async_context |
| * |
| * This method is intended for code external to the async_context (e.g. another thread/task) to |
| * execute a function with the same guarantees (single core, logical thread of execution) that |
| * async_context workers are called with. |
| * |
| * \note you should NOT call this method while holding the async_context's lock |
| * |
| * \param context the async_context |
| * \param func the function to call |
| * \param param the parameter to pass to the function |
| * \return the return value from func |
| */ |
| static inline uint32_t async_context_execute_sync(async_context_t *context, uint32_t (*func)(void *param), void *param) { |
| return context->type->execute_sync(context, func, param); |
| } |
| |
| /*! |
| * \brief Add an "at time" worker to a context |
| * \ingroup pico_async_context |
| * |
| * An "at time" worker will run at or after a specific point in time, and is automatically when (just before) it runs. |
| * |
| * The time to fire is specified in the next_time field of the worker. |
| * |
| * \note for async_contexts that provide locking (not async_context_poll), this method is threadsafe. and may be called from within any |
| * worker method called by the async_context or from any other non-IRQ context. |
| * |
| * \param context the async_context |
| * \param worker the "at time" worker to add |
| * \return true if the worker was added, false if the worker was already present. |
| */ |
| static inline bool async_context_add_at_time_worker(async_context_t *context, async_at_time_worker_t *worker) { |
| return context->type->add_at_time_worker(context, worker); |
| } |
| |
| /*! |
| * \brief Add an "at time" worker to a context |
| * \ingroup pico_async_context |
| * |
| * An "at time" worker will run at or after a specific point in time, and is automatically when (just before) it runs. |
| * |
| * The time to fire is specified by the at parameter. |
| * |
| * \note for async_contexts that provide locking (not async_context_poll), this method is threadsafe. and may be called from within any |
| * worker method called by the async_context or from any other non-IRQ context. |
| * |
| * \param context the async_context |
| * \param worker the "at time" worker to add |
| * \param at the time to fire at |
| * \return true if the worker was added, false if the worker was already present. |
| */ |
| static inline bool async_context_add_at_time_worker_at(async_context_t *context, async_at_time_worker_t *worker, absolute_time_t at) { |
| worker->next_time = at; |
| return context->type->add_at_time_worker(context, worker); |
| } |
| |
| /*! |
| * \brief Add an "at time" worker to a context |
| * \ingroup pico_async_context |
| * |
| * An "at time" worker will run at or after a specific point in time, and is automatically when (just before) it runs. |
| * |
| * The time to fire is specified by a delay via the ms parameter |
| * |
| * \note for async_contexts that provide locking (not async_context_poll), this method is threadsafe. and may be called from within any |
| * worker method called by the async_context or from any other non-IRQ context. |
| * |
| * \param context the async_context |
| * \param worker the "at time" worker to add |
| * \param ms the number of milliseconds from now to fire after |
| * \return true if the worker was added, false if the worker was already present. |
| */ |
| static inline bool async_context_add_at_time_worker_in_ms(async_context_t *context, async_at_time_worker_t *worker, uint32_t ms) { |
| worker->next_time = make_timeout_time_ms(ms); |
| return context->type->add_at_time_worker(context, worker); |
| } |
| |
| /*! |
| * \brief Remove an "at time" worker from a context |
| * \ingroup pico_async_context |
| * |
| * \note for async_contexts that provide locking (not async_context_poll), this method is threadsafe. and may be called from within any |
| * worker method called by the async_context or from any other non-IRQ context. |
| * |
| * \param context the async_context |
| * \param worker the "at time" worker to remove |
| * \return true if the worker was removed, false if the instance not present. |
| */ |
| static inline bool async_context_remove_at_time_worker(async_context_t *context, async_at_time_worker_t *worker) { |
| return context->type->remove_at_time_worker(context, worker); |
| } |
| |
| /*! |
| * \brief Add a "when pending" worker to a context |
| * \ingroup pico_async_context |
| * |
| * An "when pending" worker will run when it is pending (can be set via \ref async_context_set_work_pending), and |
| * is NOT automatically removed when it runs. |
| * |
| * The time to fire is specified by a delay via the ms parameter |
| * |
| * \note for async_contexts that provide locking (not async_context_poll), this method is threadsafe. and may be called from within any |
| * worker method called by the async_context or from any other non-IRQ context. |
| * |
| * \param context the async_context |
| * \param worker the "when pending" worker to add |
| * \return true if the worker was added, false if the worker was already present. |
| */ |
| static inline bool async_context_add_when_pending_worker(async_context_t *context, async_when_pending_worker_t *worker) { |
| return context->type->add_when_pending_worker(context, worker); |
| } |
| |
| /*! |
| * \brief Remove a "when pending" worker from a context |
| * \ingroup pico_async_context |
| * |
| * \note for async_contexts that provide locking (not async_context_poll), this method is threadsafe. and may be called from within any |
| * worker method called by the async_context or from any other non-IRQ context. |
| * |
| * \param context the async_context |
| * \param worker the "when pending" worker to remove |
| * \return true if the worker was removed, false if the instance not present. |
| */ |
| static inline bool async_context_remove_when_pending_worker(async_context_t *context, async_when_pending_worker_t *worker) { |
| return context->type->remove_when_pending_worker(context, worker); |
| } |
| |
| /*! |
| * \brief Mark a "when pending" worker as having work pending |
| * \ingroup pico_async_context |
| * |
| * The worker will be run from the async_context at a later time. |
| * |
| * \note this method may be called from any context including IRQs |
| * |
| * \param context the async_context |
| * \param worker the "when pending" worker to mark as pending. |
| */ |
| static inline void async_context_set_work_pending(async_context_t *context, async_when_pending_worker_t *worker) { |
| context->type->set_work_pending(context, worker); |
| } |
| |
| /*! |
| * \brief Perform any pending work for polling style async_context |
| * \ingroup pico_async_context |
| * |
| * For a polled async_context (e.g. \ref async_context_poll) the user is responsible for calling this method |
| * periodically to perform any required work. |
| * |
| * This method may immediately perform outstanding work on other context types, but is not required to. |
| * |
| * \param context the async_context |
| */ |
| static inline void async_context_poll(async_context_t *context) { |
| if (context->type->poll) context->type->poll(context); |
| } |
| |
| /*! |
| * \brief sleep until the specified time in an async_context callback safe way |
| * \ingroup pico_async_context |
| * |
| * \note for async_contexts that provide locking (not async_context_poll), this method is threadsafe. and may be called from within any |
| * worker method called by the async_context or from any other non-IRQ context. |
| * |
| * \param context the async_context |
| * \param until the time to sleep until |
| */ |
| static inline void async_context_wait_until(async_context_t *context, absolute_time_t until) { |
| context->type->wait_until(context, until); |
| } |
| |
| /*! |
| * \brief Block until work needs to be done or the specified time has been reached |
| * \ingroup pico_async_context |
| * |
| * \note this method should not be called from a worker callback |
| * |
| * \param context the async_context |
| * \param until the time to return at if no work is required |
| */ |
| static inline void async_context_wait_for_work_until(async_context_t *context, absolute_time_t until) { |
| context->type->wait_for_work_until(context, until); |
| } |
| |
| /*! |
| * \brief Block until work needs to be done or the specified number of milliseconds have passed |
| * \ingroup pico_async_context |
| * |
| * \note this method should not be called from a worker callback |
| * |
| * \param context the async_context |
| * \param ms the number of milliseconds to return after if no work is required |
| */ |
| static inline void async_context_wait_for_work_ms(async_context_t *context, uint32_t ms) { |
| async_context_wait_for_work_until(context, make_timeout_time_ms(ms)); |
| } |
| |
| /*! |
| * \brief Return the processor core this async_context belongs to |
| * \ingroup pico_async_context |
| * |
| * \param context the async_context |
| * \return the physical core number |
| */ |
| static inline uint async_context_core_num(const async_context_t *context) { |
| return context->core_num; |
| } |
| |
| /*! |
| * \brief End async_context processing, and free any resources |
| * \ingroup pico_async_context |
| * |
| * Note the user should clean up any resources associated with workers |
| * in the async_context themselves. |
| * |
| * Asynchronous (non-polled) async_contexts guarantee that no |
| * callback is being called once this method returns. |
| * |
| * \param context the async_context |
| */ |
| static inline void async_context_deinit(async_context_t *context) { |
| context->type->deinit(context); |
| } |
| |
| #ifdef __cplusplus |
| } |
| #endif |
| |
| #endif |