1 /*
2  * Copyright (c) 2022 Raspberry Pi (Trading) Ltd.
3  *
4  * SPDX-License-Identifier: BSD-3-Clause
5  */
6 
7 /** \file pico/async_context.h
8  *  \defgroup pico_async_context pico_async_context
9  *
10  * \brief An \ref async_context provides a logically single-threaded context for performing work, and responding
11  * to asynchronous events. Thus an async_context instance is suitable for servicing third-party libraries
12  * that are not re-entrant.
13  *
14  * The "context" in async_context refers to the fact that when calling workers or timeouts within the
15  * async_context various pre-conditions hold:
16  *
17  * <ol>
18  * <li>That there is a single logical thread of execution; i.e. that the context does not call any worker
19  * functions concurrently.
20  * <li>That the context always calls workers from the same processor core, as most uses of async_context rely on interaction
21  * with IRQs which are themselves core-specific.
22  * </ol>
23  *
24  * THe async_context provides two mechanisms for asynchronous work:
25  *
26  * * <em>when_pending</em> workers, which are processed whenever they have work pending. See \ref async_context_add_when_pending_worker,
27  * \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
28  * to signal that servicing work is required to be performed by the worker from the regular async_context.
29  * * <em>at_time</em> workers, that are executed after at a specific time.
30  *
31  * Note: "when pending" workers with work pending are executed before "at time" workers.
32  *
33  * The async_context provides locking mechanisms, see \ref async_context_acquire_lock_blocking,
34  * \ref async_context_release_lock and \ref async_context_lock_check which can be used by
35  * external code to ensure execution of external code does not happen concurrently with worker code.
36  * Locked code runs on the calling core, however \ref async_context_execute_sync is provided to
37  * synchronously run a function from the core of the async_context.
38  *
39  * The SDK ships with the following default async_contexts:
40  *
41  * async_context_poll - this context is not thread-safe, and the user is responsible for calling
42  * \ref async_context_poll() periodically, and can use async_context_wait_for_work_until() to sleep
43  * between calls until work is needed if the user has nothing else to do.
44  *
45  * async_context_threadsafe_background - in order to work in the background, a low priority IRQ is used
46  * to handle callbacks. Code is usually invoked from this IRQ context, but may be invoked after any other code
47  * that uses the async context in another (non-IRQ) context on the same core. Calling \ref async_context_poll() is
48  * not required, and is a no-op. This context implements async_context locking and is thus safe to call
49  * from either core, according to the specific notes on each API.
50  *
51  * async_context_freertos - Work is performed from a separate "async_context" task, however once again, code may
52  * also be invoked after a direct use of the async_context on the same core that the async_context belongs to. Calling
53  * \ref async_context_poll() is not required, and is a no-op. This context implements async_context locking and is thus
54  * safe to call from any task, and from either core, according to the specific notes on each API.
55  *
56  * Each async_context provides bespoke methods of instantiation which are provided in the corresponding headers (e.g.
57  * async_context_poll.h, async_context_threadsafe_background.h, asycn_context_freertos.h).
58  * async_contexts are de-initialized by the common async_context_deint() method.
59  *
60  * Multiple async_context instances can be used by a single application, and they will operate independently.
61  */
62 
63 #ifndef _PICO_ASYNC_CONTEXT_H
64 #define _PICO_ASYNC_CONTEXT_H
65 
66 #include "pico.h"
67 #include "pico/time.h"
68 
69 #ifdef __cplusplus
70 extern "C" {
71 #endif
72 
73 enum {
74     ASYNC_CONTEXT_POLL = 1,
75     ASYNC_CONTEXT_THREADSAFE_BACKGROUND = 2,
76     ASYNC_CONTEXT_FREERTOS = 3,
77 };
78 
79 typedef struct async_context async_context_t;
80 
81 /*! \brief A "timeout" instance used by an async_context
82  *  \ingroup pico_async_context
83  *
84  *  A "timeout" represents some future action that must be taken at a specific time.
85  *  Its methods are called from the async_context under lock at the given time
86  *
87  * \see async_context_add_worker_at
88  * \see async_context_add_worker_in_ms
89  */
90 typedef struct async_work_on_timeout {
91     /*!
92      * \brief private link list pointer
93      */
94     struct async_work_on_timeout *next;
95     /*!
96      * \brief Method called when the timeout is reached; may not be NULL
97      *
98      * Note, that when this method is called, the timeout has been removed from the async_context, so
99      * if you want the timeout to repeat, you should re-add it during this callback
100      * @param context
101      * @param timeout
102      */
103     void (*do_work)(async_context_t *context, struct async_work_on_timeout *timeout);
104     /*!
105      * \brief The next timeout time; this should only be modified during the above methods
106      * or via async_context methods
107      */
108     absolute_time_t next_time;
109     /*!
110      * \brief User data associated with the timeout instance
111      */
112     void *user_data;
113 } async_at_time_worker_t;
114 
115 /*! \brief A "worker" instance used by an async_context
116  *  \ingroup pico_async_context
117  *
118  *  A "worker" represents some external entity that must do work in response
119  *  to some external stimulus (usually an IRQ).
120  *  Its methods are called from the async_context under lock at the given time
121  *
122  * \see async_context_add_worker_at
123  * \see async_context_add_worker_in_ms
124  */
125 typedef struct async_when_pending_worker {
126     /*!
127      * \brief private link list pointer
128      */
129     struct async_when_pending_worker *next;
130     /*!
131      * \brief Called by the async_context when the worker has been marked as having "work pending"
132      *
133      * @param context the async_context
134      * @param worker the function to be called when work is pending
135      */
136     void (*do_work)(async_context_t *context, struct async_when_pending_worker *worker);
137     /**
138      * \brief True if the worker need do_work called
139      */
140     bool work_pending;
141     /*!
142      * \brief User data associated with the worker instance
143      */
144     void *user_data;
145 } async_when_pending_worker_t;
146 
147 #define ASYNC_CONTEXT_FLAG_CALLBACK_FROM_NON_IRQ 0x1
148 #define ASYNC_CONTEXT_FLAG_CALLBACK_FROM_IRQ 0x2
149 #define ASYNC_CONTEXT_FLAG_POLLED 0x4
150 
151 /*!
152  * \brief Implementation of an async_context type, providing methods common to that type
153  * \ingroup pico_async_context
154  */
155 typedef struct async_context_type {
156     uint16_t type;
157     // see wrapper functions for documentation
158     void (*acquire_lock_blocking)(async_context_t *self);
159     void (*release_lock)(async_context_t *self);
160     void (*lock_check)(async_context_t *self);
161     uint32_t (*execute_sync)(async_context_t *context, uint32_t (*func)(void *param), void *param);
162     bool (*add_at_time_worker)(async_context_t *self, async_at_time_worker_t *worker);
163     bool (*remove_at_time_worker)(async_context_t *self, async_at_time_worker_t *worker);
164     bool (*add_when_pending_worker)(async_context_t *self, async_when_pending_worker_t *worker);
165     bool (*remove_when_pending_worker)(async_context_t *self, async_when_pending_worker_t *worker);
166     void (*set_work_pending)(async_context_t *self, async_when_pending_worker_t *worker);
167     void (*poll)(async_context_t *self); // may be NULL
168     void (*wait_until)(async_context_t *self, absolute_time_t until);
169     void (*wait_for_work_until)(async_context_t *self, absolute_time_t until);
170     void (*deinit)(async_context_t *self);
171 } async_context_type_t;
172 
173 /*!
174  * \brief Base structure type of all async_contexts. For details about its use, see \ref pico_async_context.
175  * \ingroup pico_async_context
176  *
177  * Individual async_context_types with additional state, should contain this structure at the start.
178  */
179 struct async_context {
180     const async_context_type_t *type;
181     async_when_pending_worker_t *when_pending_list;
182     async_at_time_worker_t *at_time_list;
183     absolute_time_t next_time;
184     uint16_t flags;
185     uint8_t  core_num;
186 };
187 
188 /*!
189  * \brief Acquire the async_context lock
190  * \ingroup pico_async_context
191  *
192  * The owner of the async_context lock is the logic owner of the async_context
193  * and other work related to this async_context will not happen concurrently.
194  *
195  * This method may be called in a nested fashion by the the lock owner.
196  *
197  * \note the async_context lock is nestable by the same caller, so an internal count is maintained
198  *
199  * \note for async_contexts that provide locking (not async_context_poll), this method is threadsafe. and may be called from within any
200  * worker method called by the async_context or from any other non-IRQ context.
201  *
202  * \param context the async_context
203  *
204  * \see async_context_release_lock
205  */
async_context_acquire_lock_blocking(async_context_t * context)206 static inline void async_context_acquire_lock_blocking(async_context_t *context) {
207     context->type->acquire_lock_blocking(context);
208 }
209 
210 /*!
211  * \brief Release the async_context lock
212  * \ingroup pico_async_context
213  *
214  * \note the async_context lock may be called in a nested fashion, so an internal count is maintained. On the outermost
215  * release, When the outermost lock is released, a check is made for work which might have been skipped while the lock was held,
216  * 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.
217  *
218  * \note for async_contexts that provide locking (not async_context_poll), this method is threadsafe. and may be called from within any
219  * worker method called by the async_context or from any other non-IRQ context.
220  *
221  * \param context the async_context
222  *
223  * \see async_context_acquire_lock_blocking
224  */
async_context_release_lock(async_context_t * context)225 static inline void async_context_release_lock(async_context_t *context) {
226     context->type->release_lock(context);
227 }
228 
229 /*!
230  * \brief Assert if the caller does not own the lock for the async_context
231  * \ingroup pico_async_context
232  * \note this method is thread-safe
233  *
234  * \param context the async_context
235  */
async_context_lock_check(async_context_t * context)236 static inline void async_context_lock_check(async_context_t *context) {
237     context->type->lock_check(context);
238 }
239 
240 /*!
241  * \brief Execute work synchronously on the core the async_context belongs to.
242  * \ingroup pico_async_context
243  *
244  * This method is intended for code external to the async_context (e.g. another thread/task) to
245  * execute a function with the same guarantees (single core, logical thread of execution) that
246  * async_context workers are called with.
247  *
248  * \note you should NOT call this method while holding the async_context's lock
249  *
250  * \param context the async_context
251  * \param func the function to call
252  * \param param the parameter to pass to the function
253  * \return the return value from func
254  */
async_context_execute_sync(async_context_t * context,uint32_t (* func)(void * param),void * param)255 static inline uint32_t async_context_execute_sync(async_context_t *context, uint32_t (*func)(void *param), void *param) {
256     return context->type->execute_sync(context, func, param);
257 }
258 
259 /*!
260  * \brief Add an "at time" worker to a context
261  * \ingroup pico_async_context
262  *
263  * An "at time" worker will run at or after a specific point in time, and is automatically when (just before) it runs.
264  *
265  * The time to fire is specified in the next_time field of the worker.
266  *
267  * \note for async_contexts that provide locking (not async_context_poll), this method is threadsafe. and may be called from within any
268  * worker method called by the async_context or from any other non-IRQ context.
269  *
270  * \param context the async_context
271  * \param worker the "at time" worker to add
272  * \return true if the worker was added, false if the worker was already present.
273  */
async_context_add_at_time_worker(async_context_t * context,async_at_time_worker_t * worker)274 static inline bool async_context_add_at_time_worker(async_context_t *context, async_at_time_worker_t *worker) {
275     return context->type->add_at_time_worker(context, worker);
276 }
277 
278 /*!
279  * \brief Add an "at time" worker to a context
280  * \ingroup pico_async_context
281  *
282  * An "at time" worker will run at or after a specific point in time, and is automatically when (just before) it runs.
283  *
284  * The time to fire is specified by the at parameter.
285  *
286  * \note for async_contexts that provide locking (not async_context_poll), this method is threadsafe. and may be called from within any
287  * worker method called by the async_context or from any other non-IRQ context.
288  *
289  * \param context the async_context
290  * \param worker the "at time" worker to add
291  * \param at the time to fire at
292  * \return true if the worker was added, false if the worker was already present.
293  */
async_context_add_at_time_worker_at(async_context_t * context,async_at_time_worker_t * worker,absolute_time_t at)294 static inline bool async_context_add_at_time_worker_at(async_context_t *context, async_at_time_worker_t *worker, absolute_time_t at) {
295     worker->next_time = at;
296     return context->type->add_at_time_worker(context, worker);
297 }
298 
299 /*!
300  * \brief Add an "at time" worker to a context
301  * \ingroup pico_async_context
302  *
303  * An "at time" worker will run at or after a specific point in time, and is automatically when (just before) it runs.
304  *
305  * The time to fire is specified by a delay via the ms parameter
306  *
307  * \note for async_contexts that provide locking (not async_context_poll), this method is threadsafe. and may be called from within any
308  * worker method called by the async_context or from any other non-IRQ context.
309  *
310  * \param context the async_context
311  * \param worker the "at time" worker to add
312  * \param ms the number of milliseconds from now to fire after
313  * \return true if the worker was added, false if the worker was already present.
314  */
async_context_add_at_time_worker_in_ms(async_context_t * context,async_at_time_worker_t * worker,uint32_t ms)315 static inline bool async_context_add_at_time_worker_in_ms(async_context_t *context, async_at_time_worker_t *worker, uint32_t ms) {
316     worker->next_time = make_timeout_time_ms(ms);
317     return context->type->add_at_time_worker(context, worker);
318 }
319 
320 /*!
321  * \brief Remove an "at time" worker from a context
322  * \ingroup pico_async_context
323  *
324  * \note for async_contexts that provide locking (not async_context_poll), this method is threadsafe. and may be called from within any
325  * worker method called by the async_context or from any other non-IRQ context.
326  *
327  * \param context the async_context
328  * \param worker the "at time" worker to remove
329  * \return true if the worker was removed, false if the instance not present.
330  */
async_context_remove_at_time_worker(async_context_t * context,async_at_time_worker_t * worker)331 static inline bool async_context_remove_at_time_worker(async_context_t *context, async_at_time_worker_t *worker) {
332     return context->type->remove_at_time_worker(context, worker);
333 }
334 
335 /*!
336  * \brief Add a "when pending" worker to a context
337  * \ingroup pico_async_context
338  *
339  * An "when pending" worker will run when it is pending (can be set via \ref async_context_set_work_pending), and
340  * is NOT automatically removed when it runs.
341  *
342  * The time to fire is specified by a delay via the ms parameter
343  *
344  * \note for async_contexts that provide locking (not async_context_poll), this method is threadsafe. and may be called from within any
345  * worker method called by the async_context or from any other non-IRQ context.
346  *
347  * \param context the async_context
348  * \param worker the "when pending" worker to add
349  * \return true if the worker was added, false if the worker was already present.
350  */
async_context_add_when_pending_worker(async_context_t * context,async_when_pending_worker_t * worker)351 static inline bool async_context_add_when_pending_worker(async_context_t *context, async_when_pending_worker_t *worker) {
352     return context->type->add_when_pending_worker(context, worker);
353 }
354 
355 /*!
356  * \brief Remove a "when pending" worker from a context
357  * \ingroup pico_async_context
358  *
359  * \note for async_contexts that provide locking (not async_context_poll), this method is threadsafe. and may be called from within any
360  * worker method called by the async_context or from any other non-IRQ context.
361  *
362  * \param context the async_context
363  * \param worker the "when pending" worker to remove
364  * \return true if the worker was removed, false if the instance not present.
365  */
async_context_remove_when_pending_worker(async_context_t * context,async_when_pending_worker_t * worker)366 static inline bool async_context_remove_when_pending_worker(async_context_t *context, async_when_pending_worker_t *worker) {
367     return context->type->remove_when_pending_worker(context, worker);
368 }
369 
370 /*!
371  * \brief Mark a "when pending" worker as having work pending
372  * \ingroup pico_async_context
373  *
374  * The worker will be run from the async_context at a later time.
375  *
376  * \note this method may be called from any context including IRQs
377  *
378  * \param context the async_context
379  * \param worker the "when pending" worker to mark as pending.
380  */
async_context_set_work_pending(async_context_t * context,async_when_pending_worker_t * worker)381 static inline void async_context_set_work_pending(async_context_t *context, async_when_pending_worker_t *worker) {
382     context->type->set_work_pending(context, worker);
383 }
384 
385 /*!
386  * \brief Perform any pending work for polling style async_context
387  * \ingroup pico_async_context
388  *
389  * For a polled async_context (e.g. \ref async_context_poll) the user is responsible for calling this method
390  * periodically to perform any required work.
391  *
392  * This method may immediately perform outstanding work on other context types, but is not required to.
393  *
394  * \param context the async_context
395  */
async_context_poll(async_context_t * context)396 static inline void async_context_poll(async_context_t *context) {
397     if (context->type->poll) context->type->poll(context);
398 }
399 
400 /*!
401  * \brief sleep until the specified time in an async_context callback safe way
402  * \ingroup pico_async_context
403  *
404  * \note for async_contexts that provide locking (not async_context_poll), this method is threadsafe. and may be called from within any
405  * worker method called by the async_context or from any other non-IRQ context.
406  *
407  * \param context the async_context
408  * \param until the time to sleep until
409  */
async_context_wait_until(async_context_t * context,absolute_time_t until)410 static inline void async_context_wait_until(async_context_t *context, absolute_time_t until) {
411     context->type->wait_until(context, until);
412 }
413 
414 /*!
415  * \brief Block until work needs to be done or the specified time has been reached
416  * \ingroup pico_async_context
417  *
418  * \note this method should not be called from a worker callback
419  *
420  * \param context the async_context
421  * \param until the time to return at if no work is required
422  */
async_context_wait_for_work_until(async_context_t * context,absolute_time_t until)423 static inline void async_context_wait_for_work_until(async_context_t *context, absolute_time_t until) {
424     context->type->wait_for_work_until(context, until);
425 }
426 
427 /*!
428  * \brief Block until work needs to be done or the specified number of milliseconds have passed
429  * \ingroup pico_async_context
430  *
431  * \note this method should not be called from a worker callback
432  *
433  * \param context the async_context
434  * \param ms the number of milliseconds to return after if no work is required
435  */
async_context_wait_for_work_ms(async_context_t * context,uint32_t ms)436 static inline void async_context_wait_for_work_ms(async_context_t *context, uint32_t ms) {
437     async_context_wait_for_work_until(context, make_timeout_time_ms(ms));
438 }
439 
440 /*!
441  * \brief Return the processor core this async_context belongs to
442  * \ingroup pico_async_context
443  *
444  * \param context the async_context
445  * \return the physical core number
446  */
async_context_core_num(const async_context_t * context)447 static inline uint async_context_core_num(const async_context_t *context) {
448     return context->core_num;
449 }
450 
451 /*!
452  * \brief End async_context processing, and free any resources
453  * \ingroup pico_async_context
454  *
455  * Note the user should clean up any resources associated with workers
456  * in the async_context themselves.
457  *
458  * Asynchronous (non-polled) async_contexts guarantee that no
459  * callback is being called once this method returns.
460  *
461  * \param context the async_context
462  */
async_context_deinit(async_context_t * context)463 static inline void async_context_deinit(async_context_t *context) {
464     context->type->deinit(context);
465 }
466 
467 #ifdef __cplusplus
468 }
469 #endif
470 
471 #endif
472