gint: rename the callbacks GINT_CALL, as there are synchronous ones

This will be useful for world switch calls which are not "callbacks" but
simply polymorphic function pointers.
This commit is contained in:
Lephe 2021-04-20 13:52:18 +02:00
parent 52bc1fc848
commit 770b4e0117
No known key found for this signature in database
GPG key ID: 1BBA026E13FC0495
7 changed files with 190 additions and 177 deletions

2
TODO
View file

@ -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

169
include/gint/defs/call.h Normal file
View file

@ -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 */

View file

@ -6,6 +6,7 @@
#define GINT_GINT
#include <gint/defs/types.h>
#include <gint/defs/call.h>
#include <gint/config.h>
/* 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 */

View file

@ -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

View file

@ -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);
}

View file

@ -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;
}
}
}

View file

@ -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);