Skip to content

Commit

Permalink
ARM: attempt to fix edge-case in LDM^ instruction executed during use…
Browse files Browse the repository at this point in the history
…r/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.
  • Loading branch information
fleroviux committed Jan 21, 2025
1 parent 4945d05 commit 46cbdb3
Show file tree
Hide file tree
Showing 2 changed files with 13 additions and 4 deletions.
4 changes: 4 additions & 0 deletions src/nba/src/arm/arm7tdmi.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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];
}

Expand All @@ -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;
}

Expand Down
13 changes: 9 additions & 4 deletions src/nba/src/arm/handlers/handler32.inl
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
}

Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit 46cbdb3

Please sign in to comment.