Adding DMA transfer "Sleep" mode

This accounts for DMA stealing cycles from the CPU whenever the CPU
triggers a DMA (does not affect H/V blank or sound DMAs).
Works by moving the CPU to a PAUSED state where the cycles are accounted
for, reusing a similar mechanism for HALT/STOP.

Fixes a couple of games, notably GTA that has a DMA/IRQ race condition
(likely a bug really) if cycles are grossly miscalculated.
This commit is contained in:
David Guillen Fandos 2023-09-08 19:44:13 +02:00
parent 2352adcc50
commit c0d8ffaa38
9 changed files with 46 additions and 11 deletions

View File

@ -57,7 +57,7 @@ _##symbol:
#define REG_Z_FLAG (21 * 4)
#define REG_C_FLAG (22 * 4)
#define REG_V_FLAG (23 * 4)
#define REG_UNUSED_1 (24 * 4)
#define REG_SLEEP_CYCLES (24 * 4)
#define OAM_UPDATED (25 * 4)
#define REG_SAVE (26 * 4)

View File

@ -39,7 +39,7 @@ _##symbol:
#define REG_Z_FLAG (21 * 4)
#define REG_C_FLAG (22 * 4)
#define REG_V_FLAG (23 * 4)
#define REG_UNUSED_1 (24 * 4)
#define REG_SLEEP_CYCLES (24 * 4)
#define OAM_UPDATED (25 * 4)
#define CPU_ALERT_HALT (1 << 0)

6
cpu.c
View File

@ -1489,7 +1489,12 @@ u32 check_and_raise_interrupts()
reg[REG_PC] = 0x00000018;
set_cpu_mode(MODE_IRQ);
// Wake up CPU if it is stopped/sleeping.
if (reg[CPU_HALT_STATE] == CPU_STOP ||
reg[CPU_HALT_STATE] == CPU_HALT)
reg[CPU_HALT_STATE] = CPU_ACTIVE;
return 1;
}
return 0;
@ -3690,6 +3695,7 @@ void init_cpu(void)
spsr[i] = 0x00000010;
reg[CPU_HALT_STATE] = CPU_ACTIVE;
reg[REG_SLEEP_CYCLES] = 0;
if (selected_boot_mode == boot_game) {
reg[REG_SP] = 0x03007F00;

3
cpu.h
View File

@ -45,6 +45,7 @@ typedef u32 cpu_mode_type;
#define CPU_ACTIVE 0
#define CPU_HALT 1
#define CPU_STOP 2
#define CPU_DMA 3 /* CPU is idling due to DMA transfer */
typedef u8 cpu_alert_type;
@ -91,7 +92,7 @@ typedef enum
REG_Z_FLAG = 21,
REG_C_FLAG = 22,
REG_V_FLAG = 23,
REG_UNUSED_1 = 24,
REG_SLEEP_CYCLES = 24,
OAM_UPDATED = 25,
REG_SAVE = 26,
REG_SAVE2 = 27,

View File

@ -761,8 +761,17 @@ static cpu_alert_type trigger_dma(u32 dma_number, u32 value)
}
write_dmareg(REG_DMA0CNT_H, dma_number, value);
if(start_type == DMA_START_IMMEDIATELY)
return dma_transfer(dma_number, NULL);
if(start_type == DMA_START_IMMEDIATELY) {
// Excutes the DMA now! Copies the data and returns side effects.
int dma_cycles = 0;
cpu_alert_type ret = dma_transfer(dma_number, &dma_cycles);
if (!dma_cycles)
return ret;
// Sleep CPU for N cycles and return HALT as side effect (so it does).
reg[CPU_HALT_STATE] = CPU_DMA;
reg[REG_SLEEP_CYCLES] = 0x80000000 | (u32)dma_cycles;
return CPU_ALERT_HALT | ret;
}
}
}
else

23
main.c
View File

@ -247,6 +247,22 @@ u32 function_cc update_gba(int remaining_cycles)
// a video event or a timer event, whatever happens first.
execute_cycles = MAX(video_count, 0);
// If we are paused due to a DMA, cap the number of cyles to that amount.
if (reg[CPU_HALT_STATE] == CPU_DMA) {
u32 dma_cyc = reg[REG_SLEEP_CYCLES];
// The first iteration is marked by bit 31 set, just do nothing now.
if (dma_cyc & 0x80000000)
dma_cyc &= 0x7FFFFFFF; // Start counting DMA cycles from now on.
else
dma_cyc -= MIN(dma_cyc, completed_cycles); // Account DMA cycles.
reg[REG_SLEEP_CYCLES] = dma_cyc;
if (!dma_cyc)
reg[CPU_HALT_STATE] = CPU_ACTIVE; // DMA finished, resume execution.
else
execute_cycles = MIN(execute_cycles, dma_cyc); // Continue sleeping.
}
for (i = 0; i < 4; i++)
{
if (timer[i].status == TIMER_PRESCALE &&
@ -294,7 +310,8 @@ bool main_check_savestate(const u8 *src)
if (!bson_contains_key(p1, "cpu-ticks", BSON_TYPE_INT32) ||
!bson_contains_key(p1, "exec-cycles", BSON_TYPE_INT32) ||
!bson_contains_key(p1, "video-count", BSON_TYPE_INT32))
!bson_contains_key(p1, "video-count", BSON_TYPE_INT32) ||
!bson_contains_key(p1, "sleep-cycles", BSON_TYPE_INT32))
return false;
for (i = 0; i < 4; i++)
@ -327,7 +344,8 @@ bool main_read_savestate(const u8 *src)
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)))
bson_read_int32(p1, "video-count", (u32*)&video_count) &&
bson_read_int32(p1, "sleep-cycles", &reg[REG_SLEEP_CYCLES])))
return false;
for (i = 0; i < 4; i++)
@ -357,6 +375,7 @@ unsigned main_write_savestate(u8* dst)
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_write_int32(dst, "sleep-cycles", reg[REG_SLEEP_CYCLES]);
bson_finish_document(dst, wbptr);
bson_start_document(dst, "timers", wbptr);

View File

@ -104,7 +104,7 @@ symbol:
.equ REG_Z_FLAG, (21 * 4)
.equ REG_C_FLAG, (22 * 4)
.equ REG_V_FLAG, (23 * 4)
.equ REG_UNUSED_1, (24 * 4)
.equ REG_SLEEP_CYCLES, (24 * 4)
.equ OAM_UPDATED, (25 * 4)
.equ REG_SAVE, (26 * 4)
.equ REG_SAVE2, (27 * 4)

View File

@ -92,7 +92,7 @@ bool bson_read_bytes(const u8 *srcp, const char *key, void* buffer, unsigned cnt
/* this is an upper limit, leave room for future (?) stuff */
#define GBA_STATE_MEM_SIZE (416*1024)
#define GBA_STATE_MAGIC 0x06BAC0DE
#define GBA_STATE_VERSION 0x00010001
#define GBA_STATE_VERSION 0x00010002
bool gba_load_state(const void *src);
void gba_save_state(void *dst);

View File

@ -95,7 +95,7 @@ _##symbol:
.equ REG_Z_FLAG, (21 * 4)
.equ REG_C_FLAG, (22 * 4)
.equ REG_V_FLAG, (23 * 4)
.equ REG_UNUSED_1, (24 * 4)
.equ REG_SLEEP_CYCLES, (24 * 4)
.equ OAM_UPDATED, (25 * 4)
.equ REG_SAVE, (26 * 4)