core, tmu: add gint_switch(), return to menu, and improve timer code

* Add the gint_switch() function which executes user-provided code from
  the system (CASIOWIN) context.
* Added interrupt masks to the core context (should have been there long
  ago).
* Added the gint_osmenu() function that switches out of gint to invoke
  GetKeyWait() and inject KEY_CTRL_MENU to trigger the main menu. This
  uses many CASIOWIN syscalls, but we don't care because gint is unloaded.
  Trickery is used to catch the key following the return in the add-in
  and/or display a new application frame before GetKeyWait() even finishes
  after coming back. This is only available on fx9860g for now.
* Removed any public syscall definition to clear up interfaces.
* Patched the DMA interruption problem in a weird way on fxcg50, a
  driver function will be used to do that properly eventually.
* Changed the driver model to save driver contexts in preallocated
  spaces instead of on the stack for overall less risk.
* Enabled return-to-menu with the MENU key on fx9860g in getkey().
* Changed the keyboard driver to emit releases before presses, as a
  return-to-menu acts as a press+release of different keys in a single
  driver frame, which confuses getkey().
* Fixed a really stupid bug in memcpy() that made the function really
  not work.

Improvements in the timer driver:

* Expose ETMU modules as SH7705_TMU and SH7305_TMU in <gint/mpu/tmu.h>.
* Remove the timer_t structures, using SH*_ETMU and SH*_TMU instead.
  Only interrupt gate entries are left hardcoded.
* Discovered that not only every write to the TCNT or TCR of an ETMU
  takes about 1/32k of a second (hinting at registers being powered by
  the same clock as the timer), but every write occuring while a previous
  write is pending is *lost*. This led to terrible bugs when switching
  ETMU contexts too fast in gint_switch().
* Removed an internal timer_address() function.
* Overall simplified the handling of timers and the initialization step.
This commit is contained in:
Lephe 2020-05-10 14:03:41 +02:00
parent 2cdf925f94
commit 4485e7f865
Signed by: Lephenixnoir
GPG Key ID: 1BBA026E13FC0495
22 changed files with 425 additions and 327 deletions

View File

@ -21,7 +21,7 @@ all-targets := $(foreach b,$(builds),all-$b)
all: $(all-targets)
all-build%: build%
@ echo -e "\n$B::$W Making into $<$N"
@ echo -e "$B::$W Making into $<$N"
@ $(MAKE) --no-print-directory -C $<
#
@ -33,7 +33,7 @@ install-targets := $(foreach b,$(builds),install-$b)
install: $(install-targets)
install-build%: build%
@ echo -e "\n$B::$W Installing from $<$N"
@ echo -e "$B::$W Installing from $<$N"
@ $(MAKE) --no-print-directory -C $< install
#
@ -45,7 +45,7 @@ uninstall-targets := $(foreach b,$(builds),uninstall-$b)
uninstall: $(uninstall-targets)
uninstall-build%: build%
@ echo -e "\n$B::$W Uninstalling from $<$N"
@ echo -e "$B::$W Uninstalling from $<$N"
@ $(MAKE) --no-print-directory -C $< uninstall
#

4
TODO
View File

@ -2,6 +2,7 @@ Crucial, missing things.
! core: the four basic memory functions
! core: gint_switch() with driver contexts on stack and arbitrary code
! core: use gint_switch() to handle TLB misses
! core: return to menu on fxcg50
! bopti: fxcg50 version
! syscalls: fxcg50 BFile calls
@ -12,10 +13,11 @@ Issues.
* #3 make drawing functions parameterized
* #5 add decent random number generation (TinyMT)
* #8 support fx-CG Manager
* #9 add libimg
* #10 support fx-CG 20
Complementary elements on existing code.
* gray: double-buffer gray settings and unify d* with g*
* display: deprecate image_t and rename it bopti_image_t
* make fx9860g projects work out of the box on fxcg50
* topti: support unicode fonts
* gray: find good values for more models than the Graph 35+E II

View File

@ -5,6 +5,9 @@
#ifndef GINT_DEFS_UTIL
#define GINT_DEFS_UTIL
/* synco instruction (in a form compatible with sh3eb-elf) */
#define synco() __asm__ volatile (".word 0x00ab":::"memory")
/* min(), max() (without double evaluation) */
#define min(a, b) ({ \
__auto_type _a = (a); \

View File

@ -57,18 +57,13 @@ typedef struct
If there is no unload function, the field may be set to NULL */
void (*unload)(void);
/* Size of a context object for the driver */
uint ctx_size;
/* System context. The driver has to allocate a buffer of size at least
ctx_size, where gint stores the system's configuration. It is
advised to place this buffer in the .gint_bss section using the GBSS
macro of <defs/attributes.h> if it doesn't need to be initialized */
/* System's context and gint's context. These should point to enough
memory to store a full driver state each. These are used when
switching from the system to gint and back to the main menu. If they
don't need to be initialized, put them in gint's uninitialized BSS
section using the GBSS macro of <gint/defs/attributes.h>. */
void *sys_ctx;
/* Stack context. This is a gint *internal* pointer used when switching
in and out of the driver. Ignore it. */
void *_stack_ctx;
void *gint_ctx;
/* ctx_save() - save the driver's hardware support
This function is provided by the driver to save the state of its

View File

@ -29,11 +29,26 @@ void gint_install(void);
/* gint_unload() - unload gint and give back control to the system
This function restores the runtime environment saved by gint_install(). It
is only called when the add-in terminates. To temporarily leave gint during
execution, use gint_pause(). When possible, use syscalls without leaving
execution, use gint_switch(). When possible, use syscalls without leaving
gint for better performance. */
void gint_unload(void);
/* gint_pause() - return to main menu, with possibility of coming back
/* gint_switch() - temporarily switch out of gint
This function can be used to leave gint, restore the system's driver
context, and execute code there before returning to gint. By doing this one
can effectively interleave gint with the standard OS execution. The
limitations are quite extreme though, so unless you know precisely why
you're calling this function, you're likely doing it wrong.
This switch is used to get back to the main menu and to answer TLB misses.
To go back to the menu, use getkey(), or getkey_opt() with the GETKEY_MENU
flag set, or call gint_osmenu() for an immediate return.
@function Function to call in OS mode */
void gint_switch(void (*function)(void));
/* gint_osmenu() - switch out of gint and call the calculator's main menu
This function safely invokes the calculator's main menu by unloading gint.
If the user selects the gint application again in the menu, this function
@ -42,7 +57,7 @@ void gint_unload(void);
This function is typically called when the [MENU] key is pressed during a
getkey() call. */
void gint_pause(void);
void gint_osmenu(void);
//---
// Public functions

