use GINT_CALL() in every API that has callbacks

* Change gint_inth_callback()
* Add intc_handler_function() to use C functions as handlers instead of
  writing assembler, and use it in the RTC and USB

* Revisit the TMU handlers, which after moving out the callbacks, now
  fit into 3 gates (great!), and adapt the ETMU handler
* Improve the timer driver (less code = better code, removed magic
  constants assuming the VBR layout on SH3/SH4, etc.)
* Remove 2 gates and a gap from the compact scheme on SH3

* Define timer_configure() to replace timer_setup(), which could not be
  cleanly updated to support GINT_CALL()
* Replace rtc_start/stop_timer with rtc_periodic_enable/disable, which
  is less confusing because of ETMU being "RTC timers"
This commit is contained in:
Lephe 2021-04-27 14:29:38 +02:00
parent 85d30fa59b
commit c37f150600
No known key found for this signature in database
GPG key ID: 1BBA026E13FC0495
19 changed files with 399 additions and 523 deletions

View file

@ -26,6 +26,7 @@ set(SOURCES_COMMON
src/dma/memcpy.c src/dma/memcpy.c
src/dma/memset.c src/dma/memset.c
src/intc/intc.c src/intc/intc.c
src/intc/inth.s
src/kernel/exch.c src/kernel/exch.c
src/kernel/exch.s src/kernel/exch.s
src/kernel/hardware.c src/kernel/hardware.c
@ -55,7 +56,6 @@ set(SOURCES_COMMON
src/render/dtext.c src/render/dtext.c
src/render/dvline.c src/render/dvline.c
src/render/topti.c src/render/topti.c
src/rtc/inth.s
src/rtc/rtc.c src/rtc/rtc.c
src/rtc/rtc_ticks.c src/rtc/rtc_ticks.c
src/spu/spu.c src/spu/spu.c
@ -74,7 +74,6 @@ set(SOURCES_COMMON
src/tmu/tmu.c src/tmu/tmu.c
src/usb/classes/ff-bulk.c src/usb/classes/ff-bulk.c
src/usb/configure.c src/usb/configure.c
src/usb/inth.s
src/usb/pipes.c src/usb/pipes.c
src/usb/setup.c src/usb/setup.c
src/usb/string.c src/usb/string.c

View file

@ -66,36 +66,27 @@ static GINLINE void *gint_inthandler(int code, void const *h, size_t size) {
return intc_handler(code, h, size); return intc_handler(code, h, size);
} }
/* gint_inth_callback(): Call back arbitrary code from an interrupt handler /* gint_inth_callback(): Callback from interrupt handler to userland
Calls the specified function with the given argument after saving the user This function performs an indirect call as with gint_call(), afters saving
context, enabling interrupts and going to user bank. This function is used the user context, enabling interrupts and going to user bank. This is useful
to call user code from interrupt handlers, typically from timer or RTC to call user code from interrupt handlers. You can think of it as a kernel-
callbacks. You can think of it as a way to escape the SR.BL=1 environment to space escape to virtualized userland during interrupt handling.
safely call back virtualized and interrupt-based functions during interrupt
handling.
It is not safe to call from C code in any capacity and is mentioned here This function can only be useful in an interrupt handler's assembler code.
only for documentation purposes; you should really only call this from It is loaded at a runtime-determined address and accessed through a function
an interrupt handler's assembler code, typically like this: pointer, like this:
mov.l .callback, r0 mov.l .callback, r0
mov.l @r0, r0 # because function pointer mov.l @r0, r0 # because function pointer
mov <address of gint_call_t object>, r4
mov <function>, r4
mov <argument>, r5
jsr @r0 jsr @r0
nop nop
.callback: .callback:
.long _gint_inth_callback .long _gint_inth_callback
This function is loaded to a platform-dependent address determined at @call Address of a gint_call_t object
runtime; call it indirectly through the function pointer.
@callback Callback function, may take no argument in practice
@arg Argument
Returns the return value of the callback. */ Returns the return value of the callback. */
extern int (*gint_inth_callback)(int (*function)(void *arg), void *arg); extern int (*gint_inth_callback)(gint_call_t const *call);
#endif /* GINT_GINT */ #endif /* GINT_GINT */

View file

@ -117,4 +117,17 @@ int intc_priority(int intname, int level);
Returns the VBR address where the handler was installed. */ Returns the VBR address where the handler was installed. */
void *intc_handler(int event_code, void const *handler, size_t size); void *intc_handler(int event_code, void const *handler, size_t size);
/* intc_handler_function(): Install a function as an interrupt handler
This function can be used to install simple interrupt handlers. It installs
a pre-written interrupt handler that calls back the provided function.
Essentially it means that the interrupt handler can be written in C without
the numerous constraints of intc_handler(), at the cost of always going back
to userspace and a small time overhead.
@event_code Identifier of the interrupt block
@function Function to use as a handler
Returns true on success, false if the event code is invalid. */
bool intc_handler_function(int event_code, void (*function)(void));
#endif /* GINT_INTC */ #endif /* GINT_INTC */

View file

@ -6,6 +6,7 @@
#define GINT_RTC #define GINT_RTC
#include <gint/defs/types.h> #include <gint/defs/types.h>
#include <gint/defs/call.h>
#include <gint/timer.h> #include <gint/timer.h>
//--- //---
@ -43,7 +44,7 @@ void rtc_set_time(rtc_time_t const *time);
uint32_t rtc_ticks(void); uint32_t rtc_ticks(void);
//--- //---
// RTC timer // RTC periodic interrupt
// The real-time clock produces a regular interrupt which may be used as a // The real-time clock produces a regular interrupt which may be used as a
// timer with a maximum frequency of 256 Hz. It is also useful to check // timer with a maximum frequency of 256 Hz. It is also useful to check
// that the clock settings (see <gint/clock.h>) are properly detected, by // that the clock settings (see <gint/clock.h>) are properly detected, by
@ -63,34 +64,36 @@ enum
RTC_NONE = 0, RTC_NONE = 0,
}; };
/* rtc_start_timer(): Configure the RTC timer /* rtc_periodic_enable(): Enable the periodic interrupt
This function sets up the RTC timer to invoke the provided callback function This function sets up the periodic interrupt to invoke the provided callback
with its argument regularly. This works like standard timers. The callback regularly. As with timers, the callback must return either TIMER_CONTINUE or
is a function, the allowed types are listed in <gint/timer.h>. It may have TIMER_STOP.
zero or one argument, and must return either TIMER_CONTINUE or TIMER_STOP.
Do not confuse this "RTC timer" with Casio's added timers that run at Do not confuse this interrupt with CASIO's extra timers that run at 32768 Hz
32768 Hz (which are called "RTC timers" in CPU73050.dll). These timers are (which are called "RTC timers" in CPU73050.dll). These timers are called
called Extra TMU in gint and are handled by <gint/timer.h>. Extra TMU or ETMU in gint and are handled by <gint/timer.h>.
Note that the timing of the first callback is always uncertain. A 1 Hz timer Note that the timing of the first callback is always uncertain. A 1 Hz timer
set up when half of the current second is already elapsed will be called for set up when half of the current second is already elapsed will be called for
the first time after only 500 ms, for instance. the first time after only 500 ms, for instance.
@freq Timer frequency @frequency Periodic interrupt frequency
@callback Function to call back at the specified frequency @callback Function to call back at the specified frequency
@... Up to one 4-byte argument for the callback Returns true on success, false if the interrupt is already in use. */
Fails and returns non-zero if the RTC timer is already in use. */ bool rtc_periodic_enable(int frequency, gint_call_t callback);
int rtc_start_timer(int freq, timer_callback_t callback, ...);
/* Makes sure an argument is always provided, for va_arg() */ /* rtc_periodic_disable(): Stop the periodic interrupt
This has the same effect as returning TIMER_STOP from the callback, or
setting RTC_NONE as the parameter for rtc_periodic_enable(). */
void rtc_periodic_disable(void);
/* Deprecated versions with old-style callbacks and more confusing names */
__attribute__((deprecated("Use rtc_periodic_enable() instead")))
int rtc_start_timer(int frequency, timer_callback_t callback, ...);
#define rtc_start_timer(...) rtc_start_timer(__VA_ARGS__, 0) #define rtc_start_timer(...) rtc_start_timer(__VA_ARGS__, 0)
__attribute__((deprecated("Use rtc_periodic_disable() instead")))
/* rtc_stop_timer(): Stop the RTC timer
This function stops the RTC timer that was set up with rtc_start_timer(). If
the decision of stopping the timer comes from the callback, it is preferable
to return TIMER_STOP. */
void rtc_stop_timer(void); void rtc_stop_timer(void);
#endif /* GINT_RTC */ #endif /* GINT_RTC */

