/** * \file sys.h * \brief Entry point unit / Interrupt callback / System * \author Stephane Dallongeville * \date 08/2011 * * This unit contains SGDK initialization / reset methods, IRQ callbacks and others system stuff. */ #ifndef _SYS_H_ #define _SYS_H_ #define PROCESS_PALETTE_FADING (1 << 0) #define PROCESS_BITMAP_TASK (1 << 1) #define PROCESS_DMA_TASK (1 << 2) #define PROCESS_VDP_SCROLL_TASK (1 << 3) #define ROM_ALIGN_BIT 17 #define ROM_ALIGN (1 << ROM_ALIGN_BIT) #define ROM_ALIGN_MASK (ROM_ALIGN - 1) #define ROM_START ROM #define ROM_END (((u32) &_stext) + ((u32) &_sdata)) #define ROM_SIZE ((ROM_END + ROM_ALIGN_MASK) & (~ROM_ALIGN_MASK)) /** * \brief * To force method inlining (not sure that GCC does actually care of it) */ #define FORCE_INLINE inline __attribute__((always_inline)) /** * \brief * To force no inlining for this method */ #define NO_INLINE __attribute__((noinline)) /** * \brief * Put function in .data (RAM) instead of the default .text */ #define RAM_SECT __attribute__((section(".ramprog"))) /** * \brief * Declare function for the hint callback (generate a RTE to return from interrupt instead of RTS) */ #define HINTERRUPT_CALLBACK __attribute__((interrupt)) void /** * \brief * Macro for packing structures and enumerates */ #define PACKED __attribute__((__packed__)) // exist through rom_head.c typedef struct { char console[16]; /* Console Name (16) */ char copyright[16]; /* Copyright Information (16) */ char title_local[48]; /* Domestic Name (48) */ char title_int[48]; /* Overseas Name (48) */ char serial[14]; /* Serial Number (2, 12) */ u16 checksum; /* Checksum (2) */ char IOSupport[16]; /* I/O Support (16) */ u32 rom_start; /* ROM Start Address (4) */ u32 rom_end; /* ROM End Address (4) */ u32 ram_start; /* Start of Backup RAM (4) */ u32 ram_end; /* End of Backup RAM (4) */ char sram_sig[2]; /* "RA" for save ram (2) */ u16 sram_type; /* 0xF820 for save ram on odd bytes (2) */ u32 sram_start; /* SRAM start address - normally 0x200001 (4) */ u32 sram_end; /* SRAM end address - start + 2*sram_size (4) */ char modem_support[12]; /* Modem Support (24) */ char notes[40]; /* Memo (40) */ char region[16]; /* Country Support (16) */ } ROMHeader; extern const ROMHeader rom_header; // size of text segment --> start of initialized data (RO) extern u32 _stext; // size of initialized data segment extern u32 _sdata; /** * \brief * Define at which period to do VBlank process (see #SYS_doVBlankProcess() method) */ typedef enum { IMMEDIATELY, /** Start VBlank process immediately whatever we are in blanking period or not */ ON_VBLANK , /** Start VBlank process on VBlank period, start immediatly in we are already in VBlank */ ON_VBLANK_START /** Start VBlank process on VBlank *start* period, means that we wait the next *start* of VBlank period if we missed it */ } VBlankProcessTime; #if LEGACY_ERROR_HANDLER /** * \brief * Bus error interrupt callback. * * You can modify it to use your own callback (for debug purpose). */ extern VoidCallback *busErrorCB; /** * \brief * Address error interrupt callback. * * You can modify it to use your own callback (for debug purpose). */ extern VoidCallback *addressErrorCB; /** * \brief * Illegal instruction exception callback. * * You can modify it to use your own callback (for debug purpose). */ extern VoidCallback *illegalInstCB; /** * \brief * Division by zero exception callback. * * You can modify it to use your own callback (for debug purpose). */ extern VoidCallback *zeroDivideCB; /** * \brief * CHK instruction interrupt callback. * * You can modify it to use your own callback (for debug purpose). */ extern VoidCallback *chkInstCB; /** * \brief * TRAPV instruction interrupt callback. * * You can modify it to use your own callback (for debug purpose). */ extern VoidCallback *trapvInstCB; /** * \brief * Privilege violation exception callback. * * You can modify it to use your own callback (for debug purpose). */ extern VoidCallback *privilegeViolationCB; /** * \brief * Trace interrupt callback. * * You can modify it to use your own callback (for debug purpose). */ extern VoidCallback *traceCB; /** * \brief * Line 1x1x exception callback. * * You can modify it to use your own callback (for debug purpose). */ extern VoidCallback *line1x1xCB; /** * \brief * Error exception callback. * * You can modify it to use your own callback (for debug purpose). */ extern VoidCallback *errorExceptionCB; #endif /** * \brief * Level interrupt callback. * * You can modify it to use your own callback. */ extern VoidCallback *intCB; /** * \brief * Assert reset * * Assert reset pin on the 68000 CPU. * This is needed to reset some attached hardware. */ void SYS_assertReset(void); /** * \brief * Soft reset * * Software reset */ void SYS_reset(void); /** * \brief * Hard reset * * Reset with forced hardware init and memory clear / reset operation. */ void SYS_hardReset(void); /** * \brief * Wait for start of VBlank and do all the VBlank processing (DMA transfers, XGM driver tempo, Joypad pooling..) * \return FALSE if process was canceled because the method was called from V-Int (vertical interrupt) callback * in which case we exit the function as V-Int will be triggered immediately.
* * Do all the SGDK VBlank process.
* Some specific processing should be done during the Vertical Blank period as the VDP is idle at this time. * This is always where we should do all VDP data transfer (using the DMA preferably) but we can also do the processes which * has to be done at a frame basis (joypad polling, sound driver sync/update..)
* In the case of SGDK, calling this method will actually do the following tasks:
* - flush the DMA queue
* - process asynchronous palette fading operation
* - joypad polling
*
* Note that VBlank process may be delayed to next VBlank if we missed the start of the VBlank period so that will cause a frame miss. */ bool SYS_doVBlankProcess(void); /** * \brief * Do all the VBlank processing (DMA transfers, XGM driver tempo, Joypad pooling..) * \param processTime * Define at which period we start VBlank process, accepted values are:
* IMMEDIATELY Start VBlank process immediatly whatever we are in blanking period or not * (*highly discouraged* unless you really know what you're doing !)
* ON_VBLANK Start VBlank process on VBlank period, if we already are in VBlank period * it starts immediately (discouraged as VBlank period may be shortened and all * processes cannot be completed in time)
* ON_VBLANK_START Start VBlank process on VBlank *start* period (recommanded as default value). * That means that if #SYS_doVBlankProcess() is called too late (after the start * of VBlank) then we force a passive wait for the next start of VBlank so we can * align the processing with the beggining of VBlank period to ensure fast DMA * transfert and avoid possible graphical glitches due to VRAM update during active display.
* \return FALSE if process was canceled because we forced Start VBlank process (time = ON_VBLANK_START) * and the method was called from V-Int (vertical interrupt) callback in which case we exit the function * as V-Int will be triggered immediately.
* * Wait for Vblank and does all the SGDK VBlank process.
* Some specific processing should be done during the Vertical Blank period as the VDP is idle at this time. * This is always where we should do all VDP data transfer (using the DMA preferably) but we can also do the processes which * has to be done at a frame basis (joypad polling, sound driver sync/update..)
* In the case of SGDK, calling this method will actually do the following tasks:
* - flush the DMA queue
* - process asynchronous palette fading operation
* - joypad polling
*
* Note that depending the used time parameter, VBlank process may be delayed to next VBlank so that will wause a frame miss. */ bool SYS_doVBlankProcessEx(VBlankProcessTime processTime); /** * \brief * End the current frame (alias for #SYS_doVBlankProcess(void)). * * End the current frame and does all the internal SGDK VBlank process (DMA flush, VDP data upload, async palette fade, scroll update..) * * \see SYS_doVBlankProcess(void) */ bool SYS_nextFrame(void); /** * \brief * Returns the current value of the stack pointer register (A7) */ u32 SYS_getStackPointer(); /** * \brief * Return current interrupt mask level. * * See SYS_setInterruptMaskLevel() for more informations about interrupt mask level. */ u16 SYS_getInterruptMaskLevel(void); /** * \brief * Set interrupt mask level. * * You can disable interrupt depending their level.
* Interrupt with level <= interrupt mask level are ignored.
* We have 3 different interrupts:
* Vertical interrupt (V-INT): level 6
* Horizontal interrupt (H-INT): level 4
* External interrupt (EX-INT): level 2
* Vertical interrupt has the highest level (and so priority) where external interrupt has lowest one.
* For instance to disable Vertical interrupt just use SYS_setInterruptMaskLevel(6).
* * \see SYS_getInterruptMaskLevel() * \see SYS_getAndSetInterruptMaskLevel() * \see SYS_setVIntCallback() * \see SYS_setHIntCallback() */ void SYS_setInterruptMaskLevel(u16 value); /** * \brief * Set the interrupt mask level to given value and return previous level. * * You can disable interrupt depending their level.
* Interrupt with level <= interrupt mask level are ignored.
* We have 3 different interrupts:
* Vertical interrupt (V-INT): level 6
* Horizontal interrupt (H-INT): level 4
* External interrupt (EX-INT): level 2
* Vertical interrupt has the highest level (and so priority) where external interrupt has lowest one.
* For instance to disable Vertical interrupt just use SYS_setInterruptMaskLevel(6).
* * \see SYS_getInterruptMaskLevel() * \see SYS_setInterruptMaskLevel() * \see SYS_setVIntCallback() * \see SYS_setHIntCallback() */ u16 SYS_getAndSetInterruptMaskLevel(u16 value); /** * \brief * Disable interrupts (Vertical, Horizontal and External). * * * This method is used to temporary disable interrupts to protect some processes and should always be followed by SYS_enableInts().
* You need to protect against interrupts any processes than can be perturbed / corrupted by the interrupt callback code (IO ports access in general but not only).
* Now by default SGDK doesn't do anything armful in its interrupts handlers (except with the Bitmap engine) so it's not necessary to protect from interrupts by default * but you may need it if your interrupts callback code does mess with VDP for instance.
* Note that you can nest #SYS_disableInts / #SYS_enableInts() calls. * * \see SYS_enableInts(void) */ void SYS_disableInts(void); /** * \brief * Re-enable interrupts (Vertical, Horizontal and External). * * This method is used to reenable interrupts after a call to #SYS_disableInts().
* Note that you can nest #SYS_disableInts / #SYS_enableInts() calls. * * \see SYS_disableInts(void) */ void SYS_enableInts(void); /** * \brief * Set user 'Vertical Blank' callback method. * * \param CB * Pointer to the method to call on Vertical Blank period.
* You can remove current callback by passing a NULL pointer here. * * Vertical blank period starts right at the end of display period.
* This period is usually used to prepare next frame data (refresh sprites, scrolling ...).
* SGDK handle that in the #SYS_doVBlankProcess() method and will call the user 'Vertical Blank' from this method after all major tasks.
* It's recommended to use the 'Vertical Blank' callback instead of the 'VInt' callback if you need to do some VDP accesses. * * \see SYS_setVIntCallback(VoidCallback *CB); */ void SYS_setVBlankCallback(VoidCallback *CB); /** * \brief * Set 'Vertical Interrupt' callback method, prefer #SYS_setVBlankCallback(..) when possible. * * \param CB * Pointer to the method to call on Vertical Interrupt.
* You can remove current callback by passing a NULL pointer here. * * Vertical interrupt happen at the end of display period at the start of the vertical blank period.
* This period is usually used to prepare next frame data (refresh sprites, scrolling ...) though now * SGDK handle most of these process using #SYS_doVBlankProcess() so you can control it manually (do it from main loop or put it in Vint callback).
* The only things that SGDK always handle from the vint callback is the XGM sound driver music tempo and Bitmap engine phase reset.
* It's recommended to keep your code as fast as possible as it will eat precious VBlank time, nor you should touch the VDP from your Vint callback * otherwise you will need to protect any VDP accesses from your main loop (which is painful), use the SYS_setVIntCallback(..) instead for that. * * \see SYS_setVBlankCallback(VoidCallback *CB); * \see SYS_setHIntCallback(VoidCallback *CB); */ void SYS_setVIntCallback(VoidCallback *CB); /** * \brief * Set 'Horizontal Interrupt' callback method (need to be prefixed by HINTERRUPT_CALLBACK). * * \param CB * Pointer to the method to call on Horizontal Interrupt.
* You can remove current callback by passing a NULL pointer here.
* You need to prefix your hint method with HINTERRUPT_CALLBACK:
*

HINTERRUPT_CALLBACK myHIntFunction() * { * ... * }

*
* Horizontal interrupt happen at the end of scanline display period right before Horizontal blank.
* This period is usually used to do mid frame changes (palette, scrolling or others raster effect).
* When you do that, don't forget to protect your VDP access from your main loop using * #SYS_disableInts() / #SYS_enableInts() otherwise you may corrupt your VDP writes. */ void SYS_setHIntCallback(VoidCallback *CB); /** * \brief * Set External interrupt callback method. * * \param CB * Pointer to the method to call on External Interrupt.
* You can remove current callback by passing a null pointer here. * * External interrupt happen on Light Gun trigger (HVCounter is locked). */ void SYS_setExtIntCallback(VoidCallback *CB); /** * \brief * Return TRUE if we are in the V-Interrupt process. * * This method tests if we are currently processing a Vertical retrace interrupt (V-Int callback). */ bool SYS_isInVInt(void); /** * \brief * Return != 0 if we are on a NTSC system. * * Better to use the IS_PAL_SYSTEM */ u16 SYS_isNTSC(void); /** * \brief * Return != 0 if we are on a PAL system. * * Better to use the IS_PAL_SYSTEM */ u16 SYS_isPAL(void); /** * \brief * Returns number of Frame Per Second. * * This function actually returns the number of time it was called in the last second.
* i.e: for benchmarking you should call this method only once per frame update. */ u32 SYS_getFPS(void); /** * \brief * Returns number of Frame Per Second (fix32 form). * * This function actually returns the number of time it was called in the last second.
* i.e: for benchmarking you should call this method only once per frame update. */ fix32 SYS_getFPSAsFloat(void); /** * \brief * Return an estimation of CPU frame load (in %) * * Return an estimation of CPU load (in %, mean value computed on 8 frames) based of idle time spent in #VDP_waitVSync() / #VDP_waitVInt() methods.
* The method can return value above 100% you CPU load is higher than 1 frame. * * \see VDP_waitVSync(void) * \see VDP_waitVInt(void) */ u16 SYS_getCPULoad(void); /** * \brief * Returns TRUE if frame load is currently displayed, FALSE otherwise * \see SYS_showFrameLoad(void) */ bool SYS_getShowFrameLoad(); /** * \brief * Show a cursor indicating current frame load level in scanline (top = 0% load, bottom = 100% load) * * \param mean * frame load level display is averaged on 8 frames (mean load) * * Show current frame load using a cursor indicating the scanline reached when #VDP_waitVSync() / #VDP_waitVInt() method was called.
* Note that internally sprite 0 is used to display to cursor (palette 0 and color 15) as it is not directly used by the Sprite Engine but * if you're using the low level VDP sprite methods then you should know that sprite 0 will be used here. * * \see SYS_hideFrameLoad(void) */ void SYS_showFrameLoad(bool mean); /** * \brief * Hide the frame load cursor previously enabled using #SYS_showFrameLoad() method. * \see SYS_showFrameLoad(void) */ void SYS_hideFrameLoad(void); /** * \brief * Computes full ROM checksum and return it.
* The checksum is a custom fast 32 bit checksum converted to 16 bit at end */ u16 SYS_computeChecksum(void); /** * \brief * Returns TRUE if ROM checksum is ok (correspond to rom_head.checksum field) */ bool SYS_isChecksumOk(void); /** * \brief * Die with the specified error message.
* Program execution is interrupted.
* Accepts a list of strings. The list must end with a NULL value. * * This actually display an error message and program ends execution. */ void SYS_die(char *err, ...); #endif // _SYS_H_