View File

@ -13,8 +13,14 @@
//---
/* gray_start(): Start the gray engine
The control of the screen is transferred to the engine; you should not use
dupdate() after this function, only gupdate(). */
dupdate() after this function, only gupdate().
The engine uses timer number GRAY_TIMER (equal to 0 by default) to run. If
the timer is unavailable, this function will fail. Otherwise, the gray
engine is started and any attempt to use timer 0 while it is running will be
denied by the timer module. */
void gray_start(void);
/* gray_stop(): Stop the gray engine

View File

@ -46,8 +46,9 @@ typedef volatile struct
uint8_t UNF :1; /* Underflow flag */
uint8_t UNIE :1; /* Underflow interrupt enable */
);
pad(19);
} GPACKED(4) etmu_t;
} GPACKED(0x10) etmu_t;
//---
// SH7705 Timer Unit. Refer to:
@ -67,7 +68,14 @@ typedef volatile struct
} GPACKED(4) sh7705_tmu_t;
#define SH7705_TMU (*((sh7705_tmu_t *)0xfffffe90))
#define SH7705_TMU (*(sh7705_tmu_t *)0xfffffe90)
//---
// SH7705 Extra Timer Unit. No official documentation exists.
//---
typedef volatile etmu_t sh7705_etmu_t[1];
#define SH7705_ETMU (*(sh7705_etmu_t *)0xa44c0030)
//---
// SH7305 Timer Unit. Refer to:
@ -86,4 +94,11 @@ typedef volatile struct
#define SH7305_TMU (*((sh7305_tmu_t *)0xa4490004))
//---
// SH7305 Extra Timer Unit. No official documentation exists.
//---
typedef volatile etmu_t sh7305_etmu_t[6];
#define SH7305_ETMU (*(sh7305_etmu_t *)0xa44d0030)
#endif /* GINT_MPU_TMU */

View File

@ -1,11 +0,0 @@
//---
// gint:syscalls - calls to CASIOWIN
//---
#ifndef GINT_SYSCALLS
#define GINT_SYSCALLS
/* __os_version(): Get OS version on the form MM.mm.iiii (10 bytes) */
void __os_version(char *version);
#endif /* GINT_SYSCALLS */

View File

@ -19,9 +19,8 @@
SH4-based: 9 timers, ids 0..8 [SH7305]
You should be aware that some of these timers are used by default by gint:
- Timer 1 is used by the gray engine on fx9860g.
- Timer 3 is used by the keyboard, unless GINT_RTC_KEYBOARD is defined. This
macro is controlled by the -rtc-keyboard switch when gint is compiled.
- Timer 0 is used by the gray engine on fx9860g.
- Timer 3/8 is used by the keyboard on SH3/SH4.
timer_setup() will fail if you try to use a timer that's already running.
Always check the return value of timer_setup()! Using a timer id that has
@ -152,16 +151,4 @@ void timer_stop(int timer);
int * and you must declare the variable as volatile int. */
int timer_timeout(volatile void *arg);
//---
// Low-level functions
//---
/* timer_address() - get the address of a timer structure
Returns a tmu_t if the id is 0, 1 or 2 and an etmu_t otherwise. The address
will be NULL if the requested timer does not exist.
@timer Requested timer
@TSTR If the requested timer is a TMU, set to the TSTR address */
void *timer_address(int timer, volatile uint8_t **TSTR);
#endif /* GINT_TIMER */

View File