View file

@ -5,14 +5,15 @@
#ifndef GINT_TIMER #ifndef GINT_TIMER
#define GINT_TIMER #define GINT_TIMER
#include <gint/defs/types.h>
#include <gint/mpu/tmu.h> #include <gint/mpu/tmu.h>
#include <gint/hardware.h> #include <gint/hardware.h>
#include <gint/defs/types.h>
#include <gint/defs/call.h>
/* Timer types and numbers /* Timer types and numbers
If you're new to timers, read this comment and then check timer_setup() and If you're new to timers, read this comment and then check timer_configure()
timer_start(): most of the time you only need these. timer_start(): most of the time you only need these.
There are two types of timers on the calculator: normal timers called TMU, There are two types of timers on the calculator: normal timers called TMU,
and extra timers added by Casio called ETMU. The main difference is that TMU and extra timers added by Casio called ETMU. The main difference is that TMU
@ -23,7 +24,7 @@
* SH3-based fx9860g have 3 TMU (ID 0,1,2) and 1 ETMU (ID 3) * SH3-based fx9860g have 3 TMU (ID 0,1,2) and 1 ETMU (ID 3)
* SH4-based fx9860g and fxcg50 have 3 TMU (ID 0,1,2) and 6 ETMU (ID 3..8) * SH4-based fx9860g and fxcg50 have 3 TMU (ID 0,1,2) and 6 ETMU (ID 3..8)
You can request "a" timer with timer_setup(), and gint will choose an You can request "a" timer with timer_configure(), and gint will choose an
available timer depending on the precision you requested. Or, if you want available timer depending on the precision you requested. Or, if you want
one specifically, you ask for an ID of your choice. one specifically, you ask for an ID of your choice.
@ -33,13 +34,13 @@
is normally available for it unless you've used all TMUs at the same time is normally available for it unless you've used all TMUs at the same time
libprof also uses a TMU. libprof also uses a TMU.
Most of the time you can just use timer_setup() and specify TIMER_ANY. If Most of the time you can just use timer_configure() and specify TIMER_ANY.
you want to be sure that you have a TMU or an ETMU, you can do so by If you want to be sure that you have a TMU or an ETMU, you can do so by
specifying TIMER_TMU or TIMER_ETMU. If you further want to have a specific specifying TIMER_TMU or TIMER_ETMU. If you further want to have a specific
timer with specific settings, then you can: timer with specific settings, then you can:
* Set a specific ID in timer_setup(), in which case the delay is no longer * Set a specific ID in timer_configure(), in which case the delay is no
interpreter as count of µs, but as a TCOR value. longer interpreter as count of µs, but as a TCOR value.
* If this ID is a TMU, you can further add (with + or |) a prescaler * If this ID is a TMU, you can further add (with + or |) a prescaler
specification, one of TIMER_Pphi_{4,16,64,256}. specification, one of TIMER_Pphi_{4,16,64,256}.
* Regardless of how the timer was obtained, you can use timer_reload() to * Regardless of how the timer was obtained, you can use timer_reload() to
@ -48,7 +49,7 @@
priority levels of 13, 11, 9, and 7. The gray engine uses TMU0 to priority levels of 13, 11, 9, and 7. The gray engine uses TMU0 to
guarantee maximum visual stability in the presence of interrupts. guarantee maximum visual stability in the presence of interrupts.
In this module, timers are manipulated through their ID. timer_setup() In this module, timers are manipulated through their ID. timer_configure()
returns the ID of a timer which was allocated to you. You can check it to returns the ID of a timer which was allocated to you. You can check it to
determine whether your timer is a TMU (0,1,2) or an ETMU (3 or more). */ determine whether your timer is a TMU (0,1,2) or an ETMU (3 or more). */
@ -62,8 +63,8 @@
selects suitable speeds by default. selects suitable speeds by default.
If you want something very particular, you can add (with + or |) a prescaler If you want something very particular, you can add (with + or |) a prescaler
value to a chosen ID in timer_setup() to request that specific value. The value to a chosen ID in timer_configure() to request that specific value.
default prescaler if the ID is fixed is TIMER_Pphi_4. */ The default prescaler if the ID is fixed is TIMER_Pphi_4. */
enum { enum {
TIMER_Pphi_4 = 0x00, TIMER_Pphi_4 = 0x00,
TIMER_Pphi_16 = 0x10, TIMER_Pphi_16 = 0x10,
@ -71,37 +72,13 @@ enum {
TIMER_Pphi_256 = 0x30, TIMER_Pphi_256 = 0x30,
}; };
/* Timer selection; see timer_setup() */ /* Timer selection; see timer_configure() */
enum { enum {
TIMER_ANY = -1, TIMER_ANY = -1,
TIMER_TMU = -2, TIMER_TMU = -2,
TIMER_ETMU = -3, TIMER_ETMU = -3,
}; };
/* Type of callback functions
This module used to require callbacks of type int (*)(volatile void *), but
callbacks are rarely this generic. Now timer_setup() accepts functions of
any of the types below. */
typedef union
{
/* No argument, returns either TIMER_CONTINUE or TIMER_STOP */
int (*v)(void);
/* Single integer argument */
int (*i)(int);
/* Single pointer argument, cv-qualified as needed */
int (*pv) (void *);
int (*pVv) (volatile void *);
int (*pCv) (const void *);
int (*pCVv)(volatile const void *);
/* Integer pointer argument, cv-qualified as needed */
int (*pi) (int *);
int (*pVi) (volatile int *);
int (*pCi) (const int *);
int (*pCVi)(volatile const int *);
} GTRANSPARENT timer_callback_t;
/* Return value for timer callbacks, indicating whether the timer should /* Return value for timer callbacks, indicating whether the timer should
continue running and fire again, or stop now */ continue running and fire again, or stop now */
enum { enum {
@ -113,7 +90,7 @@ enum {
// Timer functions // Timer functions
//--- //---
/* timer_setup(): Reserve and configure a timer /* timer_configure(): Reserve and configure a timer
This function finds and configures a timer (without starting it). On This function finds and configures a timer (without starting it). On
success, it returns the ID of the configured timer, which is used in all success, it returns the ID of the configured timer, which is used in all
@ -126,15 +103,15 @@ enum {
again after the same delay) or stop the timer. again after the same delay) or stop the timer.
The first argument specifies what kind of timer you want. The first argument specifies what kind of timer you want.
* TIMER_ANY will let timer_setup() choose any available timer. timer_setup() * TIMER_ANY will let timer_configure() choose any available timer.
will only use an ETMU if the delay is more than 0.1 ms to avoid resolution timer_configure() will only use an ETMU if the delay is more than 0.1 ms
issues. Most of the time this is what you need. to avoid resolution issues. Most of the time this is what you need.
* TIMER_TMU or TIMER_ETMU will let timer_setup() choose an available TMU or * TIMER_TMU or TIMER_ETMU will let timer_configure() choose an available TMU
ETMU, respectively. or ETMU, respectively.
* Specifying an ID (0..8) will request exactly that timer. In this case, and * Specifying an ID (0..8) will request exactly that timer. In this case, and
if the ID is a TMU (0,1,2), you may add (with + or |) a prescaler value to if the ID is a TMU (0,1,2), you may add (with + or |) a prescaler value to
specify the clock input. Otherwise the clock is set to Pphi/4. specify the clock input. Otherwise the clock is set to Pphi/4.
If no timer matching the supplied settings is available, timer_setup() If no timer matching the supplied settings is available, timer_configure()
returns -1. returns -1.
The second argument is the delay. With TIMER_ANY, TIMER_TMU and TIMER_ETMU, The second argument is the delay. With TIMER_ANY, TIMER_TMU and TIMER_ETMU,
@ -142,28 +119,23 @@ enum {
is interpreted as a value of TCOR; see timer_delay() in this case. Note that is interpreted as a value of TCOR; see timer_delay() in this case. Note that
TCOR values are sensitive to the overclock level! TCOR values are sensitive to the overclock level!
The third argument is a function to be called when the timer expires. It may The third argument is an indirect function call to be made when the timer
have a single 4-byte argument (int or pointer), in which case you should expires. You can create one with GINT_CALL(); the function must return an
provide the value to call it with as an optional argument (you can only use int equal to either TIMER_CONTINUE or TIMER_STOP to control the subsequent
a single argument). It must return an int equal to either TIMER_CONTINUE or operation of the timer. Default calls are available in <gint/defs/call.h>
TIMER_STOP to control the subsequent operation of the timer. See the with common operations that are useful in timers. If GINT_CALL_NULL is
definition of timer_callback_t above for the possible function types. If passed, a default function that stops the timer will be used.
this argument is NULL, a default function that stops the timer will be used.
On success, the configured timer becomes reserved; it can no longer be On success, the configured timer becomes reserved; it can no longer be
returned by timer_setup() until: returned by timer_configure() until:
* Either timer_stop() is called, * Either timer_stop() is called,
* Or the callback returns TIMER_STOP (which also stops the timer). * Or the callback returns TIMER_STOP (which also stops the timer).
Remember than the returned timer is not started yet; see timer_start(). Remember than the returned timer is not started yet; see timer_start().
@timer Requested timer; TIMER_{ANY,TMU,ETMU} or an ID with prescaler @timer Requested timer; TIMER_{ANY,TMU,ETMU} or an ID with prescaler
@delay Delay between each event, in µs unless first argument is an ID @delay Delay between each event, in µs unless first argument is an ID
@callback Function to be called when the timer fires @callback Function to be called when the timer fires */
@... If the callback takes an argument, specify that value here */ int timer_configure(int timer, uint64_t delay_us, gint_call_t callback);
int timer_setup(int timer, uint64_t delay_us, timer_callback_t callback, ...);
/* Makes sure an argument is always provided, for va_arg() */
#define timer_setup(...) timer_setup(__VA_ARGS__, 0)
/* timer_start(): Start a configured timer /* timer_start(): Start a configured timer
The specified timer will start counting down and call its callback function The specified timer will start counting down and call its callback function
@ -227,13 +199,41 @@ uint32_t timer_delay(int timer, uint64_t delay_us, int clock);
void timer_reload(int timer, uint32_t delay); void timer_reload(int timer, uint32_t delay);
//--- //---
// Predefined timer callbacks // Deprecated API
//
// These types and functions were used in previous versions of gint but have
// been replaced. They are still here for compatibility until gint 3.
//--- //---
/* Type of callback functions; a primitive predecessor to GINT_CALL() */
typedef union
{
/* No argument, returns either TIMER_CONTINUE or TIMER_STOP */
int (*v)(void);
/* Single integer argument */
int (*i)(int);
/* Single pointer argument, cv-qualified as needed */
int (*pv) (void *);
int (*pVv) (volatile void *);
int (*pCv) (const void *);
int (*pCVv)(volatile const void *);
/* Integer pointer argument, cv-qualified as needed */
int (*pi) (int *);
int (*pVi) (volatile int *);
int (*pCi) (const int *);
int (*pCVi)(volatile const int *);
} GTRANSPARENT timer_callback_t;
/* timer_setup(): Old variant of timer_configure() */
__attribute__((deprecated("Use timer_configure() instead")))
int timer_setup(int timer, uint64_t delay_us, timer_callback_t callback, ...);
/* Makes sure an argument is always provided, for va_arg() */
#define timer_setup(...) timer_setup(__VA_ARGS__, 0)
/* timer_timeout(): Callback that sets a flag and halts the timer /* timer_timeout(): Callback that sets a flag and halts the timer
This predefined callback may be used when a timeout is required. It takes a Use GINT_CALL_SET_STOP() with timer_configure() instead. */
single argument which must be a pointer to int (or other integer type of 4
bytes) and increments it. */
int timer_timeout(volatile void *arg); int timer_timeout(volatile void *arg);
#endif /* GINT_TIMER */ #endif /* GINT_TIMER */

View file

@ -101,7 +101,8 @@ GCONSTRUCTOR static void gray_init(void)
} }
/* Try to obtain the timer right away */ /* Try to obtain the timer right away */
timer = timer_setup(GRAY_TIMER | GRAY_CLOCK, 1000, gray_int); timer = timer_configure(GRAY_TIMER | GRAY_CLOCK, 1000,
GINT_CALL(gray_int));
/* On failure, release the resources that we obtained */ /* On failure, release the resources that we obtained */
if(!gray_isinit()) gray_quit(); if(!gray_isinit()) gray_quit();

View file

@ -82,19 +82,16 @@ static struct info {
The _inth_remap table in src/kernel/inth.S combines the SH3-SH4 translation The _inth_remap table in src/kernel/inth.S combines the SH3-SH4 translation
with the compact translation, hence its entry for 0xf00 (the SH3 event code with the compact translation, hence its entry for 0xf00 (the SH3 event code
for ETMU0 underflow) is the offset in this table where 0x9e0 (the SH4 event for ETMU0 underflow) is the offset in this table where 0x9e0 (the SH4 event
code for the same event) is stored, which is 4. */ code for the same event) is stored, which is 3. */
static const uint16_t sh3_vbr_map[] = { static const uint16_t sh3_vbr_map[] = {
0x400, /* TMU0 underflow */ 0x400, /* TMU0 underflow */
0x420, /* TMU1 underflow */ 0x420, /* TMU1 underflow */
0x440, /* TMU2 underflow */ 0x440, /* TMU2 underflow */
0x460, /* (gint custom: TMU helper) */
0x9e0, /* ETMU0 underflow */ 0x9e0, /* ETMU0 underflow */
0xd00, /* ETMU4 underflow (used as helper on SH3) */ 0xd00, /* ETMU logic #1 (ETMU4 underflow) */
0xd20, /* (gint custom: ETMU helper) */ 1, /* ETMU logic #2 */
0xd40, /* (gint custom: ETMU helper) */ 1, /* ETMU logic #3 */
0xaa0, /* RTC Periodic Interrupt */ 0xaa0, /* RTC Periodic Interrupt */
1, /* (Filler to maintain the gap between 0xaa0 and 0xae0) */
0xae0, /* (gint custom: RTC helper) */
0 0
}; };
@ -173,6 +170,19 @@ void *intc_handler(int event_code, const void *handler, size_t size)
return memcpy(dest, handler, size); return memcpy(dest, handler, size);
} }
bool intc_handler_function(int event_code, void (*function)(void))
{
/* Install the genric handler */
extern void intc_generic_handler(void);
void *h = intc_handler(event_code, intc_generic_handler, 32);
if(!h) return false;
/* Set the function to be called */
*(void **)(h + 24) = function;
return true;
}
//--- //---
// State and driver metadata // State and driver metadata
//--- //---

33
src/intc/inth.s Normal file
View file

@ -0,0 +1,33 @@
/* gint.intc.inth: Standard interrupt handler
This is a generic interrupt handler that calls back into a C function,
useful for complex handling or simple drivers that benefit more from
simplicity than razor-sharp performance. */
.global _intc_generic_handler /* 32 bytes */
.section .gint.blocks, "ax"
.align 4
_intc_generic_handler:
/* Create a GINT_CALL() object with no arguments */
sts.l pr, @-r15
add #-20, r15
mov.l .function, r0
mov.l r0, @r15
/* Call back to the function in userspace */
mov.l .gint_inth_callback, r0
mov.l @r0, r0
jsr @r0
mov r15, r4
add #20, r15
lds.l @r15+, pr
rts
nop
.function:
.long 0 /* Function, set when handler is installed */
.gint_inth_callback:
.long _gint_inth_callback

View file

@ -160,9 +160,9 @@ _gint_inth_7705:
VBR offset SH3 events Description VBR offset SH3 events Description
------------------------------------------------------------------- -------------------------------------------------------------------
0x200 400 420 440 --- TMU0, TMU1, TMU2 and a helper 0x200 400 420 440 TMU0, TMU1, TMU2
0x280 f00 --- --- --- ETMU0, ETMU1, ETMU2 and a helper 0x260 f00 --- --- --- ETMU0, 3-gate logic at ETMU4
0x300 4a0 [ ] --- RTC Periodic Interrupt, gap and helper 0x2e0 4a0 RTC Periodic Interrupt
------------------------------------------------------------------- -------------------------------------------------------------------
0x600 --- --- Entry gate 0x600 --- --- Entry gate
------------------------------------------------------------------- -------------------------------------------------------------------
@ -171,7 +171,7 @@ _gint_inth_7705:
the interrupt entry gate at VBR + 0x640. */ the interrupt entry gate at VBR + 0x640. */
_inth_remap: _inth_remap:
.byte 0, 1, 2, 0xff, 0xff, 8, 0xff, 0xff .byte 0, 1, 2, 0xff, 0xff, 7, 0xff, 0xff
.byte 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff .byte 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
.byte 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff .byte 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
.byte 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff .byte 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
@ -182,10 +182,12 @@ _inth_remap:
.byte 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff .byte 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
.byte 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff .byte 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
.byte 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff .byte 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
.byte 4, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff .byte 3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
#endif #endif
.section .gint.mapped, "ax"
/* CALLBACK HELPER /* CALLBACK HELPER
This function implements the callback with context saving. It is a general This function implements the callback with context saving. It is a general
function and does not need to reside in VBR space which is block-structured. function and does not need to reside in VBR space which is block-structured.
@ -193,15 +195,9 @@ _inth_remap:
switches back to user bank and executes the callback. It does not save other switches back to user bank and executes the callback. It does not save other
registers (pr/mach/macl/gbr) which are managed by the handler entry. */ registers (pr/mach/macl/gbr) which are managed by the handler entry. */
/* gint_inth_callback() /* gint_inth_callback: Indirect call from kernel space to userland
Calls the specified function with the given argument after saving the user @r4 Address of callback function
context, enabling interrupts and going to user bank. -> Returns the return value of the callback (int). */
@r4 Callback function (volatile void * -> int)
@r5 Argument (volatile void *)
Returns the return value of the callback (int). */
.section .gint.mapped, "ax"
_gint_inth_callback_reloc: _gint_inth_callback_reloc:
stc.l r0_bank, @-r15 stc.l r0_bank, @-r15
stc.l r1_bank, @-r15 stc.l r1_bank, @-r15
@ -218,7 +214,6 @@ _gint_inth_callback_reloc:
/* Save some values to user bank; once we enable interrupts, the kernel /* Save some values to user bank; once we enable interrupts, the kernel
bank might be overwritten at any moment. */ bank might be overwritten at any moment. */
ldc r4, r0_bank ldc r4, r0_bank
ldc r5, r4_bank
/* Enable interrupts and go back to user bank. On SH4, SR.IMASK is set /* Enable interrupts and go back to user bank. On SH4, SR.IMASK is set
to the level of the current interrupt, which makes sure we can only to the level of the current interrupt, which makes sure we can only
@ -248,10 +243,15 @@ _gint_inth_callback_reloc:
.load_sr: .load_sr:
ldc r1, sr ldc r1, sr
/* We are now in the user bank with r0 and r4 set. Call back. We want /* We are now in the user bank with r0 set. Perform the call. We want
to forward the return value to kernel bank, but this bank can be to forward the return value to kernel bank, but this bank can be
changed at any moment since interrupts are enabled. */ changed at any moment since interrupts are enabled. */
sts.l pr, @-r15 sts.l pr, @-r15
mov.l @(4, r0), r4
mov.l @(8, r0), r5
mov.l @(12, r0), r6
mov.l @(16, r0), r7
mov.l @r0, r0
jsr @r0 jsr @r0
nop nop
lds.l @r15+, pr lds.l @r15+, pr

View file

@ -167,7 +167,8 @@ static void configure(void)
getkey_repeat(400, 40); getkey_repeat(400, 40);
/* The timer will be stopped when the timer driver is unloaded */ /* The timer will be stopped when the timer driver is unloaded */
keysc_tid = timer_setup(TIMER_ANY, scan_frequency_us, keysc_tick); keysc_tid = timer_configure(TIMER_ANY, scan_frequency_us,
GINT_CALL(keysc_tick));
if(keysc_tid >= 0) timer_start(keysc_tid); if(keysc_tid >= 0) timer_start(keysc_tid);
gint[HWKBD] = HW_LOADED | (isSH3() ? HWKBD_IO : HWKBD_KSI); gint[HWKBD] = HW_LOADED | (isSH3() ? HWKBD_IO : HWKBD_KSI);

View file

@ -1,62 +0,0 @@
/*
** gint:rtc:inth - Interrupt handler for the Real-Time Clock
** This one is fairly simple, just a flag to clear and potentially a timer to
** stop if the callback returns non-zero.
*/
.global _inth_rtc_pri
.global _inth_rtc_pri_helper
.section .gint.blocks, "ax"
.align 4
/* RTC PERIODIC INTERRUPT HANDLER - 56 BYTES */
_inth_rtc_pri:
/* Invoke the callback function with its argument */
sts.l pr, @-r15
mov.l .gint_inth_callback, r0
mov.l @r0, r0
mov.l 1f, r4
mov.l 2f, r5
jsr @r0
nop
/* Jump to another gate to finish the work:
- 0xc is the size of storage below
- 0x20 is the size of the gap before next gate (alarm interrupt) */
mov #0x2c, r2
braf r2
nop
1: .long 0 /* Callback function: edited dynamically */
2: .long 0 /* Argument to callback function */
.gint_inth_callback:
.long _gint_inth_callback
_inth_rtc_pri_helper:
/* Save the return value */
mov r0, r3
.clear:
/* Clear the interrupt flag */
mov.l .RCR2, r1
mov.b @r1, r0
tst #0x80, r0
and #0x7f, r0
bf.s .clear
mov.b r0, @r1
/* Stop the timer if the return value of the callback was non-zero */
tst r3, r3
bt .end
and #0x8f, r0
mov.b r0, @r1
.end:
lds.l @r15+, pr
rts
nop
.RCR2: .long 0xa413fede /* RCR2 address, edited at startup on SH3 */

View file

@ -13,19 +13,9 @@
#include <stdarg.h> #include <stdarg.h>
#undef rtc_start_timer
//---
// Real-Time Clock peripheral registers
//---
/* RTC address on SH7305, adjusted at startup on SH7337 and SH7355 */ /* RTC address on SH7305, adjusted at startup on SH7337 and SH7355 */
static rtc_t *RTC = &SH7305_RTC; static rtc_t *RTC = &SH7305_RTC;
/* Address of interrupt handler parameters */
GBSS static struct {
void *function;
uint32_t arg;
} GPACKED(4) *timer_params;
//--- //---
// Time management // Time management
@ -94,42 +84,67 @@ void rtc_set_time(rtc_time_t const *time)
} }
//--- //---
// RTC timer // RTC periodic interrupt
//--- //---
/* rtc_start_timer(): Configure the RTC timer */ static gint_call_t rtc_periodic_callback;
int rtc_start_timer(int freq, timer_callback_t f, ...)
{
/* Refuse to override an existing callback */
if(RTC->RCR2.PES != RTC_NONE) return 1;
if(freq == RTC_NONE) return 1;
/* Get the argument */ bool rtc_periodic_enable(int frequency, gint_call_t callback)
va_list args; {
va_start(args, f); /* Refuse to override an existing interrupt */
uint32_t arg = va_arg(args, uint32_t); if(RTC->RCR2.PES != RTC_NONE) return false;
va_end(args); if(frequency == RTC_NONE)
{
rtc_periodic_disable();
return true;
}
/* Temporarily disable the interrupt and set up the callback */ /* Temporarily disable the interrupt and set up the callback */
RTC->RCR2.PES = RTC_NONE; RTC->RCR2.PES = RTC_NONE;
timer_params->function = f.v; rtc_periodic_callback = callback;
timer_params->arg = arg;
/* Clear the interrupt flag */ /* Clear the interrupt flag */
do RTC->RCR2.PEF = 0; do RTC->RCR2.PEF = 0;
while(RTC->RCR2.PEF); while(RTC->RCR2.PEF);
/* Enable the interrupt */ /* Enable the interrupt */
RTC->RCR2.PES = freq; RTC->RCR2.PES = frequency;
return 0; return true;
} }
/* rtc_stop_timer(): Stop the RTC timer */ void rtc_periodic_interrupt(void)
void rtc_stop_timer(void) {
int rc = gint_call(rtc_periodic_callback);
/* Clear the interrupt flag */
do RTC->RCR2.PEF = 0;
while(RTC->RCR2.PEF);
/* Stop the interrupt if the callback returns non-zero */
if(rc) rtc_periodic_disable();
}
void rtc_periodic_disable(void)
{ {
RTC->RCR2.PES = RTC_NONE; RTC->RCR2.PES = RTC_NONE;
} }
/* Deprecated versions */
#undef rtc_start_timer
int rtc_start_timer(int frequency, timer_callback_t function, ...)
{
va_list args;
va_start(args, function);
uint32_t arg = va_arg(args, uint32_t);
va_end(args);
return !rtc_periodic_enable(frequency, GINT_CALL(function.v, arg));
}
void rtc_stop_timer(void)
{
return rtc_periodic_disable();
}
//--- //---
// Driver initialization // Driver initialization
//--- //---
@ -148,19 +163,8 @@ static void configure(void)
/* Clear the periodic interrupt flag */ /* Clear the periodic interrupt flag */
RTC->RCR2.PEF = 0; RTC->RCR2.PEF = 0;
/* Interrupt handlers provided by rtc/inth.s */
extern void inth_rtc_pri(void);
extern void inth_rtc_pri_helper(void);
/* Install the RTC interrupt handler */ /* Install the RTC interrupt handler */
GUNUSED void *h0, *h1; intc_handler_function(0xaa0, rtc_periodic_interrupt);
h0 = intc_handler(0xaa0, inth_rtc_pri, 32);
h1 = intc_handler(0xae0, inth_rtc_pri_helper, 32);
timer_params = h0 + 20;
volatile uint8_t **RCR2_pointer = h1 + 28;
*RCR2_pointer = &RTC->RCR2.byte;
/* Disable the RTC interrupts for now. Give them priority 1; higher /* Disable the RTC interrupts for now. Give them priority 1; higher
priorities cause freezes when going back to the system on SH3 priorities cause freezes when going back to the system on SH3

View file

@ -1,80 +1,77 @@
/* /*
** gint:tmu:inth-etmu - Interrupt handlers for the RTC-bound timers ** gint:tmu:inth-etmu - Interrupt handlers for the RTC-bound timers
**
** This handler uses 3 consecutive blocks like the TMU handler. However this
** time 2 empty blocks after ETMU4 (0xd20, 0xd40) are used because blocks for
** ETMU are not consecutive in memory.
**
** It would be possible to communicate between any interrupt handlers in non-
** consecutive gates. A simple way is to store at runtime a pointer to the
** desired object in one handler. But that costs a lot of space. If the
** relative position of interrupt handlers is known, the best option left is
** the unnatural @(disp,pc) addressing mode, and it doesn't even work with the
** SH3's compact VBR scheme.
*/ */
/* Gates for the extra timers (informally called ETMU) */ /* Gates for the extra timers (informally called ETMU) */
.global _inth_etmu4 .global _inth_etmu4 /* 96 bytes */
.global _inth_etmux .global _inth_etmux /* 32 bytes */
.section .gint.blocks, "ax" .section .gint.blocks, "ax"
.align 4 .align 4
/* EXTRA TMU INTERRUPT HANDLERS - 96 BYTES /* 3-block handler installed at the ETMU4 gate. */
To implement the same functionalities as the standard timers, several blocks
are once again needed. This time, 2 empty blocks after ETMU4 (0xd20, 0xd40)
are used for convenience.
It would be possible to communicate between any interrupt handlers in non-
consecutive gates. A simple way is to store at runtime a pointer to the
desired object in one handler. But that costs a lot fo space. If the
relative position of interrupt handlers is known, the best option left is
the unnatural @(disp,pc) addressing mode, and it doesn't even work with the
SH3's compact VBR scheme. */
/* FIRST GATE - ETMU4 and two empty blocks */
_inth_etmu4: _inth_etmu4:
mova .storage_etmu4, r0 mova .storage_etmu4, r0
mov #7, r2 mov #7, r2
.shared: .shared:
mov.l r2, @-r15
mov.l r8, @-r15 mov.l r8, @-r15
sts.l pr, @-r15 sts.l pr, @-r15
mov r0, r1
/* Prepare an indirect call to timer_stop(<id>) */
add #-20, r15
mov.l r2, @(4, r15)
/* Clear interrupt flag in TCR */ /* Clear interrupt flag in TCR */
mov.l @(8, r1), r3 mov r0, r1
1: mov.l @(4, r1), r3
mov.b @r3, r0 1: mov.b @r3, r0
tst #0x02, r0 tst #0x02, r0
and #0xfd, r0 and #0xfd, r0
bf/s 1b bf/s 1b
mov.b r0, @r3 mov.b r0, @r3
/* Prepare invoking the callback function */ /* Invoke callback */
mov.l .gint_inth_callback, r8 mov.l .gint_inth_callback, r8
mov.l @r8, r8 mov.l @r8, r8
mov.l @r1, r4
jsr @r8 jsr @r8
mov.l @(4, r1), r5 mov.l @r1, r4
tst r0, r0 tst r0, r0
bt 2f bt 2f
/* Invoke callback; if return value is non-zero, stop timer */ /* If return value is non-zero, stop the timer with another callback */
mov.l .timer_stop, r4 mov.l .timer_stop, r0
mov.l r0, @r15
jsr @r8 jsr @r8
mov.l @(8, r15), r5 mov r15, r4
/* Clear the flag and possibly stop the timer */ 2: add #20, r15
2:
lds.l @r15+, pr lds.l @r15+, pr
mov.l @r15+, r8
rts rts
add #4, r15 mov.l @r15+, r8
.zero 24 .zero 26
.gint_inth_callback:
.long _gint_inth_callback
.timer_stop: .timer_stop:
.long _timer_stop .long _timer_stop
.gint_inth_callback:
.long _gint_inth_callback
.storage_etmu4: .storage_etmu4:
.long 0 /* Callback: Configured dynamically */ .long _tmu_callbacks + 140
.long 0 /* Argument: Configured dynamically */ .long 0xa44d00bc /* RTCR4 */
.long 0 /* TCR: Configured dynamically */
/* SECOND GATE - All other ETMU entries, falling back to ETMU2 */ /* Generic gate for all other ETMU handlers, falling back to ETMU4. */
_inth_etmux: _inth_etmux:
/* Dynamically compute the target of the jump */ /* Dynamically compute the target of the jump */
stc vbr, r3 stc vbr, r3
@ -85,20 +82,15 @@ _inth_etmux:
mov.w .id_etmux, r2 mov.w .id_etmux, r2
jmp @r3 jmp @r3
nop nop
nop
nop
.id_etmux: .id_etmux:
.word 0 /* Timer ID */ .word 0 /* Timer ID */
/* Offset from VBR where extra timer 2 is located: /* Offset from VBR where ETMU4 is located; set during configure */
* 0x600 to reach the interrupt handlers 1: .long (.shared - _inth_etmu4)
* 0x040 to jump over the entry gate
* 0x900 to reach the handler of ETMU4
* Skip over the first instructions
This is different on SH3 due to the compact scheme so it's edited
dynamically at install time. */
1: .long 0xf40 + (.shared - _inth_etmu4)
.storage_etmux: .storage_etmux:
.long 0 /* Callback: Configured dynamically */ .long _tmu_callbacks
.long 0 /* Argument: Configured dynamically */ .long 0 /* TCR address */
.long 0 /* TCR: Configured dynamically */

