From a2fd9e33514e6d1dc800df3da3367ae0071d0ebb Mon Sep 17 00:00:00 2001 From: Lephe Date: Sun, 11 Apr 2021 18:47:17 +0200 Subject: [PATCH] kernel: add a generic callback mechanism This mechanism allows callbacks to be defined with up to 4 32-bit arguments, and could be extended later. This will hopefully replace the timer_callback_t used in timers and RTC, and will be added to the DMA and USB APIs -- the hard part is to not break source compatibility with previous versions. --- TODO | 1 + include/gint/gint.h | 156 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 157 insertions(+) diff --git a/TODO b/TODO index d904b9b..0873356 100644 --- a/TODO +++ b/TODO @@ -1,4 +1,5 @@ Extensions on existing code: +* kernel: use GINT_CB() for all callbacks, without breaking the timer API * kernel: better restore to userspace before panic (ensure BL=0 IMASK=0) * kernel: check if cpu_setVBR() really needs to be perma-mapped * project: add license file diff --git a/include/gint/gint.h b/include/gint/gint.h index 5d041e2..6b1d71e 100644 --- a/include/gint/gint.h +++ b/include/gint/gint.h @@ -129,4 +129,160 @@ void *gint_inthandler(int event_code, void const *handler, size_t size); Returns the return value of the callback. */ extern int (*gint_inth_callback)(int (*function)(void *arg), void *arg); +//--- +// Callback functions +//--- + +/* gint_callback_arg_t: All types of arguments allowed in a callback + Other types can be used if casted, notably pointers to custom types can be + casted to (void *). */ +typedef union { + /* Integer types */ + int i; + unsigned int u; + int32_t i32; + uint32_t u32; + /* 4-byte floating-point type */ + float f; + /* Pointer to void */ + void *pv; + void const *pv_c; + void volatile *pv_v; + void volatile const *pv_cv; + /* Pointer to int */ + int *pi; + int const *pi_c; + int volatile *pi_v; + int volatile const *pi_cv; + /* Pointer to unsigned int */ + unsigned int *pu; + unsigned int const *pu_c; + unsigned int volatile *pu_v; + unsigned int volatile const *pu_cv; + /* Pointer to int32_t */ + int32_t *pi32; + int32_t const *pi32_c; + int32_t volatile *pi32_v; + int32_t volatile const *pi32_cv; + /* Pointer to uint32_t */ + uint32_t *pu32; + uint32_t const *pu32_c; + uint32_t volatile *pu32_v; + uint32_t volatile const *pu32_cv; + /* Pointer to float */ + float *pf; + float const *pf_c; + float volatile *pf_v; + float volatile const *pf_cv; + /* Pointer to double */ + double *pd; + double const *pd_c; + double volatile *pd_v; + double volatile const *pd_cv; + +} gint_callback_arg_t; + +/* gint_callback_t: Callback function with up to 4 register arguments */ +typedef struct { + void *function; + gint_callback_arg_t args[4]; +} gint_callback_t; + +/* GINT_CB(): Build a callback object from function and arguments + + This macro builds a callback object (of type gint_callback_t). Callback + objects are used in various APIs (timers, RTC, DMA, USB...) to notify the + program of events that are caused by the hardware instead of the program. + + Callbacks are often called asynchronously, which means that the function + setting up the callback finishes first, and then the callback is called + later while some other part of the program is running. This is tricky, + because in order to invoke the callback: + + * The code and arguments must still exist, even though the function that + provided them has finished long ago; + * The call ABI is lost as soon as we store parameters instead of + syntactically performing a call in the code. + + For the first issue, the caller has to make sure that every pointer that is + passed to the callback will still be valid when the callback is invoked; in + particular, pointers to variables on the stack can ony be used if the + callback is guaranteed to be called before the function ends (eg. if there + is a synchronous wait in the function). + + For the second issue, gint's callback mechanism guarantees ABI compatibility + by restricting the arguments that can be passed to the callback. + + * Only arguments that fit into registers can be passed. In practice, this + mostly excludes 64-bit integer, double floating-point values, and custom + structures. This way, there is a somewhat solid guarantee that the + callback function will take arguments in r4...r7. + * Only up to 4 arguments can be passed. + * Only values of the types listed in gint_callback_arg_t can be passed. + + If you need to work around one of these limitations, pass a pointer to a + structure containing your arguments (if the callback is invoked after the + current function ends, make the structure static or global). + + If you need to pass a char or a short, cast to an int and have the callback + function take an int. If you need to pass a pointer to a type not listed in + gint_callback_arg_t (such as a structure), cast it to (void *); the callback + function can still take a pointer to the custom type as argument. + + If the conditions for the callback to work are not met, the compiler will + emit on of these two errors: + + * error: static assertion failed: "GINT_CB: too many arguments (maximum 4)" + -> This is emitted if you have more than 4 arguments. + * error: cast to union type from type not present in union + -> This is emitted if you pass a parameter of an invalid type. + + Both are followed with a series of compiler notes mentioning the various + macros defined below. */ +#define GINT_CB(func, ...) \ + ((gint_callback_t){ .function = func, .args = { \ + __VA_OPT__(GINT_CB_ARGS1(__VA_ARGS__)) \ + }}) +#define GINT_CB_ARGS1(a1, ...) \ + (gint_callback_arg_t)(a1), __VA_OPT__(GINT_CB_ARGS2(__VA_ARGS__)) +#define GINT_CB_ARGS2(a2, ...) \ + (gint_callback_arg_t)(a2), __VA_OPT__(GINT_CB_ARGS3(__VA_ARGS__)) +#define GINT_CB_ARGS3(a3, ...) \ + (gint_callback_arg_t)(a3), __VA_OPT__(GINT_CB_ARGS4(__VA_ARGS__)) +#define GINT_CB_ARGS4(a4, ...) \ + ({ __VA_OPT__(_Static_assert(0, \ + "GINT_CB: too many arguments (maximum 4)");) \ + (gint_callback_arg_t)(a4); }) + +/* GINT_CB_NULL: Empty callback */ +#define GINT_CB_NULL ((gint_callback_t){ .function = NULL, .args = {} }) + +/* GINT_CB_SET(): Callback that sets an integer to 1 + This is defined as a function to make sure the pointer is to an int. */ +static void GINT_CB_SET_function(int volatile *pointer) +{ + (*pointer) = 1; +} +static GINLINE gint_callback_t GINT_CB_SET(int volatile *pointer) +{ + return GINT_CB(GINT_CB_SET_function, pointer); +} + +/* GINT_CB_INC(): Callback that increments an integer */ +static void GINT_CB_INC_function(int volatile *pointer) +{ + (*pointer)++; +} +static GINLINE gint_callback_t GINT_CB_INC(int volatile *pointer) +{ + return GINT_CB(GINT_CB_INC_function, pointer); +} + +/* gint_callback_invoke(): Invoke a callback function */ +static GINLINE int gint_callback_invoke(gint_callback_t cb) +{ + int (*f)(int r4, int r5, int r6, int r7) = cb.function; + return f(cb.args[0].i, cb.args[1].i, cb.args[2].i, cb.args[3].i); +} + #endif /* GINT_GINT */