@ -12,7 +12,6 @@
#include <gint/gint.h>
#include <gint/display.h>
#include <gint/syscalls.h>
#include <gint/clock.h>
#ifdef GINT_BOOT_LOG
@ -87,9 +86,12 @@ void bootlog_loaded(void)
print(8, 2, "---");
#endif
char os[11];
__os_version(os);
#ifdef FX9860G
char const *osversion = (void *)0x80010020;
char os[11] = { 0 };
for(int i = 0; i < 10; i++) os[i] = osversion[i];
print(12, 2, "OS%2s%2s%4s", os, os+3, os+6);
#endif
print(1, 3, "ROM%4dk RAM%3d+%1dk ??",
(rom_size + 0x3ff) >> 10,

View File

@ -8,6 +8,8 @@
#include <core/setup.h>
#include <gint/hardware.h>
#include <gint/mpu/intc.h>
#include <gint/defs/util.h>
#include <gint/display.h>
/* VBR address, from the linker script */
extern char gint_vbr[];
@ -25,6 +27,7 @@ extern gint_driver_t bdrv, edrv;
typedef struct
{
uint16_t iprs[12];
uint8_t masks[13];
} GPACKED(2) gint_core_ctx;
@ -32,20 +35,45 @@ typedef struct
@arg ctx gint core context object */
static void gint_ctx_save(gint_core_ctx *ctx)
{
if(isSH3()) for(int i = 0; i < 8; i++)
ctx->iprs[i] = *(SH7705_INTC.IPRS[i]);
else for(int i = 0; i < 12; i++)
ctx->iprs[i] = SH7305_INTC.IPRS[2 * i];
if(isSH3())
{
for(int i = 0; i < 8; i++)
ctx->iprs[i] = *(SH7705_INTC.IPRS[i]);
}
else
{
for(int i = 0; i < 12; i++)
ctx->iprs[i] = SH7305_INTC.IPRS[2 * i];
uint8_t *IMR = (void *)SH7305_INTC.MSK;
for(int i = 0; i < 13; i++, IMR += 4)
ctx->masks[i] = *IMR;
}
}
/* gint_ctx_restore() - restore interrupt controller configuration
@arg ctx gint core context object */
static void gint_ctx_restore(gint_core_ctx *ctx)
{
if(isSH3()) for(int i = 0; i < 8; i++)
*(SH7705_INTC.IPRS[i]) = ctx->iprs[i];
else for(int i = 0; i < 12; i++)
SH7305_INTC.IPRS[2 * i] = ctx->iprs[i];
if(isSH3())
{
for(int i = 0; i < 8; i++)
*(SH7705_INTC.IPRS[i]) = ctx->iprs[i];
}
else
{
for(int i = 0; i < 12; i++)
SH7305_INTC.IPRS[2 * i] = ctx->iprs[i];
/* Setting masks it a bit more involved than reading them */
uint8_t *IMCR = (void *)SH7305_INTC.MSKCLR;
uint8_t *IMR = (void *)SH7305_INTC.MSK;
for(int i = 0; i < 13; i++, IMR += 4, IMCR += 4)
{
*IMCR = 0xff;
*IMR = ctx->masks[i];
}
}
}
//---
@ -94,8 +122,6 @@ void gint_install(void)
/* unlock() - unlock interrupts, restoring system settings */
static void unlock(void)
{
gint_ctx_restore(&sys_ctx);
/* Restore all driver settings, but do it in reverse order of loading
to honor the dependency system */
for(gint_driver_t *drv = &edrv; (--drv) >= &bdrv;)
@ -103,6 +129,8 @@ static void unlock(void)
if(drv->unload) drv->unload();
if(drv->ctx_restore) drv->ctx_restore(drv->sys_ctx);
}
gint_ctx_restore(&sys_ctx);
}
/* gint_unload() - unload gint and give back control to the system */
@ -110,3 +138,100 @@ void gint_unload(void)
{
gint_setvbr(system_vbr, unlock);
}
//---
// Hot switching to operating system
//---
static gint_core_ctx gint_ctx;
extern gint_driver_t bdrv, edrv;
static void gint_switch_out(void)
{
/* Save all drivers in reverse order */
for(gint_driver_t *drv = &edrv; (--drv) >= &bdrv;)
{
if(!drv->ctx_save || !drv->ctx_restore) continue;
drv->ctx_save(drv->gint_ctx);
}
gint_ctx_save(&gint_ctx);
/* Restore the system context */
for(gint_driver_t *drv = &edrv; (--drv) >= &bdrv;)
{
if(!drv->ctx_save || !drv->ctx_restore) continue;
drv->ctx_restore(drv->sys_ctx);
}
gint_ctx_restore(&sys_ctx);
}
static void gint_switch_in(void)
{
/* Save system state again */
for(gint_driver_t *drv = &bdrv; drv < &edrv; drv++)
{
if(!drv->ctx_save || !drv->ctx_restore) continue;
drv->ctx_save(drv->sys_ctx);
}
gint_ctx_save(&sys_ctx);
/* Restore all drivers to their gint state */
for(gint_driver_t *drv = &bdrv; drv < &edrv; drv++)
{
if(!drv->ctx_save || !drv->ctx_restore) continue;
drv->ctx_restore(drv->gint_ctx);
}
gint_ctx_restore(&gint_ctx);
}
/* gint_switch() - temporarily switch out of gint */
void gint_switch(void (*function)(void))
{
gint_setvbr(system_vbr, gint_switch_out);
if(function) function();
gint_setvbr((uint32_t)&gint_vbr, gint_switch_in);
}
int __Timer_Install(int id, void (*handler)(void), int delay);
int __Timer_Start(int id);
int __Timer_Stop(int id);
int __Timer_Deinstall(int id);
int __PutKeyCode(short *matrixcode);
int __GetKeyWait(int *col,int *row,int type,int time,int menu,uint16_t *key);
void __ClearKeyBuffer(void); /* ? */
void *__GetVRAMAddress(void);
static int __osmenu_id;
static void __osmenu_handler(void)
{
short matrixcode = 0x0308;
__PutKeyCode(&matrixcode);
__Timer_Stop(__osmenu_id);
__Timer_Deinstall(__osmenu_id);
}
static void __osmenu(void)
{
__ClearKeyBuffer();
memcpy(__GetVRAMAddress(), gint_vram, 1024);
__osmenu_id = __Timer_Install(0, __osmenu_handler, 0 /* ms */);
if(__osmenu_id <= 0) return;
__Timer_Start(__osmenu_id);
int column, row;
unsigned short keycode;
__GetKeyWait(&column, &row, 0, 0 /* s */, 0, &keycode);
}
/* gint_osmenu() - switch out of gint and call the calculator's main menu */
void gint_osmenu(void)
{
/* TODO: return to menu on fxcg50 */
#ifdef FX9860G
gint_switch(__osmenu);
#endif
}

View File

@ -184,6 +184,11 @@ int start(int isappli, int optnum)
/* Unload gint and give back control to the system. Driver settings
will be restored while interrupts are disabled */
#ifdef FXCG50
/* TODO: Put that in the driver model, stupid! */
/* TODO: Also do it in gint_switch()! */
dma_transfer_wait(-1);
#endif
gint_unload();
/* TODO: Invoke main menu instead of returning? */

View File

@ -9,9 +9,6 @@
** * File system, because it's a mess and we might ruin the ROM.
*/
/* OS version */
.global ___os_version
/* Dynamic allocation */
.global _malloc
.global _free
@ -30,6 +27,16 @@
.global _BFile_FindNext
.global _BFile_FindClose
/* Return to menu */
.global ___Timer_Install
.global ___Timer_Start
.global ___Timer_Stop
.global ___Timer_Deinstall
.global ___PutKeyCode
.global ___GetKeyWait
.global ___ClearKeyBuffer
.global ___GetVRAMAddress
.section ".pretext"
#define syscall(id) \
@ -41,11 +48,6 @@
#ifdef FX9860G
/* OS version */
___os_version:
syscall(0x02ee)
/* Dynamic allocation */
_malloc:
@ -101,6 +103,25 @@ _BFile_FindNext:
_BFile_FindClose:
syscall(0x043d)
/* Return to menu */
___Timer_Install:
syscall(0x118)
___Timer_Start:
syscall(0x11a)
___Timer_Stop:
syscall(0x11b)
___Timer_Deinstall:
syscall(0x119)
___PutKeyCode:
syscall(0x24f)
___GetKeyWait:
syscall(0x247)
___ClearKeyBuffer:
syscall(0x241)
___GetVRAMAddress:
syscall(0x135)
syscall_table:
.long 0x80010070
@ -108,11 +129,6 @@ syscall_table:
#ifdef FXCG50
/* OS version */
___os_version:
syscall(0x1406)
/* Dynamic allocation */
_malloc:

View File

@ -154,10 +154,6 @@ gint_driver_t drv_cpg = {
.name = "CPG",
.init = init,
.status = GINT_DRIVER_STATUS(cpg_status),
.ctx_size = 0,
.sys_ctx = NULL,
.ctx_save = NULL,
.ctx_restore = NULL,
};
GINT_DECLARE_DRIVER(1, drv_cpg);

View File

@ -120,6 +120,13 @@ void dma_transfer(int channel, dma_size_t size, uint blocks,
/* dma_transfer_wait(): Wait for a transfer to finish */
void dma_transfer_wait(int channel)
{
if(channel < 0)
{
for(int channel = 0; channel < 6; channel++)
dma_transfer_wait(channel);
return;
}
channel_t *ch = dma_channel(channel);
if(!ch) return;
@ -211,7 +218,7 @@ static void init(void)
static void unload(void)
{
/* Make sure any DMA transfer is finished before leaving the app */
for(int i = 0; i < 6; i++) dma_transfer_wait(i);
dma_transfer_wait(-1);
}
//---
@ -227,10 +234,12 @@ typedef struct
} GPACKED(4) ctx_t;
/* One buffer for the system state will go in gint's .bss section */
GBSS static ctx_t sys_ctx;
GBSS static ctx_t sys_ctx, gint_ctx;
static void ctx_save(void *buf)
{
dma_transfer_wait(-1);
ctx_t *ctx = buf;
for(int i = 0; i < 6; i++)
@ -250,8 +259,16 @@ static void ctx_save(void *buf)
static void ctx_restore(void *buf)
{
dma_transfer_wait(-1);
ctx_t *ctx = buf;
/* Restore the supply status of the DMA0 clock first. If the DMA was
originally on standby, the context is basically trash so we don't
want to write that back. If it was originally running, we need to
power it up again for the writes to registers to have any effect */
POWER.MSTPCR0.DMAC0 = ctx->clock;
for(int i = 0; i < 6; i++)
{
channel_t *ch = dma_channel(i);
@ -262,9 +279,6 @@ static void ctx_restore(void *buf)
}
DMA.OR.word = ctx->OR;
/* Restore the supply status of the DMA0 clock */
POWER.MSTPCR0.DMAC0 = ctx->clock;
}
//---
@ -275,8 +289,8 @@ gint_driver_t drv_dma0 = {
.name = "DMA0",
.init = init,
.unload = unload,
.ctx_size = sizeof(ctx_t),
.sys_ctx = &sys_ctx,
.gint_ctx = &gint_ctx,
.ctx_save = ctx_save,
.ctx_restore = ctx_restore,
};

View File

@ -33,8 +33,8 @@ key_event_t getkey_opt(int opt, volatile int *timeout)
/* Keyboard time when the key was pressed */
static int rep_time = 0;
/* Reset the state if the repeated key went up due to different
graphical primitives being used */
/* Reset the state if the repeated key went up while getkey() was not
aware (this happens when different keyboard primitives are used) */
if(rep_key && !keydown(rep_key))
{
rep_key = 0;
@ -59,9 +59,10 @@ key_event_t getkey_opt(int opt, volatile int *timeout)
}
#endif
/* Return to menu. TODO: use gint_switch() in getkey_opt() */
/* Return to menu */
if(opt & GETKEY_MENU && key == KEY_MENU && !(alpha || shift))
{
gint_osmenu();
continue;
}

View File

@ -24,7 +24,7 @@
Events can be seen as a delta-encoding of the keyboard state over time.
To ensure that adding pending events to the last-read state always gives the
internal driger state, this array is not updated if the generation of an
internal driver state, this array is not updated if the generation of an
event fails. (Most likely the even will be regenerated at the next scan.) */
GDATA static volatile uint8_t state[12] = { 0 };
@ -34,13 +34,13 @@ GDATA static volatile uint8_t state[12] = { 0 };
user's view of the keyboard. */
GDATA static uint8_t current[12] = { 0 };
/* A driver event, which is a change in a full row instead of a single key. */
/* A driver event, which is a common change in a full row */
typedef struct
{
uint16_t time; /* Locally unique time identifier */
uint8_t row; /* Row number */
uint8_t changed; /* Keys that changed state */
uint8_t state; /* Key state for the new row */
uint8_t kind; /* Type of change, either KEYEV_DOWN or KEYEV_UP */
} driver_event_t;
@ -93,22 +93,26 @@ static void keysc_frame(void)
for(int i = 0; i < 6; i++) array[i] = KEYSC[i];
}
/* Compare new data with the internal state. Push releases before
presses so that a key change occuring within a single analysis frame
can be performed. This happens all the time when going back to the
main MENU via gint_osmenu() on a keybind. */
for(int row = 0; row < 12; row++)
{
/* Compare new data with the internal state. */
int old = state[row];
int new = scan[row];
if(old == new) continue;
driver_event_t ev = {
.time = time,
.row = row,
.changed = old ^ new,
.state = new,
};
int diff = ~scan[row] & state[row];
if(!diff) continue;
/* Update internal status if the event could be pushed */
if(!buffer_push(ev)) state[row] = new;
driver_event_t ev = { time, row, diff, KEYEV_UP };
if(!buffer_push(ev)) state[row] &= scan[row];
}
for(int row = 0; row < 12; row++)
{
int diff = scan[row] & ~state[row];
if(!diff) continue;
driver_event_t ev = { time, row, diff, KEYEV_DOWN };
if(!buffer_push(ev)) state[row] |= scan[row];
}
}
@ -149,22 +153,18 @@ key_event_t pollevent(void)
/* Generate new key events and return the first of them*/
int changed = ev.changed;
int state = ev.state;
for(int code = ((ev.row ^ 1) << 4) | 0x7; code & 0x7; code--)
{
if(changed & 1)
{
key_event_t keyev = {
.time = ev.time,
.type = 2 - (state & 1),
.type = ev.kind,
.key = code,
};
events[events_pending++] = keyev;
}
changed >>= 1;
state >>= 1;
}
/* Call recursively to avoid duplicating the event emission code */
@ -263,7 +263,6 @@ static void init(void)
/* Configure the timer to do 128 keyboard scans per second. This
frequency *must* be high for the KEYSC interface to work! */
/* Note: the supporting timer always runs at 32768 Hz. */
/* TODO: The SH3 does not need to run fast, adjust user settings? */
int delay = 32768 / KEYBOARD_SCAN_FREQUENCY;
if(!delay) delay = 1;
@ -305,10 +304,6 @@ gint_driver_t drv_keysc = {
.init = init,
.status = GINT_DRIVER_STATUS(keysc_status),
.unload = unload,
.ctx_size = 0,
.sys_ctx = NULL,
.ctx_save = NULL,
.ctx_restore = NULL,
};
GINT_DECLARE_DRIVER(4, drv_keysc);