View file

@ -1,121 +1,94 @@
/* /*
** gint:tmu:inth-tmu - Interrupt handlers for the timer units ** gint:tmu:inth-tmu - Interrupt handlers for the timer units
** Perhaps the most technical of my interrupt handlers. They implement a **
** simple kind of interrupt handler communication by letting the control flow ** This handler consists of 3 consecutive gates that operate as a block. It
** from each interrupt handler to the next. ** clears the interrupt flags, invokes a GINT_CALL() in userspace, and stops
** the timer if the callback returns non-zero.
**
** It is important to notice that the code of the gates is continuous in this
** file and thus must be continuous in memory, as the assembler will use
** relative addressing methods. This "block operations" is only possible for
** handlers that are mapped to consecutive event codes.
*/ */
/* Gates for the standard Timer Unit (TMU) */ /* Gates for the standard Timer Unit (TMU) */
.global _inth_tmu /* 128 bytes */ .global _inth_tmu /* 96 bytes */
.section .gint.blocks, "ax" .section .gint.blocks, "ax"
.align 4 .align 4
/* TMU INTERRUPT HANDLERS - 128 BYTES /* TMU0 entry and interrupt flag clearing. */
Unfortunately I did not manage to write a handler that cleared the interrupt
flag and invoked a callback in less than 34 bytes data included. So I
decided to make several gates operate as a whole and add a bit more features
in them. Basically, these handlers:
- Clear the interrupt flag
- Invoke a callback function and pass it a user-provided argument
- Stop the timer if the callback returns non-zero
- Host their own callback pointers and arguments
It is important to notice that the code of the following gates looks like
they are contiguous in memory. The assembler will make that assumption, and
turn any address reference between two gates into a *relative displacement*.
If the gates don't have the same relative location at runtime, the code will
crash because we will have broken the references. This is why we can only do
it with handlers that are mapped to consecutive event codes. */
_inth_tmu: _inth_tmu:
mov #0, r5
mov #0, r6
mov #0, r7
/* FIRST GATE - TMU0 entry, clear underflow flag and call back */
_inth_tmu_0:
mova .storage0, r0
mov #0, r1
/*** This is the first shared section ***/
.shared1: .shared1:
mov.l .TCR0, r1
add r6, r1
/* Save the timer ID on the stack */
mov.l r8, @-r15 mov.l r8, @-r15
sts.l pr, @-r15 sts.l pr, @-r15
mov.l r1, @-r15 mov.l r5, @-r15
/* Load the TCR address */ /* Clear the interrupt flag. Because r5 contains 0, 1 or 2 the 16 top
mov.l .mask, r3 bits are 0 so we can compare without extending */
not r3, r4 1: mov.w @r1, r5
mov.l @(8, r0), r1 extu.b r5, r3
cmp/eq r5, r3
bf/s 1b
mov.w r3, @r1
/* Clear the interrupt flag */ /* Prepare to run the callback */
1: mov.w @r1, r2 mov.l .callback, r8
tst r4, r2
and r3, r2
mov.w r2, @r1
bf 1b
/* Prepare callback and jump to second section */
mov.l .gint_inth_callback, r8
bra .shared2 bra .shared2
mov.l @r8, r8 mov.l @r8, r8
/* SECOND GATE - TMU1 entry and stop timer */ /* TMU1 entry, callback and timer stop logic. */
_inth_tmu_1: _inth_tmu_1:
mova .storage1, r0 mov #1, r5
mov #12, r6
bra .shared1 bra .shared1
mov #1, r1 mov #20, r7
/*** This is the second shared section ***/
.shared2: .shared2:
/* Invoke callback */ /* Invoke callback */
mov.l @r0, r4 mov.l .tmu_callbacks, r4
jsr @r8 jsr @r8
mov.l @(4, r0), r5 add r7, r4
/* Stop the timer if the return value is not zero */
mov.l @r15+, r5
tst r0, r0 tst r0, r0
bt 2f mov.l .timer_stop, r2
mov.l .timer_stop, r4 bt/s .shared3
mov.l r2, @-r15
/* Stop the timer if the return value is not zero. We use the top of
the stack as a gint_call_t object; only the function and first
argument matter, timer_stop() will ignore the rest. */
jsr @r8 jsr @r8
mov r15, r4
bra .shared3
nop
nop nop
2: /* TMU2 entry, shared exit and storage. */
_inth_tmu_2:
mov #2, r5
mov #24, r6
bra .shared1
mov #40, r7
.shared3:
add #8, r15
lds.l @r15+, pr lds.l @r15+, pr
rts rts
mov.l @r15+, r8 mov.l @r15+, r8
.zero 2
/* THIRD GATE - TMU2 entry and storage for TMU0 */
_inth_tmu_2:
mova .storage2, r0
bra .shared1
mov #2, r1
.zero 10
.gint_inth_callback:
.long _gint_inth_callback
.storage0:
.long 0 /* Callback: Configured dynamically */
.long 0 /* Argument: Configured dynamically */
.long 0xa4490010 /* TCR0: Overridden at startup on SH3 */
/* FOURTH GATE - Storage for TMU1, TMU2 and other values */
_inth_tmu_storage:
.mask:
.long 0xfffffeff
.timer_stop: .timer_stop:
.long _timer_stop .long _timer_stop
.callback:
.storage1: .long _gint_inth_callback
.long 0 /* Callback: Configured dynamically */ .TCR0:
.long 0 /* Argument: Configured dynamically */ .long 0xa4490010
.long 0xa449001c /* TCR1: Overridden at startup on SH3 */ .tmu_callbacks:
.long _tmu_callbacks
.storage2:
.long 0 /* Callback: Configured dynamically */
.long 0 /* Argument: Configured dynamically */
.long 0xa4490028 /* TCR2: Overridden at startup on SH3 */

