Skip to content

Commit a5f6009

Browse files
committed
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.
1 parent 4945d05 commit a5f6009

File tree

2 files changed

+14
-5
lines changed

2 files changed

+14
-5
lines changed

src/nba/src/arm/arm7tdmi.hpp

+4
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,8 @@ struct ARM7TDMI {
140140
bool is_banked = id >= 8 && id != 15;
141141

142142
if(unlikely(ldm_usermode_conflict && is_banked)) {
143+
// 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.
144+
// We take care in the LDM implementation that this branch is only taken if that was the case.
143145
result |= state.bank[BANK_NONE][id - 8];
144146
}
145147

@@ -154,6 +156,8 @@ struct ARM7TDMI {
154156
bool is_banked = id >= 8 && id != 15;
155157

156158
if(unlikely(ldm_usermode_conflict && is_banked)) {
159+
// 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.
160+
// We take care in the LDM implementation that this branch is only taken if that was the case.
157161
state.bank[BANK_NONE][id - 8] = value;
158162
}
159163

src/nba/src/arm/handlers/handler32.inl

+10-5
Original file line numberDiff line numberDiff line change
@@ -544,7 +544,7 @@ void ARM_BlockDataTransfer(u32 instruction) {
544544
int base = (instruction >> 16) & 0xF;
545545
int list = instruction & 0xFFFF;
546546

547-
Mode mode;
547+
Mode mode = state.cpsr.f.mode;
548548
bool transfer_pc = list & (1 << 15);
549549
int first = 0;
550550
int bytes = 0;
@@ -571,10 +571,15 @@ void ARM_BlockDataTransfer(u32 instruction) {
571571
bytes = 64;
572572
}
573573

574-
bool switch_mode = user_mode && (!load || !transfer_pc);
574+
/**
575+
* Whether the instruction is a LDM^ instruction and we need to switch to user mode.
576+
* Note that we only switch to user mode if we aren't in user or system mode already.
577+
* This is important for emulation of the LDM user mode register bus conflict,
578+
* since the implementation of this quirk in GetReg() and SetReg() only works if the CPU isn't in user or system mode anymore.
579+
*/
580+
const bool switch_mode = user_mode && (!load || !transfer_pc) && mode != MODE_USR && mode != MODE_SYS;
575581

576582
if (switch_mode) {
577-
mode = state.cpsr.f.mode;
578583
SwitchMode(MODE_USR);
579584
}
580585

@@ -633,10 +638,10 @@ void ARM_BlockDataTransfer(u32 instruction) {
633638
bus.Idle();
634639

635640
if (switch_mode) {
636-
/* During the following two cycles of a usermode LDM,
641+
/* During the following two cycles of a user mode LDM,
637642
* register accesses will go to both the user bank and original bank.
638643
*/
639-
ldm_usermode_conflict = true;
644+
ldm_usermode_conflict = mode != MODE_USR && mode != MODE_SYS;
640645
scheduler.Add(2, Scheduler::EventClass::ARM_ldm_usermode_conflict);
641646
}
642647

0 commit comments

Comments
 (0)