diff --git a/TODO b/TODO index 2118578..70e3fc0 100644 --- a/TODO +++ b/TODO @@ -1,6 +1,6 @@ Extensions on existing code: * bfile: implement the optimization-restart as realized by Kbd2 -* kernel: use GINT_CB() for all callbacks, without breaking the timer API +* kernel: use GINT_CALL() 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/defs/call.h b/include/gint/defs/call.h new file mode 100644 index 0000000..0c19561 --- /dev/null +++ b/include/gint/defs/call.h @@ -0,0 +1,169 @@ +//--- +// gint:defs:call - Indirect calls +// +// The following types and functions can be used to create "indirect function +// calls": function calls that are executed several times or a long time after +// they are created. This is useful for callbacks, where you set up a function +// to be called when an event occurs. The function and its argument are kept in +// the call object until the event occurs, at which point the call is realized. +//--- + +#ifndef GINT_DEFS_CALL +#define GINT_DEFS_CALL + +/* gint_call_arg_t: All types of arguments allowed in an indirect call + + Because a function call cannot be easily pieced together, there are + restrictions on what arguments can be passed. The following union lists all + of the available types. Other types can be used if casted, mainly pointers; + see the description of GINT_CALL() for details. */ +typedef union { + /* 32-bit integers */ + int i; + unsigned int u; + int32_t i32; + uint32_t u32; + /* 32-bit floating-point */ + float f; + + /* Pointers to most common types, in all possible cv-qualifications */ + #define POINTER(type, name) \ + type *name; \ + type const *name ## _c; \ + type volatile *name ## _v; \ + type volatile const *name ## _cv; + + POINTER(void, pv) + POINTER(char, pc) + POINTER(unsigned char, puc) + POINTER(short, ps) + POINTER(unsigned short, pus) + POINTER(int, pi) + POINTER(unsigned int, pui) + POINTER(int8_t, pi8) + POINTER(uint8_t, pu8) + POINTER(int16_t, pi16) + POINTER(uint16_t, pu16) + POINTER(int32_t, pi32) + POINTER(uint32_t, pu32) + POINTER(int64_t, pi64) + POINTER(uint64_t, pu64) + POINTER(long long int, pll) + POINTER(unsigned long long int, pull) + POINTER(float, pf) + POINTER(double, pd) + #undef POINTER +} gint_call_arg_t; + +/* gint_call_t: Indirect call with up to 4 register arguments */ +typedef struct { + void *function; + gint_call_arg_t args[4]; +} gint_call_t; + +/* GINT_CALL(): Build an indirect call from function and arguments + + This macro builds an indirect call (of type gint_call_t). Indirect calls 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. + + The calls are often made asynchronously, which means that the function + setting up the call finishes first, and then the call is made later while + some other part of the program is running. This is tricky, because in order + to perform the call: + + * The code and arguments must still exist, even though the function that + provided them has returned 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 call will still be valid when the call is made; in particular, + pointers to variables on the stack can ony be used if the call is guaranteed + to be made before the function ends (eg. if there is a synchronous wait in + the function). + + For the second issue, gint's indirect call 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 integers, 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_call_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 call 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 function + take an int. If you need to pass a pointer to a type not listed in + gint_call_arg_t (such as a structure), cast it to (void *); the function can + still take a pointer to the custom type as argument. + + If the conditions for the arguments to work are not met, the compiler will + emit on of these two errors: + + * error: static assertion failed: "GINT_CALL: 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. */ +#define GINT_CALL(func, ...) \ + ((gint_call_t){ .function = func, .args = { \ + __VA_OPT__(GINT_CALL_ARGS1(__VA_ARGS__)) \ + }}) +#define GINT_CALL_ARGS1(a1, ...) \ + (gint_call_arg_t)(a1), __VA_OPT__(GINT_CALL_ARGS2(__VA_ARGS__)) +#define GINT_CALL_ARGS2(a2, ...) \ + (gint_call_arg_t)(a2), __VA_OPT__(GINT_CALL_ARGS3(__VA_ARGS__)) +#define GINT_CALL_ARGS3(a3, ...) \ + (gint_call_arg_t)(a3), __VA_OPT__(GINT_CALL_ARGS4(__VA_ARGS__)) +#define GINT_CALL_ARGS4(a4, ...) \ + ({ __VA_OPT__(_Static_assert(0, \ + "GINT_CALL: too many arguments (maximum 4)");) \ + (gint_call_arg_t)(a4); }) + +/* GINT_CALL_NULL: Empty function call */ +#define GINT_CALL_NULL ((gint_call_t){ .function = NULL, .args = {} }) + +/* gint_call(): Perform an indirect call */ +static GINLINE int gint_call(gint_call_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); +} + +//--- +// Predefined indirect calls +// +// * GINT_CALL_SET(pointer_to_int) will create an indirect call that sets +// (*pointer_to_int) to 1 when invoked. +// +// * GINT_CALL_INC(pointer_to_int) will create an indirect call that increments +// (*pointer_to_int) when invoked. +//--- + +/* GINT_CALL_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_CALL_SET_function(int volatile *pointer) +{ + (*pointer) = 1; +} +static GINLINE gint_call_t GINT_CALL_SET(int volatile *pointer) +{ + return GINT_CALL(GINT_CALL_SET_function, pointer); +} + +/* GINT_CALL_INC(): Callback that increments an integer */ +static void GINT_CALL_INC_function(int volatile *pointer) +{ + (*pointer)++; +} +static GINLINE gint_call_t GINT_CALL_INC(int volatile *pointer) +{ + return GINT_CALL(GINT_CALL_INC_function, pointer); +} + +#endif /* GINT_DEFS_CALL */ diff --git a/include/gint/gint.h b/include/gint/gint.h index 6b1d71e..fd8e418 100644 --- a/include/gint/gint.h +++ b/include/gint/gint.h @@ -6,6 +6,7 @@ #define GINT_GINT #include +#include #include /* gint_switch(): Switch out of gint to execute a function @@ -129,160 +130,4 @@ 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 */ diff --git a/include/gint/usb.h b/include/gint/usb.h index 53d4545..0a79a96 100644 --- a/include/gint/usb.h +++ b/include/gint/usb.h @@ -82,13 +82,13 @@ typedef struct usb_interface_endpoint { will return an error. The second parameter is a callback to be (asynchronously) invoked when the - USB link becomes ready. Use GINT_CB() to create one, or pass GINT_CB_NULL - for no callback. You can also use usb_open_wait() to synchronously wait for - the link to be ready. + USB link is ready. Use GINT_CALL() to create one, or pass GINT_CALL_NULL for + no callback. You can also use usb_open_wait() to synchronously wait for the + link to be ready. @interfaces NULL-terminate list of interfaces to open @callback Optional function to be called when the USB link opens */ -int usb_open(usb_interface_t const **interfaces, gint_callback_t callback); +int usb_open(usb_interface_t const **interfaces, gint_call_t callback); /* usb_open_wait(): Wait until the USB link is ready When called after usb_open(), this function waits until the communication is diff --git a/src/usb/pipes.c b/src/usb/pipes.c index 44003b0..4df0190 100644 --- a/src/usb/pipes.c +++ b/src/usb/pipes.c @@ -124,7 +124,7 @@ struct transfer { /* Whether to use the DMA */ bool dma; /* Callback at the end of the transfer */ - gint_callback_t callback; + gint_call_t callback; }; /* Operations to be continued whenever buffers get empty */ GBSS static struct transfer volatile pipe_transfers[10]; @@ -198,7 +198,7 @@ static bool write_round(struct transfer volatile *t, int pipe) /* After the DMA starts the code below will update pointers for the next iteration */ // dma_start(X, Y, Z, - // GINT_CB(maybe_commit, (void *)t, pipe)); + // GINT_CALL(maybe_commit, (void *)t, pipe)); } else { @@ -220,7 +220,7 @@ static bool write_round(struct transfer volatile *t, int pipe) /* usb_write_async(): Asynchronously write to a USB pipe */ int usb_write_async(int pipe, void const *data, int size, int unit_size, - bool use_dma, gint_callback_t callback) + bool use_dma, gint_call_t callback) { struct transfer volatile *t = &pipe_transfers[pipe]; @@ -257,14 +257,14 @@ int usb_write_sync(int pipe, void const *data, int size, int unit_size, /* Wait for a previous write and/or transfer to finish */ while(t->data || (pipe && !USB.PIPECTR[pipe-1].BSTS)) sleep(); - usb_write_async(pipe, data, size, unit_size, use_dma, GINT_CB_NULL); + usb_write_async(pipe, data, size, unit_size, use_dma, GINT_CALL_NULL); /* Wait for the write to finish (but not the transfer) */ while(t->data) sleep(); return 0; } -void usb_commit_async(int pipe, gint_callback_t callback) +void usb_commit_async(int pipe, gint_call_t callback) { struct transfer volatile *t = &pipe_transfers[pipe]; t->committed = true; @@ -287,6 +287,5 @@ void usb_pipe_write_bemp(int pipe) USB.BEMPENB.word &= ~(1 << pipe); - if(t->callback.function) - gint_callback_invoke(t->callback); + if(t->callback.function) gint_call(t->callback); } diff --git a/src/usb/usb.c b/src/usb/usb.c index 46a5157..5fa8d67 100644 --- a/src/usb/usb.c +++ b/src/usb/usb.c @@ -23,7 +23,7 @@ extern void inth_usb(void); /* Callback function to invoke when the USB module is configured */ /* TODO: usb_open() callback: Let interfaces specify when they're ready! */ -static gint_callback_t usb_open_callback = GINT_CB_NULL; +static gint_call_t usb_open_callback = GINT_CALL_NULL; /* Whether the USB link is currently open */ static bool volatile usb_open_status = false; @@ -119,7 +119,7 @@ static void usb_module_stop(bool restore_mselcr) *MSELCRA = (*MSELCRA & 0xff3f) | 0x0040; } -int usb_open(usb_interface_t const **interfaces, gint_callback_t callback) +int usb_open(usb_interface_t const **interfaces, gint_call_t callback) { usb_log("---- usb_open ----\n"); @@ -196,7 +196,7 @@ void usb_close(void) usb_module_stop(false); usb_log("---- usb_close ----\n"); - usb_open_callback = GINT_CB_NULL; + usb_open_callback = GINT_CALL_NULL; usb_open_status = false; } @@ -241,8 +241,8 @@ void usb_interrupt_handler(void) usb_open_status = true; if(usb_open_callback.function) { - gint_callback_invoke(usb_open_callback); - usb_open_callback = GINT_CB_NULL; + gint_call(usb_open_callback); + usb_open_callback = GINT_CALL_NULL; } } } diff --git a/src/usb/usb_private.h b/src/usb/usb_private.h index 4351e26..814249c 100644 --- a/src/usb/usb_private.h +++ b/src/usb/usb_private.h @@ -154,8 +154,8 @@ int usb_write_sync(int pipe, void const *data, int size, int unit_size, This function is similar to usb_write_sync(), but it only starts the writing and returns immediately without ever waiting. The writing then occurs in the background of the calling code, and the caller is notified through a - callback when it completes. Use GINT_CB() to create a callback or pass - GINT_CB_NULL. + callback when it completes. Use GINT_CALL() to create a callback or pass + GINT_CALL_NULL. If the pipe is busy due to a previous asynchronous write, this function returns USB_PIPE_BUSY. When called with (use_dma=true), it returns as soon @@ -175,7 +175,7 @@ int usb_write_sync(int pipe, void const *data, int size, int unit_size, @callback Optional callback to invoke when the write completes -> Returns an error code (0 on success). */ int usb_write_async(int pipe, void const *data, int size, int unit_size, - bool use_dma, gint_callback_t callback); + bool use_dma, gint_call_t callback); /* usb_commit_sync(): Synchronously commit a write @@ -190,7 +190,7 @@ void usb_commit_sync(int pipe); This function commits the specified pipe, causing the pipe to transfer written data as soon as all the writes complete. It returns immediately and instead the specified callback is invoked when the transfer completes. */ -void usb_commit_async(int pipe, gint_callback_t callback); +void usb_commit_async(int pipe, gint_call_t callback); /* usb_pipe_write_bemp(): Callback for the BEMP interrupt on a pipe */ void usb_pipe_write_bemp(int pipe);