gpsp/main.c
David Guillen Fandos 33f1e25099 Emit BIOS SWI entrypoint to ROM arena
This fixes a race condition that happens whenever the ROM cache is flushed but
the RAM one is not, causing any SWI calls (implemented as direct branches) to
jump to random instructions.
The fix could be to flush both caches at the same time (~expensive on
low mem platforms), use indirect jumps (a bit expensive) or emit the SWI
handler below the watermark to ensure it is never flushed. This is cheap
and effective, requires minimal changes.
2021-09-10 00:30:55 +02:00

343 lines
8.2 KiB
C

/* gameplaySP
*
* Copyright (C) 2006 Exophase <exophase@gmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "common.h"
#include <ctype.h>
timer_type timer[4];
const u32 global_cycles_per_instruction = 1;
u32 cpu_ticks = 0;
u32 execute_cycles = 960;
s32 video_count = 960;
u32 last_frame = 0;
u32 flush_ram_count = 0;
u32 gbc_update_count = 0;
u32 oam_update_count = 0;
char main_path[512];
char save_path[512];
void trigger_ext_event(void);
static void update_timers(irq_type *irq_raised)
{
unsigned i;
for (i = 0; i < 4; i++)
{
if(timer[i].status == TIMER_INACTIVE)
continue;
if(timer[i].status != TIMER_CASCADE)
{
timer[i].count -= execute_cycles;
/* io_registers accessors range: REG_TM0D, REG_TM1D, REG_TM2D, REG_TM3D */
write_ioreg(REG_TM0D + (i * 2), -(timer[i].count > timer[i].prescale));
}
if(timer[i].count > 0)
continue;
/* irq_raised value range: IRQ_TIMER0, IRQ_TIMER1, IRQ_TIMER2, IRQ_TIMER3 */
if(timer[i].irq == TIMER_TRIGGER_IRQ)
*irq_raised |= (8 << i);
if((i != 3) && (timer[i + 1].status == TIMER_CASCADE))
{
timer[i + 1].count--;
write_ioreg(REG_TM0D + (i + 1) * 2, -timer[i+1].count);
}
if(i < 2)
{
if(timer[i].direct_sound_channels & 0x01)
sound_timer(timer[i].frequency_step, 0);
if(timer[i].direct_sound_channels & 0x02)
sound_timer(timer[i].frequency_step, 1);
}
timer[i].count += (timer[i].reload << timer[i].prescale);
}
}
void init_main(void)
{
u32 i;
for(i = 0; i < 4; i++)
{
dma[i].start_type = DMA_INACTIVE;
dma[i].direct_sound_channel = DMA_NO_DIRECT_SOUND;
timer[i].status = TIMER_INACTIVE;
timer[i].reload = 0x10000;
}
timer[0].direct_sound_channels = TIMER_DS_CHANNEL_BOTH;
timer[1].direct_sound_channels = TIMER_DS_CHANNEL_NONE;
cpu_ticks = 0;
execute_cycles = 960;
video_count = 960;
#ifdef HAVE_DYNAREC
init_emitter();
init_caches();
#endif
}
u32 update_gba(void)
{
irq_type irq_raised = IRQ_NONE;
do
{
unsigned i;
cpu_ticks += execute_cycles;
reg[CHANGED_PC_STATUS] = 0;
reg[COMPLETED_FRAME] = 0;
if(gbc_sound_update)
{
gbc_update_count++;
update_gbc_sound(cpu_ticks);
gbc_sound_update = 0;
}
update_timers(&irq_raised);
video_count -= execute_cycles;
if(video_count <= 0)
{
u32 vcount = read_ioreg(REG_VCOUNT);
u32 dispstat = read_ioreg(REG_DISPSTAT);
if((dispstat & 0x02) == 0)
{
// Transition from hrefresh to hblank
video_count += (272);
dispstat |= 0x02;
if((dispstat & 0x01) == 0)
{
u32 i;
if(reg[OAM_UPDATED])
oam_update_count++;
update_scanline();
// If in visible area also fire HDMA
for(i = 0; i < 4; i++)
{
if(dma[i].start_type == DMA_START_HBLANK)
dma_transfer(i);
}
}
if(dispstat & 0x10)
irq_raised |= IRQ_HBLANK;
}
else
{
// Transition from hblank to next line
video_count += 960;
dispstat &= ~0x02;
vcount++;
if(vcount == 160)
{
// Transition from vrefresh to vblank
u32 i;
dispstat |= 0x01;
if(dispstat & 0x8)
{
irq_raised |= IRQ_VBLANK;
}
video_reload_counters();
for(i = 0; i < 4; i++)
{
if(dma[i].start_type == DMA_START_VBLANK)
dma_transfer(i);
}
}
else
if(vcount == 228)
{
// Transition from vblank to next screen
dispstat &= ~0x01;
/* printf("frame update (%x), %d instructions total, %d RAM flushes\n",
reg[REG_PC], instruction_count - last_frame, flush_ram_count);
last_frame = instruction_count;
*/
/* printf("%d gbc audio updates\n", gbc_update_count);
printf("%d oam updates\n", oam_update_count); */
gbc_update_count = 0;
oam_update_count = 0;
flush_ram_count = 0;
update_gbc_sound(cpu_ticks);
gbc_sound_update = 0;
/* If there's no cheat hook, run on vblank! */
if (cheat_master_hook == ~0U)
process_cheats();
vcount = 0;
// We completed a frame, tell the dynarec to exit to the main thread
reg[COMPLETED_FRAME] = 1;
}
if(vcount == (dispstat >> 8))
{
// vcount trigger
dispstat |= 0x04;
if(dispstat & 0x20)
{
irq_raised |= IRQ_VCOUNT;
}
}
else
dispstat &= ~0x04;
write_ioreg(REG_VCOUNT, vcount);
}
write_ioreg(REG_DISPSTAT, dispstat);
}
if(irq_raised)
raise_interrupt(irq_raised);
execute_cycles = video_count;
for (i = 0; i < 4; i++)
{
if(timer[i].status != TIMER_PRESCALE)
continue;
if(timer[i].count < execute_cycles)
execute_cycles = timer[i].count;
}
} while(reg[CPU_HALT_STATE] != CPU_ACTIVE && !reg[COMPLETED_FRAME]);
return execute_cycles;
}
void reset_gba(void)
{
init_memory();
init_main();
init_cpu();
reset_sound();
}
u32 file_length(FILE *fp)
{
u32 length;
fseek(fp, 0, SEEK_END);
length = ftell(fp);
fseek(fp, 0, SEEK_SET);
return length;
}
void change_ext(const char *src, char *buffer, const char *extension)
{
char *dot_position;
strcpy(buffer, src);
dot_position = strrchr(buffer, '.');
if(dot_position)
strcpy(dot_position, extension);
}
bool main_read_savestate(const u8 *src)
{
int i;
const u8 *p1 = bson_find_key(src, "emu");
const u8 *p2 = bson_find_key(src, "timers");
if (!p1 || !p2)
return false;
execute_cycles = 123;
if (!(bson_read_int32(p1, "cpu-ticks", &cpu_ticks) &&
bson_read_int32(p1, "exec-cycles", &execute_cycles) &&
bson_read_int32(p1, "video-count", (u32*)&video_count)))
return false;
for (i = 0; i < 4; i++)
{
char tname[2] = {'0' + i, 0};
const u8 *p = bson_find_key(p2, tname);
if (!(
bson_read_int32(p, "count", (u32*)&timer[i].count) &&
bson_read_int32(p, "reload", &timer[i].reload) &&
bson_read_int32(p, "prescale", &timer[i].prescale) &&
bson_read_int32(p, "freq-step", &timer[i].frequency_step) &&
bson_read_int32(p, "dsc", &timer[i].direct_sound_channels) &&
bson_read_int32(p, "irq", &timer[i].irq) &&
bson_read_int32(p, "status", &timer[i].status)))
return false;
}
return true;
}
unsigned main_write_savestate(u8* dst)
{
int i;
u8 *wbptr, *wbptr2, *startp = dst;
bson_start_document(dst, "emu", wbptr);
bson_write_int32(dst, "cpu-ticks", cpu_ticks);
bson_write_int32(dst, "exec-cycles", execute_cycles);
bson_write_int32(dst, "video-count", video_count);
bson_finish_document(dst, wbptr);
bson_start_document(dst, "timers", wbptr);
for (i = 0; i < 4; i++)
{
char tname[2] = {'0' + i, 0};
bson_start_document(dst, tname, wbptr2);
bson_write_int32(dst, "count", timer[i].count);
bson_write_int32(dst, "reload", timer[i].reload);
bson_write_int32(dst, "prescale", timer[i].prescale);
bson_write_int32(dst, "freq-step", timer[i].frequency_step);
bson_write_int32(dst, "dsc", timer[i].direct_sound_channels);
bson_write_int32(dst, "irq", timer[i].irq);
bson_write_int32(dst, "status", timer[i].status);
bson_finish_document(dst, wbptr2);
}
bson_finish_document(dst, wbptr);
return (unsigned int)(dst - startp);
}