View File

@ -3,6 +3,7 @@
//---
#include <gint/defs/types.h>
#include <gint/defs/util.h>
#include <gint/hardware.h>
#include <gint/drivers.h>
#include <gint/dma.h>
@ -52,8 +53,6 @@ typedef word_union(entry_mode_t,
// Device communication primitives
//---
#define synco() __asm__ volatile (".word 0x00ab":::"memory")
/* Interface with the controller */
GDATA static volatile uint16_t *intf = (void *)0xb4000000;
/* Bit 4 of Port R controls the RS bit of the display driver */
@ -246,7 +245,7 @@ typedef struct
} GPACKED(2) ctx_t;
/* Allocate one buffer in gint's storage section */
GBSS static ctx_t sys_ctx;
GBSS static ctx_t sys_ctx, gint_ctx;
static void ctx_save(void *buf)
{
@ -302,8 +301,8 @@ gint_driver_t drv_r61524 = {
.name = "R61524",
.init = init,
.status = GINT_DRIVER_STATUS(r61524_status),
.ctx_size = sizeof(ctx_t),
.sys_ctx = &sys_ctx,
.gint_ctx = &gint_ctx,
.ctx_save = ctx_save,
.ctx_restore = ctx_restore,
};

View File

@ -162,7 +162,7 @@ typedef struct
uint8_t RCR2;
} ctx_t;
GBSS static ctx_t sys_ctx;
GBSS static ctx_t sys_ctx, gint_ctx;
static void ctx_save(void *buf)
{
@ -174,8 +174,8 @@ static void ctx_save(void *buf)
static void ctx_restore(void *buf)
{
ctx_t *ctx = buf;
ctx->RCR1 = RTC->RCR1.byte;
ctx->RCR2 = RTC->RCR2.byte;
RTC->RCR1.byte = ctx->RCR1;
RTC->RCR2.byte = ctx->RCR2;
}
//---
@ -186,8 +186,8 @@ gint_driver_t drv_rtc = {
.name = "RTC",
.driver_sh3 = GINT_DRIVER_SH3(driver_sh3),
.init = init,
.ctx_size = sizeof(ctx_t),
.sys_ctx = &sys_ctx,
.gint_ctx = &gint_ctx,
.ctx_save = ctx_save,
.ctx_restore = ctx_restore,
};

View File

@ -11,7 +11,7 @@ static void memcpy4(uint32_t * restrict d, const void * restrict src, size_t n)
if(!modulo)
{
const uint32_t *s = src;
while(n--) *d++ = *s++;
for(; n; n-=4) *d++ = *s++;
}
#if 0
@ -41,7 +41,7 @@ static void memcpy4(uint32_t * restrict d, const void * restrict src, size_t n)
const uint16_t *s = src;
uint16_t * restrict dst = (void *)d;
while(n--)
for(; n; n-=2)
{
*dst++ = *s++;
*dst++ = *s++;
@ -74,7 +74,7 @@ void *memcpy(void * restrict dst, const void * restrict src, size_t n)
while((uintptr_t)d & 3) *d++ = *s++, n--;
/* Perform the big, efficient copy */
memcpy4((void *)d, s, (n >> 2));
memcpy4((void *)d, s, n & ~3);
size_t m = n & 3;
d += (n - m);

View File

@ -9,15 +9,13 @@
#include <gint/defs/types.h>
#include <gint/hardware.h>
#include <gint/syscalls.h>
//---
// Device specification sheet
//---
/* This version number is 1 for the old T6K11 everyone knows, and 2 for the
newer one found in the Graph 35+E II. Documentation is available only for
version 1. Dumps of Bdisp_PutDisp_DD() are used to driver version 2. */
version 1. Dumps of Bdisp_PutDisp_DD() are used to drive version 2. */
static int t6k11_version = 1;
/* Screen registers on the original T6K11. Registers 8..11 and 13..15 are test
@ -197,7 +195,7 @@ typedef struct
} GPACKED(1) ctx_t;
/* Pre-allocate a context in gint's uninitialized section */
GBSS static ctx_t sys_ctx;
GBSS static ctx_t sys_ctx, gint_ctx;
static void ctx_save(void *buf)
{
@ -256,8 +254,8 @@ gint_driver_t drv_t6k11 = {
.name = "T6K11",
.init = init,
.status = GINT_DRIVER_STATUS(t6k11_status),
.ctx_size = sizeof(ctx_t),
.sys_ctx = &sys_ctx,
.gint_ctx = &gint_ctx,
.ctx_save = ctx_save,
.ctx_restore = ctx_restore,
};

View File

@ -14,10 +14,9 @@
#include <gint/defs/types.h>
//---
// Timer structures
// Driver storage
//---
/* inth_data_t - data storage inside interrupt handlers */
typedef struct
{
@ -27,34 +26,14 @@ typedef struct
} GPACKED(4) inth_data_t;
/* timer_t - all data required to run a single timer */
typedef struct
{
void *tmu; /* Address of timer structure */
inth_data_t *data; /* Interrupt handler data */
uint16_t event; /* Interrupt event code */
} timer_t;
//---
// Driver storage
//---
/* This is the description of the structure on SH4. SH3-based fx9860g models,
which are already very rare, will adapt the values in init functions */
GDATA static timer_t timers[9] = {
{ (void *)0xa4490008, NULL, 0x400 },
{ (void *)0xa4490014, NULL, 0x420 },
{ (void *)0xa4490020, NULL, 0x440 },
{ (void *)0xa44d0030, NULL, 0x9e0 },
{ (void *)0xa44d0050, NULL, 0xc20 },
{ (void *)0xa44d0070, NULL, 0xc40 },
{ (void *)0xa44d0090, NULL, 0x900 },
{ (void *)0xa44d00b0, NULL, 0xd00 },
{ (void *)0xa44d00d0, NULL, 0xfa0 },
};
/* This array references the storage areas of all timer handlers */
GDATA static inth_data_t *timers[9] = { NULL };
/* Arrays of standard and extra timers */
GDATA static tmu_t *TMU = SH7305_TMU.TMU;
GDATA static etmu_t *ETMU = SH7305_ETMU;
/* TSTR register for standard timers */
GDATA static volatile uint8_t *TSTR = (void *)0xa4490004;
GDATA static volatile uint8_t *TSTR = &SH7305_TMU.TSTR;
//---
// Timer API
@ -64,60 +43,52 @@ GDATA static volatile uint8_t *TSTR = (void *)0xa4490004;
int timer_setup(int id, uint32_t delay, timer_input_t clock,
int (*callback)(volatile void *arg), volatile void *arg)
{
/* We need to distinguish normal and extra timers */
/* Timer is not installed (TCR address is really required) */
if(!timers[id]) return -1;
if(id < 3)
{
/* Refuse to setup timers that are already in use */
tmu_t *t = timers[id].tmu;
if(t->TCR.UNIE) return -1;
tmu_t *T = &TMU[id];
if(T->TCR.UNIE || *TSTR & (1 << id)) return -1;
/* Configure the registers of the target timer */
t->TCOR = delay;
t->TCNT = delay;
t->TCR.TPSC = clock;
/* Clear the interrupt flag */
do t->TCR.UNF = 0;
while(t->TCR.UNF);
/* Configure the counter, clear interrupt flag*/
T->TCOR = delay;
T->TCNT = delay;
T->TCR.TPSC = clock;
do T->TCR.UNF = 0;
while(T->TCR.UNF);
/* Enable interrupt and count on rising edge (SH7705) */
t->TCR.UNIE = 1;
t->TCR.CKEG = 0;
T->TCR.UNIE = 1;
T->TCR.CKEG = 0;
}
/* Extra timers have a simpler structure */
else
{
etmu_t *t = timers[id].tmu;
if(t->TCR.UNIE) return -1;
etmu_t *T = &ETMU[id-3];
if(T->TCR.UNIE) return -1;
/* Clear the interrupt flag */
do t->TCR.UNF = 0;
while(t->TCR.UNF);
/* No clock input and clock edge here. But TCR and TCNT need a
delay to retain the value */
do T->TCR.UNF = 0;
while(T->TCR.UNF);
/* There is no clock input and no clock edge settings */
t->TCOR = delay;
do T->TCOR = delay;
while(T->TCOR != delay);
/* TODO: FXCG50: does not always work on first try */
do t->TCNT = delay;
while(t->TCNT != delay);
do T->TCNT = delay;
while(T->TCNT != delay);
t->TCR.UNIE = 1;
T->TCR.UNIE = 1;
}
/* Register the callback and its argument */
if(timers[id].data)
{
timers[id].data->cb = callback;
timers[id].data->arg = arg;
}
/* Return the timer id, since configuration was successful */
timers[id]->cb = callback;
timers[id]->arg = arg;
return id;
}
/* timer_delay() - compute a delay constant from a duration in seconds */
uint32_t timer_delay(int tid, uint64_t delay_us)
uint32_t timer_delay(int id, uint64_t delay_us)
{
/* TODO: Proper timer_delay() */
const clock_frequency_t *clock = clock_freq();
@ -128,7 +99,7 @@ uint32_t timer_delay(int tid, uint64_t delay_us)
// uint64_t freq = 29020000 >> 2;
/* Extra timers all run at 32768 Hz */
if(tid >= 3) freq = 32768;
if(id >= 3) freq = 32768;
uint64_t product = freq * delay_us;
return product / 1000000;
@ -139,10 +110,8 @@ uint32_t timer_delay(int tid, uint64_t delay_us)
@state 0 to start the timer, 1 to stop it (nothing else!) */
static void timer_control(int id, int state)
{
/* For standard timers, use the MPU's TSTR register */
if(id < 3) *TSTR = (*TSTR | (1 << id)) ^ (state << id);
/* Extra timers all have their own TSTR register */
else ((etmu_t *)timers[id].tmu)->TSTR = state ^ 1;
else ETMU[id-3].TSTR = state ^ 1;
}
/* timer_start() - start a configured timer */
@ -154,8 +123,8 @@ void timer_start(int id)
/* timer_reload() - change a timer's delay constant for next interrupts */
void timer_reload(int id, uint32_t delay)
{
if(id < 3) ((tmu_t *)timers[id].tmu)->TCOR = delay;
else ((etmu_t *)timers[id].tmu)->TCOR = delay;
if(id < 3) TMU[id].TCOR = delay;
else ETMU[id-3].TCOR = delay;
}
/* timer_pause() - stop a running timer */
@ -164,7 +133,7 @@ void timer_pause(int id)
timer_control(id, 1);
}
/* timer_stp() - stop and free a timer */
/* timer_stop() - stop and free a timer */
void timer_stop(int id)
{
/* Stop the timer and disable UNIE to indicate that it's free */
@ -172,27 +141,24 @@ void timer_stop(int id)
if(id < 3)
{
tmu_t *t = timers[id].tmu;
t->TCR.UNIE = 0;
/* Clear TCOR and TCNT */
t->TCOR = 0xffffffff;
t->TCNT = 0xffffffff;
TMU[id].TCR.UNIE = 0;
TMU[id].TCOR = 0xffffffff;
TMU[id].TCNT = 0xffffffff;
}
else
{
etmu_t *t = timers[id].tmu;
t->TCR.UNIE = 0;
etmu_t *T = &ETMU[id-3];
T->TCR.UNIE = 0;
/* Also clear TCOR and TCNT to avoid spurious interrupts */
t->TCOR = 0xffffffff;
do T->TCOR = 0xffffffff;
while(T->TCOR + 1);
/* TODO: FXCG50: Again */
do t->TCNT = 0xffffffff;
while(t->TCNT + 1);
do T->TCNT = 0xffffffff;
while(T->TCNT + 1);
do t->TCR.UNF = 0;
while(t->TCR.UNF);
do T->TCR.UNF = 0;
while(T->TCR.UNF);
}
}
@ -205,8 +171,6 @@ int timer_timeout(volatile void *arg)
{
volatile int *x = arg;
(*x)++;
/* Always stop after firing once */
return 1;
}
@ -214,23 +178,15 @@ int timer_timeout(volatile void *arg)
// Low-level functions
//---
/* timer_address() - get the address of a timer structure */
void *timer_address(int timer, volatile uint8_t **TSTR_arg)
{
if((uint)timer < 2 && TSTR_arg) *TSTR_arg = TSTR;
return (uint)timer < timer_count() ? timers[timer].tmu : NULL;
}
/* timer_clear() - clear an ETMU flag and possibly stop the timer
@timer Timer ID, must be an ETMU
@stop Non-zero to stop the timer */
void timer_clear(int timer, int stop)
void timer_clear(int id, int stop)
{
etmu_t *t = timers[timer].tmu;
do t->TCR.UNF = 0;
while(t->TCR.UNF);
do ETMU[id-3].TCR.UNF = 0;
while(ETMU[id-3].TCR.UNF);
if(stop) timer_stop(timer);
if(stop) timer_stop(id);
}
//---
@ -247,79 +203,63 @@ extern void inth_etmux(void);
#ifdef FX9860G
static void driver_sh3(void)
{
timers[0].tmu = (void *)0xfffffe94;
timers[1].tmu = (void *)0xfffffea0;
timers[2].tmu = (void *)0xfffffeac;
/* We must not change the event code of ETMU0 since it's translated to
its SH4 counterpart by the interrupt handler */
timers[3].tmu = (void *)0xa44c0030;
TSTR = (void *)0xfffffe92;
TMU = SH7705_TMU.TMU;
ETMU = SH7705_ETMU;
TSTR = &SH7705_TMU.TSTR;
}
#endif /* FX9860G */
static void init(void)
{
/* Install the standard TMU's interrupt handlers */
void *h = gint_inthandler(0x400, inth_tmu, 128);
/* User information in interrupt handlers */
timers[0].data = h + 84;
timers[1].data = h + 104;
timers[2].data = h + 116;
/* Stop all timers */
uint16_t etmu_event[6] = { 0x9e0, 0xc20, 0xc40, 0x900, 0xd00, 0xfa0 };
*TSTR = 0;
/* This driver uses the UNIE (UNderflow Interrupt Enable) bit of the
TCR register to indicate which timers are being used; reset them */
for(int i = 0; i < 3; i++)
/* Install the standard TMU's interrupt handlers */
void *h = gint_inthandler(0x400, inth_tmu, 128);
timers[0] = h + 84;
timers[1] = h + 104;
timers[2] = h + 116;
/* Clear every timer to avoid surprises */
for(int id = 0; id < 3; id++)
{
tmu_t *t = timers[i].tmu;
TMU[id].TCOR = 0xffffffff;
TMU[id].TCNT = 0xffffffff;
t->TCOR = 0xffffffff;
t->TCNT = 0xffffffff;
do t->TCR.word = 0;
while(t->TCR.word);
do TMU[id].TCR.word = 0;
while(TMU[id].TCR.word);
/* Standard timers: TCR is provided to the interrupt handler */
timers[i].data->TCR = &t->TCR;
timers[id]->TCR = &TMU[id].TCR;
}
/* Clear the extra timers */
for(int i = 3; i < timer_count(); i++)
for(int id = 0; id < timer_count()-3; id++)
{
etmu_t *t = timers[i].tmu;
etmu_t *T = &ETMU[id];
/* 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.
This may be related to difficulties when setting TCNT. */
behaviour, but for safety I'll set TCOR/TCNT to non-zero. */
T->TSTR = 0;
do T->TCOR = 0xffffffff;
while(T->TCOR + 1);
t->TSTR = 0;
t->TCOR = 0xffffffff;
/* TODO: FXCG50: Safety */
do t->TCNT = 0xffffffff;
while(t->TCNT + 1);
/* Clear interrupts */
do t->TCR.byte = 0;
while(t->TCR.byte);
/* 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. We need three extra timers for the
interrupt handlers to work, so install 3 on SH3, even if only one
actually exists */
/* Install the extra timers. The interrupt handler takes 3 gates, so we
install 3 even on SH3 where there's only one */
int limit = isSH3() ? 6 : 9;
for(int i = 3; i < limit; i++)
{
void *handler = (i == 5) ? inth_etmu2 : inth_etmux;
void *h = gint_inthandler(timers[i].event, handler, 32);
void *h = gint_inthandler(etmu_event[i-3], handler, 32);
timers[i].data = h + 24;
timers[i] = h + 24;
if(i == 5) continue;
uint32_t *data_id = (h + 20);
@ -330,15 +270,12 @@ static void init(void)
gint_inthandler(0xc60, inth_etmu_help, 32);
/* Enable TMU0 at level 13, TMU1 at level 11, TMU2 at level 9 */
gint_intlevel(0, 13);
gint_intlevel(0, 7);
gint_intlevel(1, 11);
gint_intlevel(2, 9);
/* Enable the extra TMUs at level 7 */
if(isSH3())
{
gint_intlevel(23, 7);
}
if(isSH3()) gint_intlevel(23, 7);
else
{
/* Unmask the standard timers' interrupts */
@ -363,15 +300,15 @@ static void init(void)
gint[HWTMU] = HW_LOADED;
gint[HWETMU] = HW_LOADED | (isSH3() ? HWETMU_1 : HWETMU_6);
for(int i = 3; i < timer_count(); i++)
for(int i = 0; i < timer_count() - 3; i++)
{
etmu_t *t = timers[i].tmu;
int v = !(t->TCOR + 1)
&& !(t->TCNT + 1)
&& !(t->TSTR)
&& !(t->TCR.UNF)
&& !(t->TCR.UNIE);
gint[HWETMU] |= v << (i - 1);
etmu_t *T = &ETMU[i];
int v = !(T->TCOR + 1)
&& !(T->TCNT + 1)
&& !(T->TSTR)
&& !(T->TCR.UNF)
&& !(T->TCR.UNIE);
gint[HWETMU] |= v << (i + 2);
}
}
@ -380,7 +317,6 @@ static void init(void)
//---
#ifdef GINT_BOOT_LOG
/* tmu_status() - status string of extra TMUs for the boot log
The status string has a two-character code for each of the extra timers.
@ -401,13 +337,13 @@ static const char *tmu_status(void)
static char status[18] = "ETMU ";
int j = 5;
for(int i = 3; i < timer_count(); i++)
for(int i = 0; i < timer_count()-3; i++)
{
etmu_t *t = timers[i].tmu;
int v1 = (!(t->TCOR + 1))
| (!(t->TCNT + 1) << 1)
| (!(t->TSTR) << 2);
int v2 = (t->TCR.UNF << 1) | (t->TCR.UNIE);
etmu_t *T = &ETMU[i];
int v1 = (!(T->TCOR + 1))
| (!(T->TCNT + 1) << 1)
| (!(T->TSTR) << 2);
int v2 = (T->TCR.UNF << 1) | (T->TCR.UNIE);
status[j++] = '0' + v1;
status[j++] = "DLH!"[v2];
@ -416,7 +352,6 @@ static const char *tmu_status(void)
return status;
}
#endif /* GINT_BOOT_LOG */
//---
@ -428,63 +363,63 @@ typedef struct
tmu_t std[3];
etmu_t extra[6];
uint8_t TSTR;
} GPACKED(4) ctx_t;
} ctx_t;
/* Allocate a system buffer in gint's BSS area */
GBSS static ctx_t sys_ctx;
GBSS static ctx_t sys_ctx, gint_ctx;
static void ctx_save(void *buf)
{
ctx_t *ctx = buf;
ctx->TSTR = *TSTR;
for(int i = 0; i < 3; i++)
{
tmu_t *t = timers[i].tmu;
ctx->std[i].TCOR = t->TCOR;
ctx->std[i].TCNT = t->TCNT;
ctx->std[i].TCR.word = t->TCR.word;
tmu_t *c = &ctx->std[i];
c->TCOR = TMU[i].TCOR;
c->TCNT = TMU[i].TCNT;
c->TCR.word = TMU[i].TCR.word;
}
ctx->TSTR = *TSTR;
for(int i = 0; i < timer_count() - 3; i++)
{
etmu_t *t = timers[i + 3].tmu;
ctx->extra[i].TCOR = t->TCOR;
ctx->extra[i].TCNT = t->TCNT;
ctx->extra[i].TCR.byte = t->TCR.byte;
ctx->extra[i].TSTR = t->TSTR;
etmu_t *T = &ETMU[i], *c = &ctx->extra[i];
/* Don't snapshot an interrupt state, because the timer state
is sometimes garbage protected by a masked interrupt. */
c->TCOR = T->TCOR ? T->TCOR : 0xffffffff;
c->TCNT = T->TCNT ? T->TCNT : c->TCOR;
c->TCR.byte = T->TCR.byte & 0xd;
c->TSTR = T->TSTR;
}
}
static void ctx_restore(void *buf)
{
ctx_t *ctx = buf;
*TSTR = ctx->TSTR;
for(int i = 0; i < 3; i++)
{
tmu_t *t = timers[i].tmu;
t->TCNT = ctx->std[i].TCNT;
t->TCOR = ctx->std[i].TCOR;
t->TCR.word = ctx->std[i].TCR.word;
tmu_t *c = &ctx->std[i];
TMU[i].TCNT = c->TCNT;
TMU[i].TCOR = c->TCOR;
TMU[i].TCR.word = c->TCR.word;
}
*TSTR = ctx->TSTR;
for(int i = 0; i < timer_count() - 3; i++)
{
etmu_t *t = timers[i + 3].tmu;
etmu_t *T = &ETMU[i], *c = &ctx->extra[i];
/* This thing is being unloaded while interrupts are disabled,
so we don't have to heed for ctx->extra[i].TCNT = 0, which
can generate interrupts. I tried to do t->TCNT = 0xffffffff
then restore ctx->extra[i], but it causes hangs on SH3 when
the overclock level is too high. */
t->TCNT = ctx->extra[i].TCNT;
t->TCOR = ctx->extra[i].TCOR;
t->TCR.byte = ctx->extra[i].TCR.byte;
t->TSTR = ctx->extra[i].TSTR;
do T->TCOR = c->TCOR;
while(T->TCOR != c->TCOR);
T->TSTR = c->TSTR;
/* Remember that TCNT and TCR take time to write to */
do T->TCNT = c->TCNT;
while(T->TCNT != c->TCNT);
do T->TCR.byte = c->TCR.byte;
while(T->TCR.byte != c->TCR.byte);
}
}
@ -497,8 +432,8 @@ gint_driver_t drv_tmu = {
.driver_sh3 = GINT_DRIVER_SH3(driver_sh3),
.init = init,
.status = GINT_DRIVER_STATUS(tmu_status),
.ctx_size = sizeof(ctx_t),
.sys_ctx = &sys_ctx,
.gint_ctx = &gint_ctx,
.ctx_save = ctx_save,
.ctx_restore = ctx_restore,
};