From c0d8ffaa384f724e1a0743e18cb042c29dd48f7f Mon Sep 17 00:00:00 2001 From: David Guillen Fandos Date: Fri, 8 Sep 2023 19:44:13 +0200 Subject: [PATCH] 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. --- arm/arm64_stub.S | 2 +- arm/arm_stub.S | 2 +- cpu.c | 8 +++++++- cpu.h | 3 ++- gba_memory.c | 13 +++++++++++-- main.c | 23 +++++++++++++++++++++-- mips/mips_stub.S | 2 +- savestate.h | 2 +- x86/x86_stub.S | 2 +- 9 files changed, 46 insertions(+), 11 deletions(-) diff --git a/arm/arm64_stub.S b/arm/arm64_stub.S index 84cf170..06128e6 100644 --- a/arm/arm64_stub.S +++ b/arm/arm64_stub.S @@ -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) diff --git a/arm/arm_stub.S b/arm/arm_stub.S index 01078f7..b192006 100644 --- a/arm/arm_stub.S +++ b/arm/arm_stub.S @@ -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) diff --git a/cpu.c b/cpu.c index bd3767a..2f2d3c8 100644 --- a/cpu.c +++ b/cpu.c @@ -1489,7 +1489,12 @@ u32 check_and_raise_interrupts() reg[REG_PC] = 0x00000018; set_cpu_mode(MODE_IRQ); - reg[CPU_HALT_STATE] = CPU_ACTIVE; + + // 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; diff --git a/cpu.h b/cpu.h index 0887a98..b6c8f56 100644 --- a/cpu.h +++ b/cpu.h @@ -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, diff --git a/gba_memory.c b/gba_memory.c index a14a40c..7a37bd4 100644 --- a/gba_memory.c +++ b/gba_memory.c @@ -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 diff --git a/main.c b/main.c index b912bb7..3df7ca0 100644 --- a/main.c +++ b/main.c @@ -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_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); diff --git a/mips/mips_stub.S b/mips/mips_stub.S index c29f6ef..72974c8 100644 --- a/mips/mips_stub.S +++ b/mips/mips_stub.S @@ -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) diff --git a/savestate.h b/savestate.h index 2fce2b8..aece48a 100644 --- a/savestate.h +++ b/savestate.h @@ -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); diff --git a/x86/x86_stub.S b/x86/x86_stub.S index cbd2c94..d411f09 100644 --- a/x86/x86_stub.S +++ b/x86/x86_stub.S @@ -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)