blob: 6f7c4f0dcd3c460e0e9008e693d6a8bc2b654c0a [file] [log] [blame]
/*
* 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