View file

@ -9,7 +9,8 @@ static void do_sleep(uint64_t delay_us, int spin)
{ {
volatile int flag = 0; volatile int flag = 0;
int timer = timer_setup(TIMER_ANY, delay_us, timer_timeout, &flag); int timer = timer_configure(TIMER_ANY, delay_us,
GINT_CALL_SET_STOP(&flag));
if(timer < 0) return; if(timer < 0) return;
timer_start(timer); timer_start(timer);

View file

@ -7,40 +7,29 @@
#include <gint/drivers/states.h> #include <gint/drivers/states.h>
#include <gint/clock.h> #include <gint/clock.h>
#include <gint/intc.h> #include <gint/intc.h>
#include <gint/cpu.h>
#include <gint/mpu/tmu.h> #include <gint/mpu/tmu.h>
#include <stdarg.h> #include <stdarg.h>
#undef timer_setup /* Callbacks for all timers */
gint_call_t tmu_callbacks[9];
//---
// Driver storage
//---
/* inth_data_t - data storage inside interrupt handlers */
typedef struct
{
void *function; /* User-provided callback */
uint32_t arg; /* Argument for function */
volatile void *TCR; /* TCR address for TMU */
} GPACKED(4) inth_data_t;
/* This array references the storage areas of all timer handlers */
static inth_data_t *timers[9] = { NULL };
/* Arrays of standard and extra timers */ /* Arrays of standard and extra timers */
static tmu_t *TMU = SH7305_TMU.TMU; static tmu_t *TMU = SH7305_TMU.TMU;
static etmu_t *ETMU = SH7305_ETMU; static etmu_t *ETMU = SH7305_ETMU;
/* TSTR register for standard timers */ /* TSTR register for standard timers */
static volatile uint8_t *TSTR = &SH7305_TMU.TSTR; static volatile uint8_t *TSTR = &SH7305_TMU.TSTR;
/* Shortcut to set registers that are slow to update */
#define set(lval, rval) do(lval = rval); while(rval != lval)
//--- //---
// Local functions // Local functions
//--- //---
/* conf(): Configure a fixed timer */ /* conf(): Configure a fixed timer */
static void conf(int id, uint32_t delay, int clock, void *f, uint32_t arg) static void conf(int id, uint32_t delay, int clock, gint_call_t call)
{ {
if(id < 3) if(id < 3)
{ {
@ -52,8 +41,7 @@ static void conf(int id, uint32_t delay, int clock, void *f, uint32_t arg)
T->TCOR = delay; T->TCOR = delay;
T->TCNT = delay; T->TCNT = delay;
T->TCR.TPSC = clock; T->TCR.TPSC = clock;
do T->TCR.UNF = 0; set(T->TCR.UNF, 0);
while(T->TCR.UNF);
/* Enable interrupt and count on rising edge (SH7705) */ /* Enable interrupt and count on rising edge (SH7705) */
T->TCR.UNIE = 1; T->TCR.UNIE = 1;
@ -64,22 +52,14 @@ static void conf(int id, uint32_t delay, int clock, void *f, uint32_t arg)
etmu_t *T = &ETMU[id-3]; etmu_t *T = &ETMU[id-3];
if(T->TCR.UNIE) return; if(T->TCR.UNIE) return;
/* No clock input and clock edge here. But TCR and TCNT need /* No clock input and clock edge here */
some time to execute the write */ set(T->TCR.UNF, 0);
do T->TCR.UNF = 0; set(T->TCOR, delay);
while(T->TCR.UNF); set(T->TCNT, delay);
do T->TCOR = delay;
while(T->TCOR != delay);
do T->TCNT = delay;
while(T->TCNT != delay);
T->TCR.UNIE = 1; T->TCR.UNIE = 1;
} }
timers[id]->function = f; tmu_callbacks[id] = call;
timers[id]->arg = arg;
} }
/* matches(): Check if a timer matches the provided specification and delay */ /* matches(): Check if a timer matches the provided specification and delay */
@ -99,8 +79,7 @@ static int matches(int id, int spec, uint32_t delay)
/* available(): Check if a timer is available (UNIE cleared, not running) */ /* available(): Check if a timer is available (UNIE cleared, not running) */
static int available(int id) static int available(int id)
{ {
/* The timer should also be installed... */ if(id >= timer_count()) return 0;
if(!timers[id]) return 0;
if(id < 3) if(id < 3)
{ {
@ -124,19 +103,12 @@ static int stop_callback(void)
// Timer API // Timer API
//--- //---
/* timer_setup(): Reserve and configure a timer */ int timer_configure(int spec, uint64_t delay, gint_call_t call)
int timer_setup(int spec, uint64_t delay, timer_callback_t function, ...)
{ {
int clock = 0; int clock = 0;
/* Get the optional argument */ /* Default behavior for the callback */
va_list va; if(!call.function) call = GINT_CALL(stop_callback);
va_start(va, function);
uint32_t arg = va_arg(va, uint32_t);
va_end(va);
/* Default value for the callback */
if(!function.v) function.v = stop_callback;
/* Find a matching timer, starting from the slowest timers with the /* Find a matching timer, starting from the slowest timers with the
smallest interrupt priorities all the way up to TMU0 */ smallest interrupt priorities all the way up to TMU0 */
@ -169,7 +141,7 @@ int timer_setup(int spec, uint64_t delay, timer_callback_t function, ...)
/* Find the delay constant for that timer and clock */ /* Find the delay constant for that timer and clock */
if(spec < 0) delay = timer_delay(id, delay, clock); if(spec < 0) delay = timer_delay(id, delay, clock);
conf(id, delay, clock, function.v, arg); conf(id, delay, clock, call);
return id; return id;
} }
@ -196,10 +168,6 @@ uint32_t timer_delay(int id, uint64_t delay_us, int clock)
freq = 32768; freq = 32768;
} }
/* fxcg50: Calculated = 29491200 but it's too low */
/* TODO: Account for down spread spectrum in the CPG */
// uint64_t freq = 29020000 >> 2;
uint64_t product = freq * delay_us; uint64_t product = freq * delay_us;
return product / 1000000; return product / 1000000;
} }
@ -246,18 +214,13 @@ void timer_stop(int id)
} }
else else
{ {
/* Extra timers generate interrupts when TCNT=0 even if TSTR=0.
We always keep TCOR/TCNT to non-zero values when idle. */
etmu_t *T = &ETMU[id-3]; etmu_t *T = &ETMU[id-3];
T->TCR.UNIE = 0; T->TCR.UNIE = 0;
set(T->TCOR, 0xffffffff);
/* Also clear TCOR and TCNT to avoid spurious interrupts */ set(T->TCNT, 0xffffffff);
do T->TCOR = 0xffffffff; set(T->TCR.UNF, 0);
while(T->TCOR + 1);
do T->TCNT = 0xffffffff;
while(T->TCNT + 1);
do T->TCR.UNF = 0;
while(T->TCR.UNF);
} }
} }
@ -293,10 +256,20 @@ void timer_spinwait(int id)
} }
//--- //---
// Predefined timer callbacks // Deprecated API
//--- //---
/* timer_timeout() - callback that sets a flag and halts the timer */ #undef timer_setup
int timer_setup(int spec, uint64_t delay, timer_callback_t function, ...)
{
va_list va;
va_start(va, function);
uint32_t arg = va_arg(va, uint32_t);
va_end(va);
return timer_configure(spec, delay, GINT_CALL(function.v, arg));
}
int timer_timeout(void volatile *arg) int timer_timeout(void volatile *arg)
{ {
int volatile *x = arg; int volatile *x = arg;
@ -308,7 +281,7 @@ int timer_timeout(void volatile *arg)
// Driver initialization // Driver initialization
//--- //---
/* Interrupt handlers for standard timers (4 gates) */ /* Interrupt handlers for standard timers (3 gates) */
extern void inth_tmu(void); extern void inth_tmu(void);
/* Interrupt handlers for extra timers */ /* Interrupt handlers for extra timers */
extern void inth_etmu4(void); extern void inth_etmu4(void);
@ -329,65 +302,44 @@ static void configure(void)
uint16_t etmu_event[6] = { 0x9e0, 0xc20, 0xc40, 0x900, 0xd00, 0xfa0 }; uint16_t etmu_event[6] = { 0x9e0, 0xc20, 0xc40, 0x900, 0xd00, 0xfa0 };
*TSTR = 0; *TSTR = 0;
/* Install the standard TMU's interrupt handlers */ /* Install the TMU handlers and adjust the TCR0 value on SH3 */
void *h = intc_handler(0x400, inth_tmu, 128); void *h = intc_handler(0x400, inth_tmu, 96);
timers[0] = h + 84; if(isSH3()) *(void volatile **)(h + 88) = &TMU[0].TCR;
timers[1] = h + 104;
timers[2] = h + 116;
/* Clear every timer to avoid surprises */ /* Clear all timers */
for(int id = 0; id < 3; id++) for(int i = 0; i < 3; i++)
{ {
do TMU[id].TCR.word = 0; set(TMU[i].TCR.word, 0);
while(TMU[id].TCR.word); TMU[i].TCOR = 0xffffffff;
TMU[i].TCNT = 0xffffffff;
TMU[id].TCOR = 0xffffffff;
TMU[id].TCNT = 0xffffffff;
/* Standard timers: TCR is provided to the interrupt handler */
timers[id]->TCR = &TMU[id].TCR;
} }
for(int id = 0; id < timer_count()-3; id++) for(int i = 3; i < timer_count(); i++)
{ {
etmu_t *T = &ETMU[id]; etmu_t *T = &ETMU[i-3];
/* Extra timers seem to generate interrupts as long as TCNT=0,
regardless of TSTR. I'm not entirely sure about this weird
behaviour, but for safety I'll set TCOR/TCNT to non-zero. */
T->TSTR = 0; T->TSTR = 0;
do T->TCOR = 0xffffffff; set(T->TCOR, 0xffffffff);
while(T->TCOR + 1); set(T->TCNT, 0xffffffff);
set(T->TCR.byte, 0);
/* Also TCNT and TCR take some time to record changes */
do T->TCNT = 0xffffffff;
while(T->TCNT + 1);
do T->TCR.byte = 0;
while(T->TCR.byte);
} }
/* Install the extra timers. On SH3, only ETMU0 is available */ /* Install the ETMU4 handler, which contains the logic for ETMUs */
void *h4 = intc_handler(etmu_event[4], inth_etmu4, 96);
/* Install the other ETMU handlers, and set their parameters */
for(int i = 3; i < timer_count(); i++) if(i != 7) for(int i = 3; i < timer_count(); i++) if(i != 7)
{ {
void *h = intc_handler(etmu_event[i-3], inth_etmux, 32); void *h = intc_handler(etmu_event[i-3], inth_etmux, 32);
timers[i] = h + 20;
/* On SH3, the ETMU handler is not at an offset of 0x900 (event /* Distance from VBR handler to ETMU4, used to jump */
code 0xd00) but at an offset of 0xa0 */ *(uint32_t *)(h + 20) += (uint32_t)h4 - cpu_getVBR();
uint32_t *etmu_offset = h + 16; /* Timer ID, used for timer_stop() after the callback */
if(isSH3()) *etmu_offset = *etmu_offset - 0xf40 + 0x2a0; *(uint16_t *)(h + 18) = i;
/* Pointer to the callback */
uint16_t *data_id = h + 14; *(gint_call_t **)(h + 24) += i;
*data_id = i; /* TCR address to acknowledge the interrupt */
*(void volatile **)(h + 28) = &ETMU[i-3].TCR;
uint32_t *TCR = h + 28;
*TCR = (uint32_t)&ETMU[i-3].TCR;
} }
/* Also install ETMU4, even on SH3, because it contains common code */
h = intc_handler(etmu_event[4], inth_etmu4, 96);
timers[7] = h + 84;
*(uint32_t *)(h + 92) = (uint32_t)&ETMU[4].TCR;
/* Enable TMU0 at level 13, TMU1 at level 11, TMU2 at level 9 */ /* Enable TMU0 at level 13, TMU1 at level 11, TMU2 at level 9 */
intc_priority(INTC_TMU_TUNI0, 13); intc_priority(INTC_TMU_TUNI0, 13);
intc_priority(INTC_TMU_TUNI1, 11); intc_priority(INTC_TMU_TUNI1, 11);
@ -448,16 +400,10 @@ static void hrestore(tmu_state_t const *s)
struct tmu_state_stored_timer const *c = &s->t[i]; struct tmu_state_stored_timer const *c = &s->t[i];
etmu_t *T = &ETMU[i-3]; etmu_t *T = &ETMU[i-3];
do T->TCOR = c->TCOR; set(T->TCOR, c->TCOR);
while(T->TCOR != c->TCOR);
T->TSTR = c->TSTR; T->TSTR = c->TSTR;
set(T->TCNT, c->TCNT);
do T->TCNT = c->TCNT; set(T->TCR.byte, c->TCR);
while(T->TCNT != c->TCNT);
do T->TCR.byte = c->TCR;
while(T->TCR.byte != c->TCR);
} }
*TSTR = s->TSTR; *TSTR = s->TSTR;

