From 46cbdb3b771baea344d9eeef04b227ace2045473 Mon Sep 17 00:00:00 2001 From: fleroviux Date: Tue, 21 Jan 2025 20:38:53 +0100 Subject: [PATCH] ARM: attempt to fix edge-case in LDM^ instruction executed during user/system mode The LDM^ (user mode LDM) register bus conflict was not implemented correctly, when the CPU is in system or user mode when executing the instruction. This commit attempts to fix the incorrect behavior, although it is unverified. --- src/nba/src/arm/arm7tdmi.hpp | 4 ++++ src/nba/src/arm/handlers/handler32.inl | 13 +++++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/nba/src/arm/arm7tdmi.hpp b/src/nba/src/arm/arm7tdmi.hpp index 277cd1af..7d37bea5 100644 --- a/src/nba/src/arm/arm7tdmi.hpp +++ b/src/nba/src/arm/arm7tdmi.hpp @@ -140,6 +140,8 @@ struct ARM7TDMI { bool is_banked = id >= 8 && id != 15; if(unlikely(ldm_usermode_conflict && is_banked)) { + // This array holds the current user/sys bank value only if the CPU wasn't in user or system mode all along during the user mode LDM instruction. + // We take care in the LDM implementation that this branch is only taken if that was the case. result |= state.bank[BANK_NONE][id - 8]; } @@ -154,6 +156,8 @@ struct ARM7TDMI { bool is_banked = id >= 8 && id != 15; if(unlikely(ldm_usermode_conflict && is_banked)) { + // This array holds the current user/sys bank value only if the CPU wasn't in user or system mode all along during the user mode LDM instruction. + // We take care in the LDM implementation that this branch is only taken if that was the case. state.bank[BANK_NONE][id - 8] = value; } diff --git a/src/nba/src/arm/handlers/handler32.inl b/src/nba/src/arm/handlers/handler32.inl index b99d00d9..386c5faa 100644 --- a/src/nba/src/arm/handlers/handler32.inl +++ b/src/nba/src/arm/handlers/handler32.inl @@ -544,7 +544,7 @@ void ARM_BlockDataTransfer(u32 instruction) { int base = (instruction >> 16) & 0xF; int list = instruction & 0xFFFF; - Mode mode; + Mode mode = state.cpsr.f.mode; bool transfer_pc = list & (1 << 15); int first = 0; int bytes = 0; @@ -571,10 +571,15 @@ void ARM_BlockDataTransfer(u32 instruction) { bytes = 64; } - bool switch_mode = user_mode && (!load || !transfer_pc); + /** + * Whether the instruction is a LDM^ instruction and we need to switch to user mode. + * Note that we only switch to user mode if we aren't in user or system mode already. + * This is important for emulation of the LDM user mode register bus conflict, + * since the implementation of this quirk in GetReg() and SetReg() only works if the CPU isn't in user or system mode anymore. + */ + const bool switch_mode = user_mode && (!load || !transfer_pc) && mode != MODE_USR && mode != MODE_SYS; if (switch_mode) { - mode = state.cpsr.f.mode; SwitchMode(MODE_USR); } @@ -633,7 +638,7 @@ void ARM_BlockDataTransfer(u32 instruction) { bus.Idle(); if (switch_mode) { - /* During the following two cycles of a usermode LDM, + /* During the following two cycles of a user mode LDM, * register accesses will go to both the user bank and original bank. */ ldm_usermode_conflict = true;