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