View file

@ -1,29 +0,0 @@
/*
** gint:usb:inth - Interrupt handler for the USB function module
*/
.global _inth_usb
.section .gint.blocks, "ax"
.align 4
/* USB INTERRUPT HANDLER */
_inth_usb:
/* Call back into driver code, there is way too much work that needs to
be done to fit in here */
sts.l pr, @-r15
mov.l 1f, r0
mov.l @r0, r0
mov.l 2f, r4
jsr @r0
nop
lds.l @r15+, pr
rts
nop
.zero 6
1: .long _gint_inth_callback
2: .long _usb_interrupt_handler

View file

@ -10,8 +10,7 @@
#define USB SH7305_USB #define USB SH7305_USB
/* Interrupt handler */ static void usb_interrupt_handler(void);
extern void inth_usb(void);
/* Shorthand to clear a bit in INTSTS0 */ /* Shorthand to clear a bit in INTSTS0 */
#define INTSTS0_clear(field_name) { \ #define INTSTS0_clear(field_name) { \
@ -165,7 +164,7 @@ int usb_open(usb_interface_t const **interfaces, gint_call_t callback)
USB.NRDYENB.word = 0x0000; USB.NRDYENB.word = 0x0000;
USB.BEMPENB.word = 0x0000; USB.BEMPENB.word = 0x0000;
intc_handler(0xa20, inth_usb, 32); intc_handler_function(0xa20, usb_interrupt_handler);
intc_priority(INTC_USB, 15); intc_priority(INTC_USB, 15);
usb_open_status = true; usb_open_status = true;
@ -191,7 +190,7 @@ void usb_close(void)
// Userspace interrupt handler // Userspace interrupt handler
//--- //---
void usb_interrupt_handler(void) static void usb_interrupt_handler(void)
{ {
static char const * const device_st[] = { static char const * const device_st[] = {
"powered", "default", "address", "configured", "powered", "default", "address", "configured",

View file

@ -205,7 +205,8 @@ void usb_pipe_init_transfers(void);
/* usb_while(): A while loop with a timeout */ /* usb_while(): A while loop with a timeout */
#define usb_while(condition) ({ \ #define usb_while(condition) ({ \
volatile int __f = 0; \ volatile int __f = 0; \
int __t = timer_setup(TIMER_ANY, 100000 /*µs*/, timer_timeout, &__f); \ int __t = timer_configure(TIMER_ANY, 100000 /*µs*/, \
GINT_CALL_SET_STOP(&__f)); \
if(__t >= 0) timer_start(__t); \ if(__t >= 0) timer_start(__t); \
while((condition) && __f == 0) {} \ while((condition) && __f == 0) {} \
if(__f) usb_log("%s: %d: (" #condition ") holds\n", \ if(__f) usb_log("%s: %d: (" #condition ") holds\n", \