mirror of
https://git.planet-casio.com/Lephenixnoir/gint.git
synced 2024-12-28 04:23:36 +01:00
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:
parent
52bc1fc848
commit
770b4e0117
7 changed files with 190 additions and 177 deletions
2
TODO
2
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
|
||||
|
|
169
include/gint/defs/call.h
Normal file
169
include/gint/defs/call.h
Normal 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 */
|
|
@ -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 */
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue